diff options
Diffstat (limited to 'src/fields')
| -rw-r--r-- | src/fields/AudioField.ts | 8 | ||||
| -rw-r--r-- | src/fields/BasicField.ts | 8 | ||||
| -rw-r--r-- | src/fields/BooleanField.ts | 25 | ||||
| -rw-r--r-- | src/fields/Document.ts | 201 | ||||
| -rw-r--r-- | src/fields/DocumentReference.ts | 6 | ||||
| -rw-r--r-- | src/fields/Field.ts | 8 | ||||
| -rw-r--r-- | src/fields/HtmlField.ts | 6 | ||||
| -rw-r--r-- | src/fields/ImageField.ts | 8 | ||||
| -rw-r--r-- | src/fields/InkField.ts | 6 | ||||
| -rw-r--r-- | src/fields/Key.ts | 10 | ||||
| -rw-r--r-- | src/fields/KeyStore.ts | 19 | ||||
| -rw-r--r-- | src/fields/ListField.ts | 139 | ||||
| -rw-r--r-- | src/fields/NumberField.ts | 10 | ||||
| -rw-r--r-- | src/fields/PDFField.ts | 10 | ||||
| -rw-r--r-- | src/fields/RichTextField.ts | 6 | ||||
| -rw-r--r-- | src/fields/ScriptField.ts | 101 | ||||
| -rw-r--r-- | src/fields/TextField.ts | 8 | ||||
| -rw-r--r-- | src/fields/TupleField.ts | 10 | ||||
| -rw-r--r-- | src/fields/VideoField.ts | 8 | ||||
| -rw-r--r-- | src/fields/WebField.ts | 8 |
20 files changed, 440 insertions, 165 deletions
diff --git a/src/fields/AudioField.ts b/src/fields/AudioField.ts index 8864471ae..87e47a715 100644 --- a/src/fields/AudioField.ts +++ b/src/fields/AudioField.ts @@ -4,7 +4,7 @@ import { Types } from "../server/Message"; export class AudioField extends BasicField<URL> { constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) { - super(data == undefined ? new URL("http://techslides.com/demos/samples/sample.mp3") : data, save, id); + super(data === undefined ? new URL("http://techslides.com/demos/samples/sample.mp3") : data, save, id); } toString(): string { @@ -20,12 +20,12 @@ export class AudioField extends BasicField<URL> { return new AudioField(this.Data); } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.Audio, data: this.Data.href, - _id: this.Id - } + id: this.Id + }; } }
\ No newline at end of file diff --git a/src/fields/BasicField.ts b/src/fields/BasicField.ts index a92c4a236..17b1fc4e8 100644 --- a/src/fields/BasicField.ts +++ b/src/fields/BasicField.ts @@ -1,4 +1,4 @@ -import { Field, FieldId } from "./Field" +import { Field, FieldId } from "./Field"; import { observable, computed, action } from "mobx"; import { Server } from "../client/Server"; import { UndoManager } from "../client/util/UndoManager"; @@ -9,7 +9,7 @@ export abstract class BasicField<T> extends Field { this.data = data; if (save) { - Server.UpdateField(this) + Server.UpdateField(this); } } @@ -36,7 +36,7 @@ export abstract class BasicField<T> extends Field { UndoManager.AddEvent({ undo: () => this.Data = oldValue, redo: () => this.Data = value - }) + }); Server.UpdateField(this); } @@ -46,7 +46,7 @@ export abstract class BasicField<T> extends Field { @action TrySetValue(value: any): boolean { - if (typeof value == typeof this.data) { + if (typeof value === typeof this.data) { this.Data = value; return true; } diff --git a/src/fields/BooleanField.ts b/src/fields/BooleanField.ts new file mode 100644 index 000000000..d49bfe82b --- /dev/null +++ b/src/fields/BooleanField.ts @@ -0,0 +1,25 @@ +import { BasicField } from "./BasicField"; +import { FieldId } from "./Field"; +import { Types } from "../server/Message"; + +export class BooleanField extends BasicField<boolean> { + constructor(data: boolean = false as boolean, id?: FieldId, save: boolean = true as boolean) { + super(data, save, id); + } + + ToScriptString(): string { + return `new BooleanField("${this.Data}")`; + } + + Copy() { + return new BooleanField(this.Data); + } + + ToJson() { + return { + type: Types.Boolean, + data: this.Data, + id: this.Id + }; + } +} diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 85ff6ddcb..7cf784f0e 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -1,6 +1,6 @@ -import { Key } from "./Key" +import { Key } from "./Key"; import { KeyStore } from "./KeyStore"; -import { Field, Cast, FieldWaiting, FieldValue, FieldId, Opt } from "./Field" +import { Field, Cast, FieldWaiting, FieldValue, FieldId, Opt } from "./Field"; import { NumberField } from "./NumberField"; import { ObservableMap, computed, action, runInAction } from "mobx"; import { TextField } from "./TextField"; @@ -9,19 +9,29 @@ import { Server } from "../client/Server"; import { Types } from "../server/Message"; import { UndoManager } from "../client/util/UndoManager"; import { HtmlField } from "./HtmlField"; +import { BooleanField } from "./BooleanField"; +import { allLimit } from "async"; +import { prototype } from "nodemailer/lib/smtp-pool"; +import { HistogramField } from "../client/northstar/dash-fields/HistogramField"; export class Document extends Field { //TODO tfs: We should probably store FieldWaiting in fields when we request it from the server so that we don't set up multiple server gets for the same document and field - public fields: ObservableMap<string, { key: Key, field: Field }> = 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) + super(id); if (save) { - Server.UpdateField(this) + Server.UpdateField(this); } } + static FromJson(data: any, id: string, save: boolean): Document { + let doc = new Document(id, save); + let fields = data as [string, string][]; + fields.forEach(pair => doc._proxies.set(pair[0], pair[1])); + return doc; + } UpdateFromServer(data: [string, string][]) { for (const key in data) { @@ -30,22 +40,24 @@ export class Document extends Field { } } - 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) } + public Width = () => this.GetNumber(KeyStore.Width, 0); + public Height = () => 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 = () => this.GetNumber(KeyStore.Scale, 1); @computed public get Title(): string { let title = this.Get(KeyStore.Title, true); - if (title) - if (title != FieldWaiting && title instanceof TextField) + if (title || title === FieldWaiting) { + if (title !== FieldWaiting && title instanceof TextField) { return title.Data; + } else return "-waiting-"; + } let parTitle = this.GetT(KeyStore.Title, TextField); - if (parTitle) - if (parTitle != FieldWaiting) - return parTitle.Data + ".alias"; + if (parTitle || parTitle === FieldWaiting) { + if (parTitle !== FieldWaiting) return parTitle.Data + ".alias"; else return "-waiting-.alias"; + } return "-untitled-"; } @@ -57,7 +69,7 @@ export class Document extends Field { /** * Get the field in the document associated with the given key. If the * associated field has not yet been filled in from the server, a request - * to the server will automatically be sent, the value will be filled in + * to the server will automatically be sent, the value will be filled in * when the request is completed, and {@link Field.ts#FieldWaiting} will be returned. * @param key - The key of the value to get * @param ignoreProto - If true, ignore any prototype this document @@ -65,7 +77,7 @@ export class Document extends Field { * If false (default), search up the prototype chain, starting at this document, * for a document that has a field associated with the given key, and return the first * one found. - * + * * @returns If the document does not have a field associated with the given key, returns `undefined`. * If the document does have an associated field, but the field has not been fetched from the server, returns {@link Field.ts#FieldWaiting}. * If the document does have an associated field, and the field has not been fetched from the server, returns the associated field. @@ -78,10 +90,10 @@ export class Document extends 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 - */ + 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 { @@ -90,17 +102,17 @@ export class Document extends Field { } } else { let doc: FieldValue<Document> = this; - while (doc && doc != FieldWaiting && field != FieldWaiting) { + while (doc && field !== FieldWaiting) { 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 - */ + 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 { @@ -108,7 +120,10 @@ export class Document extends Field { } break; } - if ((doc.fields.has(KeyStore.Prototype.Id) || doc._proxies.has(KeyStore.Prototype.Id))) { + if ( + doc.fields.has(KeyStore.Prototype.Id) || + doc._proxies.has(KeyStore.Prototype.Id) + ) { doc = doc.GetPrototype(); } else { break; @@ -118,8 +133,7 @@ export class Document extends Field { break; } } - if (doc == FieldWaiting) - field = FieldWaiting; + if (doc === FieldWaiting) field = FieldWaiting; } return field; @@ -148,24 +162,32 @@ export class Document extends Field { } else { callback(undefined); } - }) + }); } else { callback(undefined); } } GetTAsync<T extends Field>(key: Key, ctor: { new(): T }): Promise<Opt<T>>; - GetTAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: Opt<T>) => void): void; - GetTAsync<T extends Field>(key: Key, ctor: { new(): T }, callback?: (field: Opt<T>) => void): Promise<Opt<T>> | void { + GetTAsync<T extends Field>( + key: Key, + ctor: { new(): T }, + callback: (field: Opt<T>) => void + ): void; + GetTAsync<T extends Field>( + key: Key, + ctor: { new(): T }, + callback?: (field: Opt<T>) => void + ): Promise<Opt<T>> | void { let fn = (cb: (field: Opt<T>) => void) => { - return this.GetAsync(key, (field) => { + return this.GetAsync(key, field => { cb(Cast(field, ctor)); }); - } + }; if (callback) { fn(callback); } else { - return new Promise(res => fn(res)); + return new Promise(fn); } } @@ -175,10 +197,14 @@ export class Document extends Field { * or the field associated with the given key is not of the given type. * @param ctor - Constructor of the field type to get. E.g., TextField, ImageField, etc. */ - GetOrCreateAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: T) => void): void { + GetOrCreateAsync<T extends Field>( + key: Key, + ctor: { new(): T }, + callback: (field: T) => void + ): void { //This currently doesn't deal with prototypes if (this._proxies.has(key.Id)) { - Server.GetDocumentField(this, key, (field) => { + Server.GetDocumentField(this, key, field => { if (field && field instanceof ctor) { callback(field); } else { @@ -201,17 +227,25 @@ export class Document extends Field { * @returns Same as {@link Document#Get}, except will return `undefined` * if there is an associated field but it is of the wrong type. */ - GetT<T extends Field = Field>(key: Key, ctor: { new(...args: any[]): T }, ignoreProto: boolean = false): FieldValue<T> { + 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) { + if (getfield !== FieldWaiting) { return Cast(getfield, ctor); } return FieldWaiting; } - GetOrCreate<T extends Field>(key: Key, ctor: { new(): T }, ignoreProto: boolean = false): T { + GetOrCreate<T extends Field>( + key: Key, + ctor: { new(): T }, + ignoreProto: boolean = false + ): T { const field = this.GetT(key, ctor, ignoreProto); - if (field && field != FieldWaiting) { + if (field && field !== FieldWaiting) { return field; } const newField = new ctor(); @@ -219,9 +253,13 @@ export class Document extends Field { return newField; } - GetData<T, U extends Field & { Data: T }>(key: Key, ctor: { new(): U }, defaultVal: T): T { + GetData<T, U extends Field & { Data: T }>( + key: Key, + ctor: { new(): U }, + defaultVal: T + ): T { let val = this.Get(key); - let vval = (val && val instanceof ctor) ? val.Data : defaultVal; + let vval = val && val instanceof ctor ? val.Data : defaultVal; return vval; } @@ -229,6 +267,10 @@ export class Document extends Field { return this.GetData(key, HtmlField, defaultVal); } + GetBoolean(key: Key, defaultVal: boolean): boolean { + return this.GetData(key, BooleanField, defaultVal); + } + GetNumber(key: Key, defaultVal: number): number { return this.GetData(key, NumberField, defaultVal); } @@ -238,7 +280,7 @@ export class Document extends Field { } GetList<T extends Field>(key: Key, defaultVal: T[]): T[] { - return this.GetData<T[], ListField<T>>(key, ListField, defaultVal) + return this.GetData<T[], ListField<T>>(key, ListField, defaultVal); } @action @@ -246,16 +288,15 @@ export class Document extends Field { let old = this.fields.get(key.Id); let oldField = old ? old.field : undefined; if (setOnPrototype) { - this.SetOnPrototype(key, field) - } - else { + this.SetOnPrototype(key, field); + } else { if (field) { this.fields.set(key.Id, { key, field }); - this._proxies.set(key.Id, field.Id) + this._proxies.set(key.Id, field.Id); // Server.AddDocumentField(this, key, field); } else { this.fields.delete(key.Id); - this._proxies.delete(key.Id) + this._proxies.delete(key.Id); // Server.DeleteDocumentField(this, key); } Server.UpdateField(this); @@ -264,22 +305,22 @@ export class Document extends Field { UndoManager.AddEvent({ undo: () => this.Set(key, oldField, setOnPrototype), redo: () => this.Set(key, field, setOnPrototype) - }) + }); } } @action SetOnPrototype(key: Key, field: Field | undefined): void { this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => { - f && f.Set(key, field) - }) + f && f.Set(key, field); + }); } @action SetDataOnPrototype<T, U extends Field & { Data: T }>(key: Key, value: T, ctor: { new(): U }, replaceWrongType = true) { this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => { - f && f.SetData(key, value, ctor) - }) + f && f.SetData(key, value, ctor, replaceWrongType); + }); } @action @@ -298,7 +339,10 @@ export class Document extends Field { SetText(key: Key, value: string, replaceWrongType = true) { this.SetData(key, value, TextField, replaceWrongType); } - + @action + SetBoolean(key: Key, value: boolean, replaceWrongType = true) { + this.SetData(key, value, BooleanField, replaceWrongType); + } @action SetNumber(key: Key, value: number, replaceWrongType = true) { this.SetData(key, value, NumberField, replaceWrongType); @@ -311,7 +355,7 @@ export class Document extends Field { GetAllPrototypes(): Document[] { let protos: Document[] = []; let doc: FieldValue<Document> = this; - while (doc && doc != FieldWaiting) { + while (doc && doc !== FieldWaiting) { protos.push(doc); doc = doc.GetPrototype(); } @@ -319,12 +363,12 @@ export class Document extends Field { } CreateAlias(id?: string): Document { - let alias = new Document(id) + let alias = new Document(id); this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => { - f && alias.Set(KeyStore.Prototype, f) - }) + f && alias.Set(KeyStore.Prototype, f); + }); - return alias + return alias; } MakeDelegate(id?: string): Document { @@ -348,22 +392,39 @@ export class Document extends Field { 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]) + Copy(copyProto?: boolean, id?: string): Field { + let copy = new Document(); + this._proxies.forEach((fieldid, keyid) => { // copy each prototype field + let key = KeyStore.KeyLookup(keyid); + if (key) { + this.GetAsync(key, (field: Opt<Field>) => { + if (key === KeyStore.Prototype && copyProto) { // handle prototype field specially + if (field instanceof Document) { + copy.Set(key, field.Copy(false)); // only copying one level of prototypes for now... + } + } + else + if (field instanceof Document) { // ... TODO bcz: should we copy documents or reference them + copy.Set(key!, field); + } + else if (field) { + copy.Set(key!, field.Copy()); + } + }); } }); + return copy; + } + + ToJson() { + let fields: [string, string][] = []; + this._proxies.forEach((field, key) => + field && fields.push([key, field])); return { type: Types.Document, data: fields, - _id: this.Id - } + id: this.Id + }; } -}
\ No newline at end of file +} diff --git a/src/fields/DocumentReference.ts b/src/fields/DocumentReference.ts index 9d3c209b4..303754177 100644 --- a/src/fields/DocumentReference.ts +++ b/src/fields/DocumentReference.ts @@ -47,11 +47,11 @@ export class DocumentReference extends Field { return ""; } - ToJson(): { type: Types, data: FieldId, _id: string } { + ToJson() { return { type: Types.DocumentReference, data: this.document.Id, - _id: this.Id - } + id: this.Id + }; } }
\ No newline at end of file diff --git a/src/fields/Field.ts b/src/fields/Field.ts index d48509a47..3b3e95c2b 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -1,6 +1,6 @@ import { Utils } from "../Utils"; -import { Types } from "../server/Message"; +import { Types, Transferable } from "../server/Message"; import { computed } from "mobx"; export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T }): Opt<T> { @@ -12,8 +12,8 @@ export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T return undefined; } -export const FieldWaiting: FIELD_WAITING = "<Waiting>"; -export type FIELD_WAITING = "<Waiting>"; +export const FieldWaiting: FIELD_WAITING = null; +export type FIELD_WAITING = null; export type FieldId = string; export type Opt<T> = T | undefined; export type FieldValue<T> = Opt<T> | FIELD_WAITING; @@ -65,5 +65,5 @@ export abstract class Field { abstract Copy(): Field; - abstract ToJson(): { _id: string, type: Types, data: any } + abstract ToJson(): Transferable; }
\ No newline at end of file diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts index 7cbdf7e58..a1d880070 100644 --- a/src/fields/HtmlField.ts +++ b/src/fields/HtmlField.ts @@ -15,11 +15,11 @@ export class HtmlField extends BasicField<string> { return new HtmlField(this.Data); } - ToJson(): { _id: string; type: Types; data: string; } { + ToJson() { return { type: Types.Html, data: this.Data, - _id: this.Id, - } + id: this.Id, + }; } }
\ No newline at end of file diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts index a9ece7d7b..bce20f242 100644 --- a/src/fields/ImageField.ts +++ b/src/fields/ImageField.ts @@ -4,7 +4,7 @@ import { Types } from "../server/Message"; export class ImageField extends BasicField<URL> { 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); + super(data === undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id); } toString(): string { @@ -19,11 +19,11 @@ export class ImageField extends BasicField<URL> { return new ImageField(this.Data); } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.Image, data: this.Data.href, - _id: this.Id - } + id: this.Id + }; } }
\ No newline at end of file diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 2a4ed18e7..2eacd7d0c 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -31,12 +31,12 @@ export class InkField extends BasicField<StrokeMap> { return new InkField(this.Data); } - ToJson(): { _id: string; type: Types; data: any; } { + ToJson() { return { type: Types.Ink, data: this.Data, - _id: this.Id, - } + id: this.Id, + }; } UpdateFromServer(data: any) { diff --git a/src/fields/Key.ts b/src/fields/Key.ts index 00d78d516..57e2dadf0 100644 --- a/src/fields/Key.ts +++ b/src/fields/Key.ts @@ -1,4 +1,4 @@ -import { Field, FieldId } from "./Field" +import { Field, FieldId } from "./Field"; import { Utils } from "../Utils"; import { observable } from "mobx"; import { Types } from "../server/Message"; @@ -16,7 +16,7 @@ export class Key extends Field { this.name = name; if (save) { - Server.UpdateField(this) + Server.UpdateField(this); } } @@ -40,11 +40,11 @@ export class Key extends Field { return name; } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.Key, data: this.name, - _id: this.Id - } + id: this.Id + }; } }
\ No newline at end of file diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts index 1f039e592..16a909eb8 100644 --- a/src/fields/KeyStore.ts +++ b/src/fields/KeyStore.ts @@ -15,6 +15,7 @@ export namespace KeyStore { export const Width = new Key("Width"); export const Height = new Key("Height"); export const ZIndex = new Key("ZIndex"); + export const Zoom = new Key("Zoom"); export const Data = new Key("Data"); export const Annotations = new Key("Annotations"); export const ViewType = new Key("ViewType"); @@ -43,6 +44,22 @@ export namespace KeyStore { export const Cursors = new Key("Cursors"); export const OptionalRightCollection = new Key("OptionalRightCollection"); export const Archives = new Key("Archives"); - export const Updated = new Key("Updated"); export const Workspaces = new Key("Workspaces"); + export const Minimized = new Key("Minimized"); + export const CopyDraggedItems = new Key("CopyDraggedItems"); + + export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight, + Width, Height, ZIndex, Zoom, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys, + LayoutFields, ColumnsKey, SchemaSplitPercentage, Caption, ActiveWorkspace, DocumentText, BrushingDocs, LinkedToDocs, LinkedFromDocs, + LinkDescription, LinkTags, Thumbnail, ThumbnailPage, CurPage, AnnotationOn, NumPages, Ink, Cursors, OptionalRightCollection, + Archives, Workspaces, Minimized, CopyDraggedItems + ]; + export function KeyLookup(keyid: string) { + for (const key of KeyList) { + if (key.Id === keyid) { + return key; + } + } + return undefined; + } } diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts index c4008bd12..e24099126 100644 --- a/src/fields/ListField.ts +++ b/src/fields/ListField.ts @@ -5,12 +5,18 @@ import { Types } from "../server/Message"; import { BasicField } from "./BasicField"; import { Field, FieldId } from "./Field"; import { FieldMap } from "../client/SocketStub"; +import { ScriptField } from "./ScriptField"; export class ListField<T extends Field> extends BasicField<T[]> { - private _proxies: string[] = [] - constructor(data: T[] = [], id?: FieldId, save: boolean = true) { + private _proxies: string[] = []; + private _scriptIds: string[] = []; + private scripts: ScriptField[] = []; + + constructor(data: T[] = [], scripts: ScriptField[] = [], id?: FieldId, save: boolean = true) { super(data, save, id); + this.scripts = scripts; this.updateProxies(); + this._scriptIds = this.scripts.map(script => script.Id); if (save) { Server.UpdateField(this); } @@ -22,43 +28,90 @@ export class ListField<T extends Field> extends BasicField<T[]> { private observeDisposer: Lambda | undefined; private observeList(): void { if (this.observeDisposer) { - this.observeDisposer() + this.observeDisposer(); } this.observeDisposer = observe(this.Data as IObservableArray<T>, (change: IArrayChange<T> | IArraySplice<T>) => { - this.updateProxies() - if (change.type == "splice") { + const target = change.object; + this.updateProxies(); + if (change.type === "splice") { + this.runScripts(change.removed, false); UndoManager.AddEvent({ - undo: () => this.Data.splice(change.index, change.addedCount, ...change.removed), - redo: () => this.Data.splice(change.index, change.removedCount, ...change.added) - }) + undo: () => target.splice(change.index, change.addedCount, ...change.removed), + redo: () => target.splice(change.index, change.removedCount, ...change.added) + }); + this.runScripts(change.added, true); } else { + this.runScripts([change.oldValue], false); UndoManager.AddEvent({ - undo: () => this.Data[change.index] = change.oldValue, - redo: () => this.Data[change.index] = change.newValue - }) + undo: () => target[change.index] = change.oldValue, + redo: () => target[change.index] = change.newValue + }); + this.runScripts([change.newValue], true); } - if (!this._processingServerUpdate) + if (!this._processingServerUpdate) { Server.UpdateField(this); + } + }); + } + + private runScripts(fields: T[], added: boolean) { + for (const script of this.scripts) { + this.runScript(fields, script, added); + } + } + + private runScript(fields: T[], script: ScriptField, added: boolean) { + if (!this._processingServerUpdate) { + for (const field of fields) { + script.script.run({ field, added }); + } + } + } + + addScript(script: ScriptField) { + this.scripts.push(script); + this._scriptIds.push(script.Id); + + this.runScript(this.Data, script, true); + UndoManager.AddEvent({ + undo: () => this.removeScript(script), + redo: () => this.addScript(script), }); + Server.UpdateField(this); + } + + removeScript(script: ScriptField) { + const index = this.scripts.indexOf(script); + if (index === -1) { + return; + } + this.scripts.splice(index, 1); + this._scriptIds.splice(index, 1); + UndoManager.AddEvent({ + undo: () => this.addScript(script), + redo: () => this.removeScript(script), + }); + this.runScript(this.Data, script, false); + Server.UpdateField(this); } protected setData(value: T[]) { + this.runScripts(this.data, false); + this.data = observable(value); this.updateProxies(); this.observeList(); + this.runScripts(this.data, true); } 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 (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. @@ -72,33 +125,42 @@ export class ListField<T extends Field> extends BasicField<T[]> { } init(callback: (field: Field) => any) { - Server.GetFields(this._proxies, action((fields: FieldMap) => { + const fieldsPromise = Server.GetFields(this._proxies).then(action((fields: FieldMap) => { if (!this.arraysEqual(this._proxies, this.data.map(field => field.Id))) { var dataids = this.data.map(d => d.Id); var proxies = this._proxies.map(p => p); var added = this.data.length < this._proxies.length; var deleted = this.data.length > this._proxies.length; - for (let i = 0; i < dataids.length && added; i++) - added = proxies.indexOf(dataids[i]) != -1; - for (let i = 0; i < this._proxies.length && deleted; i++) - deleted = dataids.indexOf(proxies[i]) != -1; + for (let i = 0; i < dataids.length && added; i++) { + added = proxies.indexOf(dataids[i]) !== -1; + } + for (let i = 0; i < this._proxies.length && deleted; i++) { + deleted = dataids.indexOf(proxies[i]) !== -1; + } this._processingServerUpdate = true; for (let i = 0; i < proxies.length && added; i++) { - if (dataids.indexOf(proxies[i]) === -1) + if (dataids.indexOf(proxies[i]) === -1) { this.Data.splice(i, 0, fields[proxies[i]] as T); + } } for (let i = dataids.length - 1; i >= 0 && deleted; i--) { - if (proxies.indexOf(dataids[i]) === -1) + if (proxies.indexOf(dataids[i]) === -1) { this.Data.splice(i, 1); + } } if (!added && !deleted) {// otherwise, just rebuild the whole list this.setData(proxies.map(id => fields[id] as T)); } this._processingServerUpdate = false; } - callback(this); - })) + })); + + const scriptsPromise = Server.GetFields(this._scriptIds).then((fields: FieldMap) => { + this.scripts = this._scriptIds.map(id => fields[id] as ScriptField); + }); + + Promise.all([fieldsPromise, scriptsPromise]).then(() => callback(this)); } ToScriptString(): string { @@ -109,17 +171,26 @@ export class ListField<T extends Field> extends BasicField<T[]> { return new ListField<T>(this.Data); } - ToJson(): { type: Types, data: string[], _id: string } { + + UpdateFromServer(data: { fields: string[], scripts: string[] }) { + this._proxies = data.fields; + this._scriptIds = data.scripts; + } + ToJson() { return { type: Types.List, - data: this._proxies || [], - _id: this.Id - } + data: { + fields: this._proxies, + scripts: this._scriptIds, + }, + id: this.Id + }; } - static FromJson(id: string, ids: string[]): ListField<Field> { - let list = new ListField([], id, false); - list._proxies = ids; - return list + static FromJson(id: string, data: { fields: string[], scripts: string[] }): ListField<Field> { + let list = new ListField([], [], id, false); + list._proxies = data.fields; + list._scriptIds = data.scripts; + return list; } }
\ No newline at end of file diff --git a/src/fields/NumberField.ts b/src/fields/NumberField.ts index 47dfc74cb..7eea360c0 100644 --- a/src/fields/NumberField.ts +++ b/src/fields/NumberField.ts @@ -1,4 +1,4 @@ -import { BasicField } from "./BasicField" +import { BasicField } from "./BasicField"; import { Types } from "../server/Message"; import { FieldId } from "./Field"; @@ -8,18 +8,18 @@ export class NumberField extends BasicField<number> { } ToScriptString(): string { - return "new NumberField(this.Data)"; + return `new NumberField(${this.Data})`; } Copy() { return new NumberField(this.Data); } - ToJson(): { _id: string, type: Types, data: number } { + ToJson() { return { - _id: this.Id, + id: this.Id, type: Types.Number, data: this.Data - } + }; } }
\ No newline at end of file diff --git a/src/fields/PDFField.ts b/src/fields/PDFField.ts index b6625387e..718a1a4c0 100644 --- a/src/fields/PDFField.ts +++ b/src/fields/PDFField.ts @@ -1,13 +1,13 @@ import { BasicField } from "./BasicField"; import { Field, FieldId } from "./Field"; -import { observable } from "mobx" +import { observable } from "mobx"; import { Types } from "../server/Message"; export class PDFField extends BasicField<URL> { 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); + super(data === undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id); } toString(): string { @@ -22,12 +22,12 @@ export class PDFField extends BasicField<URL> { return `new PDFField("${this.Data}")`; } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.PDF, data: this.Data.href, - _id: this.Id - } + id: this.Id + }; } @observable diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index 5efb43314..f53f48ca6 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -15,12 +15,12 @@ export class RichTextField extends BasicField<string> { return new RichTextField(this.Data); } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.RichText, data: this.Data, - _id: this.Id - } + id: this.Id + }; } }
\ No newline at end of file diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts new file mode 100644 index 000000000..7f87be45d --- /dev/null +++ b/src/fields/ScriptField.ts @@ -0,0 +1,101 @@ +import { Field, FieldId } from "./Field"; +import { Types } from "../server/Message"; +import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting"; +import { Server } from "../client/Server"; +import { Without } from "../Utils"; + +export interface SerializableOptions extends Without<ScriptOptions, "capturedVariables"> { + capturedIds: { [id: string]: string }; +} + +export interface ScriptData { + script: string; + options: SerializableOptions; +} + +export class ScriptField extends Field { + private _script?: CompiledScript; + get script(): CompiledScript { + return this._script!; + } + private options?: ScriptData; + + constructor(script?: CompiledScript, id?: FieldId, save: boolean = true) { + super(id); + + this._script = script; + + if (save) { + Server.UpdateField(this); + } + } + + ToScriptString() { + return "new ScriptField(...)"; + } + + GetValue() { + return this.script; + } + + TrySetValue(): boolean { + throw new Error("Script fields currently can't be modified"); + } + + UpdateFromServer() { + throw new Error("Script fields currently can't be updated"); + } + + static FromJson(id: string, data: ScriptData): ScriptField { + let field = new ScriptField(undefined, id, false); + field.options = data; + return field; + } + + init(callback: (res: Field) => any) { + const options = this.options!; + const keys = Object.keys(options.options.capturedIds); + Server.GetFields(keys).then(fields => { + let captured: { [name: string]: Field } = {}; + keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]); + const opts: ScriptOptions = { + addReturn: options.options.addReturn, + params: options.options.params, + requiredType: options.options.requiredType, + capturedVariables: captured + }; + const script = CompileScript(options.script, opts); + if (!script.compiled) { + throw new Error("Can't compile script"); + } + this._script = script; + callback(this); + }); + } + + ToJson() { + const { options, originalScript } = this.script; + let capturedIds: { [id: string]: string } = {}; + for (const capt in options.capturedVariables) { + capturedIds[options.capturedVariables[capt].Id] = capt; + } + const opts: SerializableOptions = { + ...options, + capturedIds + }; + delete (opts as any).capturedVariables; + return { + id: this.Id, + type: Types.Script, + data: { + script: originalScript, + options: opts, + }, + }; + } + + Copy(): Field { + //Script fields are currently immutable, so we can fake copy them + return this; + } +}
\ No newline at end of file diff --git a/src/fields/TextField.ts b/src/fields/TextField.ts index 71d8ea310..ddedec9b1 100644 --- a/src/fields/TextField.ts +++ b/src/fields/TextField.ts @@ -1,4 +1,4 @@ -import { BasicField } from "./BasicField" +import { BasicField } from "./BasicField"; import { FieldId } from "./Field"; import { Types } from "../server/Message"; @@ -15,11 +15,11 @@ export class TextField extends BasicField<string> { return new TextField(this.Data); } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.Text, data: this.Data, - _id: this.Id - } + id: this.Id + }; } }
\ No newline at end of file diff --git a/src/fields/TupleField.ts b/src/fields/TupleField.ts index e2162c751..347f1fa05 100644 --- a/src/fields/TupleField.ts +++ b/src/fields/TupleField.ts @@ -21,7 +21,7 @@ export class TupleField<T, U> extends BasicField<[T, U]> { UndoManager.AddEvent({ undo: () => this.Data[change.index] = change.oldValue, redo: () => this.Data[change.index] = change.newValue - }) + }); Server.UpdateField(this); } else { throw new Error("Why are you messing with the length of a tuple, huh?"); @@ -31,7 +31,7 @@ export class TupleField<T, U> extends BasicField<[T, U]> { protected setData(value: [T, U]) { if (this.observeDisposer) { - this.observeDisposer() + this.observeDisposer(); } this.data = observable(value) as (T | U)[] as [T, U]; this.observeTuple(); @@ -49,11 +49,11 @@ export class TupleField<T, U> extends BasicField<[T, U]> { return new TupleField<T, U>(this.Data); } - ToJson(): { type: Types, data: [T, U], _id: string } { + ToJson() { return { type: Types.Tuple, data: this.Data, - _id: this.Id - } + id: this.Id + }; } }
\ No newline at end of file diff --git a/src/fields/VideoField.ts b/src/fields/VideoField.ts index 626e4ec83..838b811b1 100644 --- a/src/fields/VideoField.ts +++ b/src/fields/VideoField.ts @@ -4,7 +4,7 @@ import { Types } from "../server/Message"; export class VideoField extends BasicField<URL> { constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) { - super(data == undefined ? new URL("http://techslides.com/demos/sample-videos/small.mp4") : data, save, id); + super(data === undefined ? new URL("http://techslides.com/demos/sample-videos/small.mp4") : data, save, id); } toString(): string { @@ -19,12 +19,12 @@ export class VideoField extends BasicField<URL> { return new VideoField(this.Data); } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.Video, data: this.Data.href, - _id: this.Id - } + id: this.Id + }; } }
\ No newline at end of file diff --git a/src/fields/WebField.ts b/src/fields/WebField.ts index 6c4de5000..8b276a552 100644 --- a/src/fields/WebField.ts +++ b/src/fields/WebField.ts @@ -4,7 +4,7 @@ import { Types } from "../server/Message"; export class WebField extends BasicField<URL> { constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) { - super(data == undefined ? new URL("https://crossorigin.me/" + "https://cs.brown.edu/") : data, save, id); + super(data === undefined ? new URL("https://crossorigin.me/" + "https://cs.brown.edu/") : data, save, id); } toString(): string { @@ -19,12 +19,12 @@ export class WebField extends BasicField<URL> { return new WebField(this.Data); } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.Web, data: this.Data.href, - _id: this.Id - } + id: this.Id + }; } }
\ No newline at end of file |
