diff options
Diffstat (limited to 'src/fields')
| -rw-r--r-- | src/fields/Document.ts | 69 | ||||
| -rw-r--r-- | src/fields/Field.ts | 3 | ||||
| -rw-r--r-- | src/fields/ImageField.ts | 1 | ||||
| -rw-r--r-- | src/fields/InkField.ts | 53 | ||||
| -rw-r--r-- | src/fields/KVPField | 0 | ||||
| -rw-r--r-- | src/fields/KVPField.ts | 30 | ||||
| -rw-r--r-- | src/fields/Key.ts | 1 | ||||
| -rw-r--r-- | src/fields/KeyStore.ts | 5 | ||||
| -rw-r--r-- | src/fields/ListField.ts | 21 | ||||
| -rw-r--r-- | src/fields/PDFField.ts | 36 | ||||
| -rw-r--r-- | src/fields/WebField.ts | 30 |
11 files changed, 243 insertions, 6 deletions
diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 1b27c4a13..d5ff32c6f 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -8,6 +8,7 @@ import { ListField } from "./ListField"; import { Server } from "../client/Server"; import { Types } from "../server/Message"; import { UndoManager } from "../client/util/UndoManager"; +import { HtmlField } from "./HtmlField"; export class Document extends Field { public fields: ObservableMap<string, { key: Key, field: Field }> = new ObservableMap(); @@ -17,8 +18,6 @@ export class Document extends Field { super(id) if (save) { - var title = (this._proxies.has(KeyStore.Title.Id) ? "???" : this.Title) + "(" + this.Id + ")"; - console.log("Save " + title); Server.UpdateField(this) } } @@ -31,7 +30,7 @@ export class Document extends Field { } public Width = () => { return this.GetNumber(KeyStore.Width, 0) } - public Height = () => { return this.GetNumber(KeyStore.Height, 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 @@ -39,6 +38,22 @@ export class Document extends Field { return this.GetText(KeyStore.Title, "<untitled>"); } + /** + * 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 + * 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 + * might have and only search for the value on this immediate document. + * 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. + */ Get(key: Key, ignoreProto: boolean = false): FieldValue<Field> { let field: FieldValue<Field>; if (ignoreProto) { @@ -94,7 +109,17 @@ export class Document extends Field { return field; } + /** + * Tries to get the field associated with the given key, and if there is an + * associated field, calls the given callback with that field. + * @param key - The key of the value to get + * @param callback - A function that will be called with the associated field, if it exists, + * once it is fetched from the server (this may be immediately if the field has already been fetched). + * Note: The callback will not be called if there is no associated field. + * @returns `true` if the field exists on the document and `callback` will be called, and `false` otherwise + */ GetAsync(key: Key, callback: (field: Field) => void): boolean { + //TODO: This should probably check if this.fields contains the key before calling Server.GetDocumentField //This currently doesn't deal with prototypes if (this._proxies.has(key.Id)) { Server.GetDocumentField(this, key, callback); @@ -103,6 +128,38 @@ export class Document extends Field { return false; } + /** + * Same as {@link Document#GetAsync}, except a field of the given type + * will be created if there is no field associated with the given key, + * 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 { + //This currently doesn't deal with prototypes + if (this._proxies.has(key.Id)) { + Server.GetDocumentField(this, key, (field) => { + if (field && field instanceof ctor) { + callback(field); + } else { + let newField = new ctor(); + this.Set(key, newField); + callback(newField); + } + }); + } else { + let newField = new ctor(); + this.Set(key, newField); + callback(newField); + } + } + + /** + * Same as {@link Document#Get}, except that it will additionally + * check if the field is of the given type. + * @param ctor - Constructor of the field type to get. E.g., `TextField`, `ImageField`, etc. + * @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> { var getfield = this.Get(key, ignoreProto); if (getfield != FieldWaiting) { @@ -127,6 +184,10 @@ export class Document extends Field { return vval; } + GetHtml(key: Key, defaultVal: string): string { + return this.GetData(key, HtmlField, defaultVal); + } + GetNumber(key: Key, defaultVal: number): number { return this.GetData(key, NumberField, defaultVal); } @@ -215,6 +276,8 @@ export class Document extends Field { this.GetAsync(KeyStore.Prototype, (f: Field) => { alias.Set(KeyStore.Prototype, f) }) + alias.SetNumber(KeyStore.Width, 300) + alias.SetNumber(KeyStore.Height, 300) return alias } diff --git a/src/fields/Field.ts b/src/fields/Field.ts index c7e0232af..d48509a47 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -1,6 +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) { @@ -25,6 +26,8 @@ export abstract class Field { } private id: FieldId; + + @computed get Id(): FieldId { return this.id; } diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts index b2226d55a..be8d73e68 100644 --- a/src/fields/ImageField.ts +++ b/src/fields/ImageField.ts @@ -1,7 +1,6 @@ import { BasicField } from "./BasicField"; 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, id?: FieldId, save: boolean = true) { diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts new file mode 100644 index 000000000..2a4ed18e7 --- /dev/null +++ b/src/fields/InkField.ts @@ -0,0 +1,53 @@ +import { BasicField } from "./BasicField"; +import { Types } from "../server/Message"; +import { FieldId } from "./Field"; +import { observable, ObservableMap } from "mobx"; + +export enum InkTool { + None, + Pen, + Highlighter, + Eraser +} +export interface StrokeData { + pathData: Array<{ x: number, y: number }>; + color: string; + width: string; + tool: InkTool; + page: number; +} +export type StrokeMap = Map<string, StrokeData>; + +export class InkField extends BasicField<StrokeMap> { + constructor(data: StrokeMap = new Map, id?: FieldId, save: boolean = true) { + super(data, save, id); + } + + ToScriptString(): string { + return `new InkField("${this.Data}")`; + } + + Copy() { + return new InkField(this.Data); + } + + ToJson(): { _id: string; type: Types; data: any; } { + return { + type: Types.Ink, + data: this.Data, + _id: this.Id, + } + } + + UpdateFromServer(data: any) { + this.data = new ObservableMap(data); + } + + static FromJson(id: string, data: any): InkField { + let map: StrokeMap = new Map<string, StrokeData>(); + Object.keys(data).forEach(key => { + map.set(key, data[key]); + }); + return new InkField(map, id, false); + } +}
\ No newline at end of file diff --git a/src/fields/KVPField b/src/fields/KVPField new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/fields/KVPField diff --git a/src/fields/KVPField.ts b/src/fields/KVPField.ts new file mode 100644 index 000000000..a7ecc0768 --- /dev/null +++ b/src/fields/KVPField.ts @@ -0,0 +1,30 @@ +import { BasicField } from "./BasicField" +import { FieldId } from "./Field"; +import { Types } from "../server/Message"; +import { Document } from "./Document" + +export class KVPField extends BasicField<Document> { + constructor(data: Document | undefined = undefined, id?: FieldId, save: boolean = true) { + super(data == undefined ? new Document() : data, save, id); + } + + toString(): string { + return this.Data.Title; + } + + ToScriptString(): string { + return `new KVPField("${this.Data}")`; + } + + Copy() { + return new KVPField(this.Data); + } + + ToJson(): { type: Types, data: Document, _id: string } { + return { + type: Types.Text, + 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 c16a00878..00d78d516 100644 --- a/src/fields/Key.ts +++ b/src/fields/Key.ts @@ -2,7 +2,6 @@ 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 { diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts index a3b39735d..f67257093 100644 --- a/src/fields/KeyStore.ts +++ b/src/fields/KeyStore.ts @@ -4,6 +4,7 @@ export namespace KeyStore { export const Prototype = new Key("Prototype"); export const X = new Key("X"); export const Y = new Key("Y"); + export const Page = new Key("Page"); export const Title = new Key("Title"); export const Author = new Key("Author"); export const PanX = new Key("PanX"); @@ -26,4 +27,8 @@ export namespace KeyStore { export const Caption = new Key("Caption"); export const ActiveFrame = new Key("ActiveFrame"); export const DocumentText = new Key("DocumentText"); + export const Thumbnail = new Key("Thumbnail"); + export const CurPage = new Key("CurPage"); + export const NumPages = new Key("NumPages"); + export const Ink = new Key("Ink"); } diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts index 700600804..ce32da0a6 100644 --- a/src/fields/ListField.ts +++ b/src/fields/ListField.ts @@ -40,6 +40,7 @@ export class ListField<T extends Field> extends BasicField<T[]> { this.observeDisposer() } this.data = observable(value); + this.updateProxies(); this.observeList(); } @@ -69,7 +70,25 @@ export class ListField<T extends Field> extends BasicField<T[]> { 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) + var dataids = this.data.map(d => d.Id); + var added = this.data.length == this._proxies.length - 1; + var deleted = this.data.length > this._proxies.length; + for (let i = 0; i < dataids.length && added; i++) + added = this._proxies.indexOf(dataids[i]) != -1; + for (let i = 0; i < this._proxies.length && deleted; i++) + deleted = dataids.indexOf(this._proxies[i]) != -1; + if (added) { // if only 1 items was added + for (let i = 0; i < this._proxies.length; i++) + if (dataids.indexOf(this._proxies[i]) === -1) + this.Data.splice(i, 0, fields[this._proxies[i]] as T); + } else if (deleted) { // if only items were deleted + for (let i = this.data.length - 1; i >= 0; i--) { + if (this._proxies.indexOf(this.data[i].Id) === -1) { + this.Data.splice(i, 1); + } + } + } else // otherwise, just rebuild the whole list + this.data = this._proxies.map(id => fields[id] as T) observe(this.Data, () => { this.updateProxies() Server.UpdateField(this); diff --git a/src/fields/PDFField.ts b/src/fields/PDFField.ts new file mode 100644 index 000000000..f3a009001 --- /dev/null +++ b/src/fields/PDFField.ts @@ -0,0 +1,36 @@ +import { BasicField } from "./BasicField"; +import { Field, FieldId } from "./Field"; +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); + } + + toString(): string { + return this.Data.href; + } + + Copy(): Field { + return new PDFField(this.Data); + } + + ToScriptString(): string { + return `new PDFField("${this.Data}")`; + } + + ToJson(): { type: Types, data: URL, _id: string } { + return { + type: Types.PDF, + data: this.Data, + _id: this.Id + } + } + + @observable + Page: Number = 1; + +}
\ No newline at end of file diff --git a/src/fields/WebField.ts b/src/fields/WebField.ts new file mode 100644 index 000000000..8f945d686 --- /dev/null +++ b/src/fields/WebField.ts @@ -0,0 +1,30 @@ +import { BasicField } from "./BasicField"; +import { Field, FieldId } from "./Field"; +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); + } + + toString(): string { + return this.Data.href; + } + + ToScriptString(): string { + return `new WebField("${this.Data}")`; + } + + Copy(): Field { + return new WebField(this.Data); + } + + ToJson(): { type: Types, data: URL, _id: string } { + return { + type: Types.Web, + data: this.Data, + _id: this.Id + } + } + +}
\ No newline at end of file |
