diff options
author | Tyler Schicke <tyler_schicke@brown.edu> | 2019-02-14 05:43:09 -0500 |
---|---|---|
committer | Tyler Schicke <tyler_schicke@brown.edu> | 2019-02-14 05:43:09 -0500 |
commit | 4bcc62fd164c5ee6c4fc50077753ba7d969478e3 (patch) | |
tree | d3a0a7ffc657ef890e640c52dd1e906bf19701c0 | |
parent | 4eb4ef6e073652661dcfa30597f63e93058fb876 (diff) |
Got almost all of collaboration and server communication working
-rw-r--r-- | src/client/Server.ts | 53 | ||||
-rw-r--r-- | src/client/SocketStub.ts | 23 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 41 | ||||
-rw-r--r-- | src/client/views/Main.tsx | 138 | ||||
-rw-r--r-- | src/client/views/collections/CollectionFreeFormView.tsx | 21 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 4 | ||||
-rw-r--r-- | src/fields/BasicField.ts | 12 | ||||
-rw-r--r-- | src/fields/Document.ts | 37 | ||||
-rw-r--r-- | src/fields/DocumentReference.ts | 4 | ||||
-rw-r--r-- | src/fields/Field.ts | 14 | ||||
-rw-r--r-- | src/fields/ImageField.ts | 4 | ||||
-rw-r--r-- | src/fields/Key.ts | 10 | ||||
-rw-r--r-- | src/fields/ListField.ts | 65 | ||||
-rw-r--r-- | src/fields/NumberField.ts | 4 | ||||
-rw-r--r-- | src/fields/RichTextField.ts | 4 | ||||
-rw-r--r-- | src/fields/TextField.ts | 4 | ||||
-rw-r--r-- | src/server/Message.ts | 1 | ||||
-rw-r--r-- | src/server/ServerUtil.ts | 22 | ||||
-rw-r--r-- | src/server/database.ts | 25 | ||||
-rw-r--r-- | src/server/index.ts | 27 |
20 files changed, 341 insertions, 172 deletions
diff --git a/src/client/Server.ts b/src/client/Server.ts index fab51ca9c..e9edb7b9c 100644 --- a/src/client/Server.ts +++ b/src/client/Server.ts @@ -1,34 +1,57 @@ -import { Field, FieldWaiting, FIELD_ID, FIELD_WAITING, FieldValue } from "../fields/Field" +import { Field, FieldWaiting, FIELD_ID, FIELD_WAITING, FieldValue, Opt } from "../fields/Field" import { Key, KeyStore } from "../fields/Key" import { ObservableMap, action } from "mobx"; import { Document } from "../fields/Document" import { SocketStub } from "./SocketStub"; import * as OpenSocket from 'socket.io-client'; import { Utils } from "./../Utils"; -import { MessageStore } from "./../server/Message"; +import { MessageStore, Types } from "./../server/Message"; export class Server { private static ClientFieldsCached: ObservableMap<FIELD_ID, Field | FIELD_WAITING> = new ObservableMap(); - static Socket: SocketIOClient.Socket = OpenSocket("http://localhost:1234") + static Socket: SocketIOClient.Socket = OpenSocket("http://localhost:1234"); static GUID: string = Utils.GenerateGuid() + private static Cache: { [id: string]: Field } = {}; + + @action + static updateField(field: { _id: string, data: any, type: Types }) { + if (field._id in Server.Cache) { + const f = Server.Cache[field._id]; + f.UpdateFromServer(field.data); + f.init(() => { }); + } + } + // Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached). // Call this is from within a reaction and test whether the return value is FieldWaiting. // 'hackTimeout' is here temporarily for simplicity when debugging things. - public static GetField(fieldid: FIELD_ID, callback: (field: Field) => void = (f) => { }, hackTimeout: number = -1) { + public static GetField(fieldid: FIELD_ID, callback: (field: Opt<Field>) => void = (f) => { }, hackTimeout: number = -1) { if (!this.ClientFieldsCached.get(fieldid)) { - this.ClientFieldsCached.set(fieldid, FieldWaiting); + // this.ClientFieldsCached.set(fieldid, FieldWaiting); //simulating a server call with a registered callback action - SocketStub.SEND_FIELD_REQUEST(fieldid, - action((field: Field) => { - callback(Server.cacheField(field)) - })); + SocketStub.SEND_FIELD_REQUEST(fieldid, (field) => { + if (field) { + this.Cache[field.Id] = field; + } + callback(field) + }); } else if (this.ClientFieldsCached.get(fieldid) != FieldWaiting) { callback(this.ClientFieldsCached.get(fieldid) as Field); } return this.ClientFieldsCached.get(fieldid); } + public static GetFields(fieldIds: FIELD_ID[], callback: (fields: { [id: string]: Field }) => any) { + SocketStub.SEND_FIELDS_REQUEST(fieldIds, (fields) => { + for (let key in fields) { + let field = fields[key]; + this.Cache[field.Id] = field; + } + callback(fields) + }); + } + static times = 0; // hack for testing public static GetDocumentField(doc: Document, key: Key) { // let keyId: string = element[0] @@ -46,8 +69,10 @@ export class Server { // }) return this.GetField(doc._proxies.get(key.Id), - action((fieldfromserver: Field) => { - doc.fields.set(key, fieldfromserver); + action((fieldfromserver: Opt<Field>) => { + if (fieldfromserver) { + doc.fields.set(key.Id, { key, field: fieldfromserver }); + } })); } @@ -65,9 +90,10 @@ export class Server { public static UpdateField(field: Field) { if (this.lock) { - setTimeout(this.UpdateField, 1000, field) + // setTimeout(this.UpdateField, 1000, field) } this.lock = true + // console.log("updating field " + field.Id) SocketStub.SEND_SET_FIELD(field, (args: any) => { if (this.lock) { this.lock = false @@ -91,4 +117,5 @@ export class Server { } } -Server.Socket.on(MessageStore.Foo.Message, Server.connected);
\ No newline at end of file +Server.Socket.on(MessageStore.Foo.Message, Server.connected); +Server.Socket.on(MessageStore.SetField.Message, Server.updateField);
\ No newline at end of file diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts index 136c69668..7545a166c 100644 --- a/src/client/SocketStub.ts +++ b/src/client/SocketStub.ts @@ -1,10 +1,11 @@ -import { Field, FIELD_ID } from "../fields/Field" +import { Field, FIELD_ID, Opt } from "../fields/Field" import { Key, KeyStore } from "../fields/Key" import { ObservableMap, action } from "mobx"; import { Document } from "../fields/Document" import { MessageStore, SetFieldArgs, GetFieldArgs, DocumentTransfer, Types } from "../server/Message"; import { Utils } from "../Utils"; import { Server } from "./Server"; +import { ServerUtils } from "../server/ServerUtil"; export class SocketStub { @@ -32,12 +33,28 @@ export class SocketStub { Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(document.ToJson())) } - public static SEND_FIELD_REQUEST(fieldid: FIELD_ID, callback: (field: Field) => void) { + public static SEND_FIELD_REQUEST(fieldid: FIELD_ID, callback: (field: Opt<Field>) => void) { if (fieldid) { - Utils.EmitCallback(Server.Socket, MessageStore.GetField, fieldid, (field: Field) => callback(field)) + Utils.EmitCallback(Server.Socket, MessageStore.GetField, fieldid, (field: any) => { + if (field) { + ServerUtils.FromJson(field).init(callback); + } else { + callback(undefined); + } + }) } } + public static SEND_FIELDS_REQUEST(fieldIds: FIELD_ID[], callback: (fields: { [key: string]: Field }) => any) { + Utils.EmitCallback(Server.Socket, MessageStore.GetFields, fieldIds, (fields: any[]) => { + let fieldMap: any = {}; + for (let field of fields) { + fieldMap[field._id] = ServerUtils.FromJson(field); + } + callback(fieldMap) + }); + } + public static SEND_ADD_DOCUMENT_FIELD(doc: Document, key: Key, value: Field) { // Send a serialized version of the field to the server along with the diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f362af392..210e63cd3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -21,6 +21,17 @@ interface DocumentOptions { } export namespace Documents { + export function initProtos(callback: () => void) { + Server.GetFields([collectionProtoId, textProtoId, imageProtoId, schemaProtoId, dockProtoId], (fields) => { + collectionProto = fields[collectionProtoId] as Document; + imageProto = fields[imageProtoId] as Document; + textProto = fields[textProtoId] as Document; + dockProto = fields[dockProtoId] as Document; + schemaProto = fields[schemaProtoId] as Document; + callback() + }); + } + function setupOptions(doc: Document, options: DocumentOptions): void { if (options.x) { doc.SetData(KeyStore.X, options.x, NumberField); @@ -43,9 +54,10 @@ export namespace Documents { } let textProto: Document; + const textProtoId = "textProto"; function GetTextPrototype(): Document { if (!textProto) { - textProto = new Document(); + textProto = new Document(textProtoId); textProto.Set(KeyStore.X, new NumberField(0)); textProto.Set(KeyStore.Y, new NumberField(0)); textProto.Set(KeyStore.Width, new NumberField(300)); @@ -64,9 +76,10 @@ export namespace Documents { } let schemaProto: Document; + const schemaProtoId = "schemaProto"; function GetSchemaPrototype(): Document { if (!schemaProto) { - schemaProto = new Document(); + schemaProto = new Document(schemaProtoId); schemaProto.Set(KeyStore.X, new NumberField(0)); schemaProto.Set(KeyStore.Y, new NumberField(0)); schemaProto.Set(KeyStore.Width, new NumberField(300)); @@ -86,6 +99,7 @@ export namespace Documents { let dockProto: Document; + const dockProtoId = "dockProto"; function GetDockPrototype(): Document { if (!dockProto) { dockProto = new Document(); @@ -107,11 +121,11 @@ export namespace Documents { } - let imageProtoId: FIELD_ID; + let imageProto: Document; + const imageProtoId = "imageProto"; function GetImagePrototype(): Document { - if (imageProtoId === undefined) { - let imageProto = new Document(); - imageProtoId = imageProto.Id; + if (!imageProto) { + imageProto = new Document(imageProtoId); imageProto.Set(KeyStore.Title, new TextField("IMAGE PROTO")); imageProto.Set(KeyStore.X, new NumberField(0)); imageProto.Set(KeyStore.Y, new NumberField(0)); @@ -120,26 +134,23 @@ export namespace Documents { imageProto.Set(KeyStore.Layout, new TextField(ImageBox.LayoutString())); // imageProto.SetField(KeyStore.Layout, new TextField('<div style={"background-image: " + {Data}} />')); imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); - // Server.AddDocument(imageProto); return imageProto; } - return new Document(); - // return Server.GetField(imageProtoId) as Document; + return imageProto; } export function ImageDocument(url: string, options: DocumentOptions = {}): Document { let doc = GetImagePrototype().MakeDelegate(); setupOptions(doc, options); doc.Set(KeyStore.Data, new ImageField(new URL(url))); - // Server.AddDocument(doc); - // var sdoc = Server.GetField(doc.Id) as Document; return doc; } let collectionProto: Document; - function GetCollectionPrototype(isMainDoc: boolean): Document { + const collectionProtoId = "collectionProto"; + function GetCollectionPrototype(): Document { if (!collectionProto) { - collectionProto = new Document(isMainDoc ? "dash" : undefined); + collectionProto = new Document(collectionProtoId); collectionProto.Set(KeyStore.X, new NumberField(0)); collectionProto.Set(KeyStore.Y, new NumberField(0)); collectionProto.Set(KeyStore.Scale, new NumberField(1)); @@ -153,8 +164,8 @@ export namespace Documents { return collectionProto; } - export function CollectionDocument(documents: Array<Document>, options: DocumentOptions = {}, isMainDoc: boolean = false): Document { - let doc = GetCollectionPrototype(isMainDoc).MakeDelegate(); + export function CollectionDocument(documents: Array<Document>, options: DocumentOptions = {}, id?: string): Document { + let doc = GetCollectionPrototype().MakeDelegate(id); setupOptions(doc, options); doc.Set(KeyStore.Data, new ListField(documents)); return doc; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 376876ebb..14e60409e 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -60,77 +60,79 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { // schemaDocs.push(doc2); // const doc7 = Documents.SchemaDocument(schemaDocs) -Utils.EmitCallback(Server.Socket, MessageStore.GetField, "dash", (res: any) => { - console.log("HELLO WORLD") - console.log("RESPONSE: " + res) - let mainContainer: Document = new Document(); - if (res) { - let obj = ServerUtils.FromJson(res) as Document - mainContainer = obj - console.log(mainContainer) - } - else { - const docset: Document[] = []; - let doc4 = Documents.CollectionDocument(docset, { - x: 0, y: 400, title: "mini collection" - }, true); - mainContainer = doc4; - let args = new DocumentTransfer(mainContainer.ToJson()) - Utils.Emit(Server.Socket, MessageStore.AddDocument, args) - } +const mainDocId = "mainDoc"; +Documents.initProtos(() => { + Utils.EmitCallback(Server.Socket, MessageStore.GetField, mainDocId, (res: any) => { + console.log("HELLO WORLD") + console.log("RESPONSE: " + res) + let mainContainer: Document; + if (res) { + let obj = ServerUtils.FromJson(res) as Document + mainContainer = obj + } + else { + const docset: Document[] = []; + let doc4 = Documents.CollectionDocument(docset, { + x: 0, y: 400, title: "mini collection" + }, mainDocId); + mainContainer = doc4; + let args = new DocumentTransfer(mainContainer.ToJson()) + Utils.Emit(Server.Socket, MessageStore.AddDocument, args) + } - let addImageNode = action(() => { - mainContainer.GetList<Document>(KeyStore.Data, []).push(Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { - x: 0, y: 300, width: 200, height: 200, title: "added note" - })); - }) - let addTextNode = action(() => { - mainContainer.GetList<Document>(KeyStore.Data, []).push(Documents.TextDocument({ - x: 0, y: 300, width: 200, height: 200, title: "added note" - })); - }) - let addColNode = action(() => { - mainContainer.GetList<Document>(KeyStore.Data, []).push(Documents.CollectionDocument([], { - x: 0, y: 300, width: 200, height: 200, title: "added note" - })); - }) + let addImageNode = action(() => { + mainContainer.GetList<Document>(KeyStore.Data, []).push(Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { + x: 0, y: 300, width: 200, height: 200, title: "added note" + })); + }) + let addTextNode = action(() => { + mainContainer.GetList<Document>(KeyStore.Data, []).push(Documents.TextDocument({ + x: 0, y: 300, width: 200, height: 200, title: "added note" + })); + }) + let addColNode = action(() => { + mainContainer.GetList<Document>(KeyStore.Data, []).push(Documents.CollectionDocument([], { + x: 0, y: 300, width: 200, height: 200, title: "added note" + })); + }) - let clearDatabase = action(() => { - Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}); - }) + let clearDatabase = action(() => { + Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}); + }) - ReactDOM.render(( - <div style={{ position: "absolute", width: "100%", height: "100%" }}> - <DocumentView Document={mainContainer} ContainingCollectionView={undefined} DocumentView={undefined} /> - <DocumentDecorations /> - <ContextMenu /> - <button style={{ - position: 'absolute', - bottom: '0px', - left: '0px', - width: '150px' - }} onClick={addImageNode}>Add Image</button> - <button style={{ - position: 'absolute', - bottom: '25px', - left: '0px', - width: '150px' - }} onClick={addTextNode}>Add Text</button> - <button style={{ - position: 'absolute', - bottom: '50px', - left: '0px', - width: '150px' - }} onClick={addColNode}>Add Collection</button> - <button style={{ - position: 'absolute', - bottom: '75px', - left: '0px', - width: '150px' - }} onClick={clearDatabase}>Clear Database</button> - </div>), - document.getElementById('root')); -}) + ReactDOM.render(( + <div style={{ position: "absolute", width: "100%", height: "100%" }}> + <DocumentView Document={mainContainer} ContainingCollectionView={undefined} DocumentView={undefined} /> + <DocumentDecorations /> + <ContextMenu /> + <button style={{ + position: 'absolute', + bottom: '0px', + left: '0px', + width: '150px' + }} onClick={addImageNode}>Add Image</button> + <button style={{ + position: 'absolute', + bottom: '25px', + left: '0px', + width: '150px' + }} onClick={addTextNode}>Add Text</button> + <button style={{ + position: 'absolute', + bottom: '50px', + left: '0px', + width: '150px' + }} onClick={addColNode}>Add Collection</button> + <button style={{ + position: 'absolute', + bottom: '75px', + left: '0px', + width: '150px' + }} onClick={clearDatabase}>Clear Database</button> + </div>), + document.getElementById('root')); + }) +}); // let doc5 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { // x: 650, y: 500, width: 600, height: 600, title: "cat 2" // }); diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 9cf29d000..c7ead2f2f 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -17,7 +17,6 @@ import { FieldWaiting } from "../../../fields/Field"; @observer export class CollectionFreeFormView extends CollectionViewBase { public static LayoutString() { return CollectionViewBase.LayoutString("CollectionFreeFormView"); } - private _containerRef = React.createRef<HTMLDivElement>(); private _canvasRef = React.createRef<HTMLDivElement>(); private _nodeContainerRef = React.createRef<HTMLDivElement>(); private _lastX: number = 0; @@ -51,9 +50,13 @@ export class CollectionFreeFormView extends CollectionViewBase { e.stopPropagation(); } - componentDidMount() { - if (this._containerRef.current) { - DragManager.MakeDropTarget(this._containerRef.current, { + private dropDisposer?: DragManager.DragDropDisposer; + createDropTarget = (ele: HTMLDivElement) => { + if (this.dropDisposer) { + this.dropDisposer(); + } + if (ele) { + this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop } @@ -174,7 +177,11 @@ export class CollectionFreeFormView extends CollectionViewBase { render() { const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; - const value: Document[] = Document.GetList<Document>(fieldKey, []); + // const value: Document[] = Document.GetList<Document>(fieldKey, []); + const lvalue = Document.GetT<ListField<Document>>(fieldKey, ListField); + if (!lvalue || lvalue === "<Waiting>") { + return <p>Error loading collection data</p> + } const panx: number = Document.GetNumber(KeyStore.PanX, 0); const pany: number = Document.GetNumber(KeyStore.PanY, 0); const currScale: number = Document.GetNumber(KeyStore.Scale, 1); @@ -189,11 +196,11 @@ export class CollectionFreeFormView extends CollectionViewBase { onContextMenu={(e) => e.preventDefault()} onDrop={this.onDrop} onDragOver={this.onDragOver} - ref={this._containerRef}> + ref={this.createDropTarget}> <div className="collectionfreeformview" style={{ transform: `translate(${panx}px, ${pany}px) scale(${currScale}, ${currScale})`, transformOrigin: `left, top` }} ref={this._canvasRef}> <div className="node-container" ref={this._nodeContainerRef}> - {value.map(doc => { + {lvalue.Data.map(doc => { return (<CollectionFreeFormDocumentView key={doc.Id} ContainingCollectionView={this} Document={doc} DocumentView={undefined} />); })} </div> diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 730ce62f2..3df351c6c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -128,6 +128,10 @@ export class DocumentView extends React.Component<DocumentViewProps> { render() { let bindings = { ...this.props } as any; + let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField); + if (!lkeys || lkeys === "<Waiting>") { + return <p>Error loading layout keys</p>; + } for (const key of this.layoutKeys) { bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data } diff --git a/src/fields/BasicField.ts b/src/fields/BasicField.ts index 4b68ba01f..95f737dea 100644 --- a/src/fields/BasicField.ts +++ b/src/fields/BasicField.ts @@ -3,11 +3,19 @@ import { observable, computed, action } from "mobx"; import { Server } from "../client/Server"; export abstract class BasicField<T> extends Field { - constructor(data: T, id: FIELD_ID = undefined) { + constructor(data: T, save: boolean, id: FIELD_ID = undefined) { super(id); this.data = data; - Server.UpdateField(this) + if (save) { + Server.UpdateField(this) + } + } + + UpdateFromServer(data: any) { + if (this.data !== data) { + this.data = data; + } } @observable diff --git a/src/fields/Document.ts b/src/fields/Document.ts index cb4f6f25c..4bab1299d 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -10,13 +10,22 @@ import { Types } from "../server/Message"; import { ObjectID } from "bson"; export class Document extends Field { - public fields: ObservableMap<Key, Opt<Field>> = new ObservableMap(); + public fields: ObservableMap<string, { key: Key, field: Opt<Field> }> = new ObservableMap(); public _proxies: ObservableMap<string, FIELD_ID> = new ObservableMap(); - constructor(id?: string) { + constructor(id?: string, save: boolean = true) { super(id) - Server.UpdateField(this) + 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]); + } } @computed @@ -27,25 +36,25 @@ 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); + if (this.fields.has(key.Id)) { + field = this.fields.get(key.Id)!.field; } else if (this._proxies.has(key.Id)) { field = Server.GetDocumentField(this, key); } } else { let doc: FieldValue<Document> = this; while (doc && doc != FieldWaiting && field != FieldWaiting) { - if (!doc.fields.has(key)) { + if (!doc.fields.has(key.Id)) { if (doc._proxies.has(key.Id)) { field = Server.GetDocumentField(doc, key); break; } - if ((doc.fields.has(KeyStore.Prototype) || doc._proxies.has(KeyStore.Prototype.Id))) { + if ((doc.fields.has(KeyStore.Prototype.Id) || doc._proxies.has(KeyStore.Prototype.Id))) { doc = doc.GetPrototype(); } else break; } else { - field = doc.fields.get(key); + field = doc.fields.get(key.Id)!.field; break; } } @@ -93,11 +102,11 @@ export class Document extends Field { @action Set(key: Key, field: Field | undefined): void { if (field) { - this.fields.set(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); + this.fields.delete(key.Id); this._proxies.delete(key.Id) // Server.DeleteDocumentField(this, key); } @@ -144,8 +153,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); @@ -167,14 +176,14 @@ export class Document extends Field { } ToJson(): { type: Types, data: [string, string][], _id: string } { - console.log(this.fields) + // console.log(this.fields) let fields: [string, string][] = [] this._proxies.forEach((field, key) => { if (field) { fields.push([key, field as string]) } }); - console.log(fields) + // console.log(fields) return { type: Types.Document, diff --git a/src/fields/DocumentReference.ts b/src/fields/DocumentReference.ts index b1edd1dff..4096cbb92 100644 --- a/src/fields/DocumentReference.ts +++ b/src/fields/DocumentReference.ts @@ -17,6 +17,10 @@ export class DocumentReference extends Field { super(); } + UpdateFromServer() { + + } + Dereference(): FieldValue<Field> { return this.document.Get(this.key); } diff --git a/src/fields/Field.ts b/src/fields/Field.ts index 5a65e35b9..853fb9327 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -1,14 +1,6 @@ import { Utils } from "../Utils"; import { Types } from "../server/Message"; -import { NumberField } from "./NumberField"; -import { TextField } from "./TextField"; -import { RichTextField } from "./RichTextField"; -import { KeyStore, Key } from "./Key"; -import { ImageField } from "./ImageField"; -import { ListField } from "./ListField"; -import { Document } from "./Document"; -import { Server } from "../client/Server"; export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T }): Opt<T> { if (field) { @@ -28,6 +20,10 @@ 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: string; get Id(): string { return this.id; @@ -56,6 +52,8 @@ export abstract class Field { return this.id === other.id; } + abstract UpdateFromServer(serverData: any): void; + abstract ToScriptString(): string; abstract TrySetValue(value: any): boolean; diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts index 12503b2ec..aba011199 100644 --- a/src/fields/ImageField.ts +++ b/src/fields/ImageField.ts @@ -4,8 +4,8 @@ import { Types } from "../server/Message"; import { ObjectID } from "bson"; export class ImageField extends BasicField<URL> { - constructor(data: URL | undefined = undefined, id: FIELD_ID = undefined) { - super(data == undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, id); + constructor(data: URL | undefined = undefined, id: FIELD_ID = undefined, save: boolean = true) { + super(data == undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id); } toString(): string { diff --git a/src/fields/Key.ts b/src/fields/Key.ts index 1e878a361..51d8e093c 100644 --- a/src/fields/Key.ts +++ b/src/fields/Key.ts @@ -12,11 +12,17 @@ export class Key extends Field { return this.name; } - constructor(name: string, id?: string) { + constructor(name: string, id?: string, save: boolean = true) { super(id || Utils.GenerateDeterministicGuid(name)); this.name = name; - Server.UpdateField(this) + if (save) { + Server.UpdateField(this) + } + } + + UpdateFromServer(data: string) { + this.name = data; } TrySetValue(value: any): boolean { diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts index cf8a1ba8b..1585746df 100644 --- a/src/fields/ListField.ts +++ b/src/fields/ListField.ts @@ -1,10 +1,59 @@ -import { Field, FIELD_ID } from "./Field"; +import { Field, FIELD_ID, FieldValue, Opt } from "./Field"; import { BasicField } from "./BasicField"; import { Types } from "../server/Message"; +import { observe, action } from "mobx"; +import { Server } from "../client/Server"; +import { ServerUtils } from "../server/ServerUtil"; export class ListField<T extends Field> extends BasicField<T[]> { - constructor(data: T[] = [], id: FIELD_ID = undefined) { - super(data.slice(), id); + private _proxies: string[] = [] + constructor(data: T[] = [], id: FIELD_ID = undefined, save: boolean = true) { + super(data, save, id); + this.updateProxies(); + if (save) { + Server.UpdateField(this); + } + observe(this.Data, () => { + this.updateProxies() + Server.UpdateField(this); + }) + } + + 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 { @@ -15,11 +64,17 @@ export class ListField<T extends Field> extends BasicField<T[]> { return new ListField<T>(this.Data); } - ToJson(): { type: Types, data: T[], _id: string } { + ToJson(): { type: Types, data: string[], _id: string } { return { type: Types.List, - data: this.Data, + 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 7fa9ec2e4..29e285201 100644 --- a/src/fields/NumberField.ts +++ b/src/fields/NumberField.ts @@ -3,8 +3,8 @@ import { Types } from "../server/Message"; import { FIELD_ID } from "./Field"; export class NumberField extends BasicField<number> { - constructor(data: number = 0, id: FIELD_ID = undefined) { - super(data, id); + constructor(data: number = 0, id: FIELD_ID = undefined, save: boolean = true) { + super(data, save, id); } ToScriptString(): string { diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index 3c6151009..9783107e3 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -3,8 +3,8 @@ import { Types } from "../server/Message"; import { FIELD_ID } from "./Field"; export class RichTextField extends BasicField<string> { - constructor(data: string = "", id: FIELD_ID = undefined) { - super(data, id); + constructor(data: string = "", id: FIELD_ID = undefined, save: boolean = true) { + super(data, save, id); } ToScriptString(): string { diff --git a/src/fields/TextField.ts b/src/fields/TextField.ts index f2b277298..efb3c035f 100644 --- a/src/fields/TextField.ts +++ b/src/fields/TextField.ts @@ -3,8 +3,8 @@ import { FIELD_ID } from "./Field"; import { Types } from "../server/Message"; export class TextField extends BasicField<string> { - constructor(data: string = "", id: FIELD_ID = undefined) { - super(data, id); + constructor(data: string = "", id: FIELD_ID = undefined, save: boolean = true) { + super(data, save, id); } ToScriptString(): string { diff --git a/src/server/Message.ts b/src/server/Message.ts index 528ba0cd7..1ec6e25f3 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -121,6 +121,7 @@ export namespace MessageStore { export const AddDocument = new Message<DocumentTransfer>("Add Document"); export const SetField = new Message<{ _id: string, data: any, type: Types }>("Set Field") export const GetField = new Message<string>("Get Field") + export const GetFields = new Message<string[]>("Get Fields") export const GetDocument = new Message<string>("Get Document"); export const DeleteAll = new Message<any>("Delete All"); }
\ No newline at end of file diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index d1de71dbe..03b9751da 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -11,38 +11,36 @@ import { Types } from './Message'; import { Utils } from '../Utils'; export class ServerUtils { - public static FromJson(json: string): Field { - let obj = JSON.parse(JSON.stringify(json)) - console.log(obj) + public static FromJson(json: any): Field { + let obj = json let data: any = obj.data let id: string = obj._id let type: Types = obj.type - if (!(data && id && type != undefined)) { + if (!(data !== undefined && id && type !== undefined)) { console.log("how did you manage to get an object that doesn't have a data or an id?") return new TextField("Something to fill the space", Utils.GenerateGuid()); } switch (type) { case Types.Number: - return new NumberField(data, id) + return new NumberField(data, id, false) case Types.Text: - return new TextField(data, id) + return new TextField(data, id, false) case Types.RichText: - return new RichTextField(data, id) + return new RichTextField(data, id, false) case Types.Key: - return new Key(data, id) + return new Key(data, id, false) case Types.Image: - return new ImageField(data, id) + return new ImageField(data, id, false) case Types.List: - return new ListField(data, id) + return ListField.FromJson(id, data) case Types.Document: - let doc: Document = new Document(id) + let doc: Document = new Document(id, false) let fields: [string, string][] = data as [string, string][] fields.forEach(element => { doc._proxies.set(element[0], element[1]); }); - console.log(doc._proxies) return doc } return new TextField(data, id) diff --git a/src/server/database.ts b/src/server/database.ts index 282238327..51103520e 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -12,7 +12,9 @@ export class Database { public update(id: string, value: any) { this.MongoClient.connect(this.url, { bufferMaxEntries: 1 }, (err, db) => { let collection = db.db().collection('documents'); - collection.update({ _id: id }, { $set: value }); + collection.update({ _id: id }, { $set: value }, { + upsert: true + }); db.close(); }); } @@ -34,15 +36,12 @@ export class Database { public insert(kvpairs: any) { this.MongoClient.connect(this.url, { bufferMaxEntries: 1 }, (err, db) => { - // console.log(kvpairs) let collection = db.db().collection('documents'); collection.insertOne(kvpairs, (err: any, res: any) => { if (err) { // console.log(err) return } - // console.log(kvpairs) - // console.log("1 document inserted") }); db.close(); }); @@ -69,6 +68,24 @@ export class Database { }); } + public getDocuments(ids: string[], fn: (res: any) => void) { + var result: JSON; + this.MongoClient.connect(this.url, { + bufferMaxEntries: 1 + }, (err, db) => { + if (err) { + console.log(err) + return undefined + } + let collection = db.db().collection('documents'); + let cursor = collection.find({ _id: { "$in": ids } }) + cursor.toArray((err, docs) => { + fn(docs); + }) + db.close(); + }); + } + public print() { console.log("db says hi!") } diff --git a/src/server/index.ts b/src/server/index.ts index ef02ffbfc..812338080 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -14,6 +14,7 @@ import { Database } from './database'; import { ServerUtils } from './ServerUtil'; import { ObjectID } from 'mongodb'; import { Document } from '../fields/Document'; +import * as io from 'socket.io' const config = require('../../webpack.config') const compiler = webpack(config) const port = 1050; // default port to listen @@ -30,6 +31,11 @@ app.get("/hello", (req, res) => { res.send("<p>Hello</p>"); }) +app.get("/delete", (req, res) => { + deleteAll(); + res.redirect("/"); +}); + app.use(wdm(compiler, { publicPath: config.output.publicPath })) @@ -41,7 +47,7 @@ app.listen(port, () => { console.log(`server started at http://localhost:${port}`); }) -const server = require("socket.io")(); +const server = io(); interface Map { [key: string]: Client; } @@ -53,8 +59,9 @@ server.on("connection", function (socket: Socket) { Utils.Emit(socket, MessageStore.Foo, "handshooken") Utils.AddServerHandler(socket, MessageStore.Bar, barReceived) - Utils.AddServerHandler(socket, MessageStore.SetField, setField) + Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args)) Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField) + Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields) Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteAll) }) @@ -82,15 +89,13 @@ function getField([id, callback]: [string, (result: any) => void]) { }) } -function setField(newValue: Transferable) { - Database.Instance.getDocument(newValue._id, (res: any) => { - if (res) { - Database.Instance.update(newValue._id, newValue) - } - else { - Database.Instance.insert(newValue) - } - }) +function getFields([ids, callback]: [string[], (result: any) => void]) { + Database.Instance.getDocuments(ids, callback); +} + +function setField(socket: Socket, newValue: Transferable) { + Database.Instance.update(newValue._id, newValue) + socket.broadcast.emit(MessageStore.SetField.Message, newValue) } server.listen(serverPort); |