From 79d8b5c812db5c28f477ace8db0ee4d9e18a84b7 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 17 Apr 2019 06:16:50 -0400
Subject: Started implementing new documents

---
 src/debug/Test.tsx | 39 +++++++++++++++++++++------------------
 1 file changed, 21 insertions(+), 18 deletions(-)

(limited to 'src/debug/Test.tsx')

diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 11f2b0c4e..ca093e5b2 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,29 +1,32 @@
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
-import JsxParser from 'react-jsx-parser';
+import { serialize, deserialize, map } from 'serializr';
+import { URLField, Doc } from '../fields/NewDoc';
 
-class Hello extends React.Component<{ firstName: string, lastName: string }> {
-    render() {
-        return <div>Hello {this.props.firstName} {this.props.lastName}</div>;
+class Test extends React.Component {
+    onClick = () => {
+        const url = new URLField(new URL("http://google.com"));
+        const doc = new Doc("a");
+        const doc2 = new Doc("b");
+        doc.hello = 5;
+        doc.fields = "test";
+        doc.test = "hello doc";
+        doc.url = url;
+        doc.testDoc = doc2;
+
+        console.log(doc.hello);
+        console.log(doc.fields);
+        console.log(doc.test);
+        console.log(doc.url);
+        console.log(doc.testDoc);
     }
-}
 
-class Test extends React.Component {
     render() {
-        let jsx = "<Hello {...props}/>";
-        let bindings = {
-            props: {
-                firstName: "First",
-                lastName: "Last"
-            }
-        };
-        return <JsxParser jsx={jsx} bindings={bindings} components={{ Hello }}></JsxParser>;
+        return <button onClick={this.onClick}>Click me</button>;
     }
 }
 
-ReactDOM.render((
-    <div style={{ position: "absolute", width: "100%", height: "100%" }}>
-        <Test />
-    </div>),
+ReactDOM.render(
+    <Test />,
     document.getElementById('root')
 );
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 6afdd5e5136394c9dc739b5de390aa1b55c6360f Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 17 Apr 2019 13:51:40 -0400
Subject: Serialization is mostly working

---
 src/client/util/SerializationHelper.ts | 72 ++++++++++++++++++++++++++++++++++
 src/debug/Test.tsx                     | 10 ++---
 src/fields/NewDoc.ts                   | 45 ++++++++++++++-------
 3 files changed, 107 insertions(+), 20 deletions(-)
 create mode 100644 src/client/util/SerializationHelper.ts

(limited to 'src/debug/Test.tsx')

diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
new file mode 100644
index 000000000..656101c95
--- /dev/null
+++ b/src/client/util/SerializationHelper.ts
@@ -0,0 +1,72 @@
+import { PropSchema, serialize, deserialize, custom } from "serializr";
+import { Field } from "../../fields/NewDoc";
+
+export class SerializationHelper {
+
+    public static Serialize(obj: Field): any {
+        if (!obj) {
+            return null;
+        }
+
+        if (typeof obj !== 'object') {
+            return obj;
+        }
+
+        if (!(obj.constructor.name in reverseMap)) {
+            throw Error(`type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
+        }
+
+        const json = serialize(obj);
+        json.__type = reverseMap[obj.constructor.name];
+        return json;
+    }
+
+    public static Deserialize(obj: any): any {
+        if (!obj) {
+            return null;
+        }
+
+        if (typeof obj !== 'object') {
+            return obj;
+        }
+
+        if (!obj.__type) {
+            throw Error("No property 'type' found in JSON.");
+        }
+
+        if (!(obj.__type in serializationTypes)) {
+            throw Error(`type '${obj.__type}' not registered. Make sure you register it using a @Deserializable decorator`);
+        }
+
+        return deserialize(serializationTypes[obj.__type], obj);
+    }
+}
+
+let serializationTypes: { [name: string]: any } = {};
+let reverseMap: { [ctor: string]: string } = {};
+
+export function Deserializable(name: string): Function;
+export function Deserializable(constructor: Function): void;
+export function Deserializable(constructor: Function | string): Function | void {
+    function addToMap(name: string, ctor: Function) {
+        if (!(name in serializationTypes)) {
+            serializationTypes[name] = constructor;
+            reverseMap[ctor.name] = name;
+        } else {
+            throw new Error(`Name ${name} has already been registered as deserializable`);
+        }
+    }
+    if (typeof constructor === "string") {
+        return (ctor: Function) => {
+            addToMap(constructor, ctor);
+        };
+    }
+    addToMap(constructor.name, constructor);
+}
+
+export function autoObject(): PropSchema {
+    return custom(
+        (s) => SerializationHelper.Serialize(s),
+        (s) => SerializationHelper.Deserialize(s)
+    );
+}
\ No newline at end of file
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index ca093e5b2..7e7b3a964 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -2,6 +2,7 @@ import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { serialize, deserialize, map } from 'serializr';
 import { URLField, Doc } from '../fields/NewDoc';
+import { SerializationHelper } from '../client/util/SerializationHelper';
 
 class Test extends React.Component {
     onClick = () => {
@@ -14,11 +15,10 @@ class Test extends React.Component {
         doc.url = url;
         doc.testDoc = doc2;
 
-        console.log(doc.hello);
-        console.log(doc.fields);
-        console.log(doc.test);
-        console.log(doc.url);
-        console.log(doc.testDoc);
+        console.log("doc", doc);
+        const cereal = Doc.Serialize(doc);
+        console.log("cereal", cereal);
+        console.log("doc again", SerializationHelper.Deserialize(cereal));
     }
 
     render() {
diff --git a/src/fields/NewDoc.ts b/src/fields/NewDoc.ts
index d0e518306..05dd14bc3 100644
--- a/src/fields/NewDoc.ts
+++ b/src/fields/NewDoc.ts
@@ -1,16 +1,15 @@
 import { observable, action } from "mobx";
 import { Server } from "../client/Server";
 import { UndoManager } from "../client/util/UndoManager";
-import { serialize, deserialize, serializable, primitive } from "serializr";
+import { serialize, deserialize, serializable, primitive, map, alias } from "serializr";
+import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
 
-const Type = Symbol("type");
-
-const Id = Symbol("id");
 export abstract class RefField {
-    readonly [Id]: string;
+    @serializable(alias("id", primitive()))
+    readonly __id: string;
 
     constructor(id: string) {
-        this[Id] = id;
+        this.__id = id;
     }
 }
 
@@ -31,6 +30,8 @@ function url() {
         }
     };
 }
+
+@Deserializable
 export class URLField extends ObjectField {
     @serializable(url())
     url: URL;
@@ -41,6 +42,7 @@ export class URLField extends ObjectField {
     }
 }
 
+@Deserializable
 export class ProxyField<T extends RefField> extends ObjectField {
     constructor();
     constructor(value: T);
@@ -48,7 +50,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
         super();
         if (value) {
             this.cache = value;
-            this.fieldId = value[Id];
+            this.fieldId = value.__id;
         }
     }
 
@@ -94,11 +96,17 @@ export class ProxyField<T extends RefField> extends ObjectField {
 export type Field = number | string | boolean | ObjectField | RefField;
 
 const Self = Symbol("Self");
+
+@Deserializable
 export class Doc extends RefField {
 
     private static setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+        if (prop === "__id" || prop === "__fields") {
+            target[prop] = value;
+            return true;
+        }
         const curValue = target.__fields[prop];
-        if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
+        if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value.__id)) {
             // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
             // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way
             return true;
@@ -130,16 +138,16 @@ export class Doc extends RefField {
     }
 
     private static getter(target: any, prop: string | symbol | number, receiver: any): any {
-        if (typeof prop !== "string") {
-            return undefined;
+        if (typeof prop === "symbol") {
+            return target[prop];
+        }
+        if (prop === "__id" || prop === "__fields") {
+            return target[prop];
         }
         return Doc.getField(target, prop, receiver);
     }
 
-    private static getField(target: any, prop: string, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
-        if (typeof prop === "symbol") {
-            return target.__fields[prop];
-        }
+    private static getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
         const field = target.__fields[prop];
         if (field instanceof ProxyField) {
             return field.value(callback);
@@ -162,6 +170,10 @@ export class Doc extends RefField {
         return new Promise(res => Doc.getField(self, key, ignoreProto, res));
     }
 
+    static Serialize(doc: Doc) {
+        return SerializationHelper.Serialize(doc[Self]);
+    }
+
     constructor(id: string) {
         super(id);
         const doc = new Proxy<this>(this, {
@@ -175,6 +187,7 @@ export class Doc extends RefField {
 
     [key: string]: Field | null | undefined;
 
+    @serializable(alias("fields", map(autoObject())))
     @observable
     private __fields: { [key: string]: Field | null | undefined } = {};
 
@@ -187,4 +200,6 @@ export class Doc extends RefField {
 
 export namespace Doc {
     export const Prototype = Symbol("Prototype");
-}
\ No newline at end of file
+}
+
+export const GetAsync = Doc.GetAsync;
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 9712a046868ee51a565a425d3216a2bb297c4eee Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 17 Apr 2019 19:45:59 -0400
Subject: Got saving to database working

---
 src/client/DocServer.ts                | 72 +++++++++++++++++++++++++++++++++
 src/client/util/SerializationHelper.ts | 73 +++++++++++++++++++++++++++++-----
 src/debug/Test.tsx                     |  6 +--
 src/fields/NewDoc.ts                   | 52 ++++++++++++++++--------
 src/server/Message.ts                  | 12 ++++++
 src/server/database.ts                 | 16 ++++----
 src/server/index.ts                    | 19 ++++++++-
 7 files changed, 212 insertions(+), 38 deletions(-)
 create mode 100644 src/client/DocServer.ts

(limited to 'src/debug/Test.tsx')

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
new file mode 100644
index 000000000..9a3e122e8
--- /dev/null
+++ b/src/client/DocServer.ts
@@ -0,0 +1,72 @@
+import * as OpenSocket from 'socket.io-client';
+import { MessageStore, Types } from "./../server/Message";
+import { Opt, FieldWaiting, RefField, HandleUpdate } from '../fields/NewDoc';
+import { Utils } from '../Utils';
+import { SerializationHelper } from './util/SerializationHelper';
+
+export namespace DocServer {
+    const _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
+    const _socket = OpenSocket(`${window.location.protocol}//${window.location.hostname}:4321`);
+    const GUID: string = Utils.GenerateGuid();
+
+    export async function GetRefField(id: string): Promise<Opt<RefField>> {
+        let cached = _cache[id];
+        if (cached === undefined) {
+            const prom = Utils.EmitCallback(_socket, MessageStore.GetRefField, id).then(fieldJson => {
+                const field = fieldJson === undefined ? fieldJson : SerializationHelper.Deserialize(fieldJson);
+                if (field) {
+                    _cache[id] = field;
+                } else {
+                    delete _cache[id];
+                }
+                return field;
+            });
+            _cache[id] = prom;
+            return prom;
+        } else if (cached instanceof Promise) {
+            return cached;
+        } else {
+            return cached;
+        }
+    }
+
+    export function UpdateField(id: string, diff: any) {
+        Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
+    }
+
+    export function CreateField(initialState: any) {
+        if (!("id" in initialState)) {
+            throw new Error("Can't create a field on the server without an id");
+        }
+        Utils.Emit(_socket, MessageStore.CreateField, initialState);
+    }
+
+    function respondToUpdate(diff: any) {
+        const id = diff.id;
+        if (id === undefined) {
+            return;
+        }
+        const field = _cache[id];
+        const update = (f: Opt<RefField>) => {
+            if (f === undefined) {
+                return;
+            }
+            const handler = f[HandleUpdate];
+            if (handler) {
+                handler(diff);
+            }
+        };
+        if (field instanceof Promise) {
+            field.then(update);
+        } else {
+            update(field);
+        }
+    }
+
+    function connected(message: string) {
+        _socket.emit(MessageStore.Bar.Message, GUID);
+    }
+
+    Utils.AddServerHandler(_socket, MessageStore.Foo, connected);
+    Utils.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
+}
\ No newline at end of file
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 656101c95..7273c3fe4 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,9 +1,13 @@
-import { PropSchema, serialize, deserialize, custom } from "serializr";
+import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema, primitive, SKIP } from "serializr";
 import { Field } from "../../fields/NewDoc";
 
-export class SerializationHelper {
+export namespace SerializationHelper {
+    let serializing: number = 0;
+    export function IsSerializing() {
+        return serializing > 0;
+    }
 
-    public static Serialize(obj: Field): any {
+    export function Serialize(obj: Field): any {
         if (!obj) {
             return null;
         }
@@ -12,16 +16,18 @@ export class SerializationHelper {
             return obj;
         }
 
+        serializing += 1;
         if (!(obj.constructor.name in reverseMap)) {
             throw Error(`type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
         }
 
         const json = serialize(obj);
         json.__type = reverseMap[obj.constructor.name];
+        serializing -= 1;
         return json;
     }
 
-    public static Deserialize(obj: any): any {
+    export function Deserialize(obj: any): any {
         if (!obj) {
             return null;
         }
@@ -30,6 +36,7 @@ export class SerializationHelper {
             return obj;
         }
 
+        serializing += 1;
         if (!obj.__type) {
             throw Error("No property 'type' found in JSON.");
         }
@@ -38,32 +45,78 @@ export class SerializationHelper {
             throw Error(`type '${obj.__type}' not registered. Make sure you register it using a @Deserializable decorator`);
         }
 
-        return deserialize(serializationTypes[obj.__type], obj);
+        const value = deserialize(serializationTypes[obj.__type], obj);
+        serializing -= 1;
+        return value;
     }
 }
 
 let serializationTypes: { [name: string]: any } = {};
 let reverseMap: { [ctor: string]: string } = {};
 
-export function Deserializable(name: string): Function;
+export interface DeserializableOpts {
+    (constructor: Function): void;
+    withFields(fields: string[]): Function;
+}
+
+export function Deserializable(name: string): DeserializableOpts;
 export function Deserializable(constructor: Function): void;
-export function Deserializable(constructor: Function | string): Function | void {
+export function Deserializable(constructor: Function | string): DeserializableOpts | void {
     function addToMap(name: string, ctor: Function) {
         if (!(name in serializationTypes)) {
-            serializationTypes[name] = constructor;
+            serializationTypes[name] = ctor;
             reverseMap[ctor.name] = name;
         } else {
             throw new Error(`Name ${name} has already been registered as deserializable`);
         }
     }
     if (typeof constructor === "string") {
-        return (ctor: Function) => {
+        return Object.assign((ctor: Function) => {
             addToMap(constructor, ctor);
-        };
+        }, { withFields: Deserializable.withFields });
     }
     addToMap(constructor.name, constructor);
 }
 
+export namespace Deserializable {
+    export function withFields(fields: string[]) {
+        return function (constructor: { new(...fields: any[]): any }) {
+            Deserializable(constructor);
+            let schema = getDefaultModelSchema(constructor);
+            if (schema) {
+                schema.factory = context => {
+                    const args = fields.map(key => context.json[key]);
+                    return new constructor(...args);
+                };
+                // TODO A modified version of this would let us not reassign fields that we're passing into the constructor later on in deserializing
+                // fields.forEach(field => {
+                //     if (field in schema.props) {
+                //         let propSchema = schema.props[field];
+                //         if (propSchema === false) {
+                //             return;
+                //         } else if (propSchema === true) {
+                //             propSchema = primitive();
+                //         }
+                //         schema.props[field] = custom(propSchema.serializer,
+                //             () => {
+                //                 return SKIP;
+                //             });
+                //     }
+                // });
+            } else {
+                schema = {
+                    props: {},
+                    factory: context => {
+                        const args = fields.map(key => context.json[key]);
+                        return new constructor(...args);
+                    }
+                };
+                setDefaultModelSchema(constructor, schema);
+            }
+        };
+    }
+}
+
 export function autoObject(): PropSchema {
     return custom(
         (s) => SerializationHelper.Serialize(s),
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 7e7b3a964..660115453 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -7,8 +7,8 @@ import { SerializationHelper } from '../client/util/SerializationHelper';
 class Test extends React.Component {
     onClick = () => {
         const url = new URLField(new URL("http://google.com"));
-        const doc = new Doc("a");
-        const doc2 = new Doc("b");
+        const doc = new Doc();
+        const doc2 = new Doc();
         doc.hello = 5;
         doc.fields = "test";
         doc.test = "hello doc";
@@ -16,7 +16,7 @@ class Test extends React.Component {
         doc.testDoc = doc2;
 
         console.log("doc", doc);
-        const cereal = Doc.Serialize(doc);
+        const cereal = SerializationHelper.Serialize(doc);
         console.log("cereal", cereal);
         console.log("doc again", SerializationHelper.Deserialize(cereal));
     }
diff --git a/src/fields/NewDoc.ts b/src/fields/NewDoc.ts
index 05dd14bc3..46f2e20e9 100644
--- a/src/fields/NewDoc.ts
+++ b/src/fields/NewDoc.ts
@@ -1,16 +1,24 @@
 import { observable, action } from "mobx";
 import { Server } from "../client/Server";
 import { UndoManager } from "../client/util/UndoManager";
-import { serialize, deserialize, serializable, primitive, map, alias } from "serializr";
+import { serializable, primitive, map, alias } from "serializr";
 import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
+import { Utils } from "../Utils";
+import { DocServer } from "../client/DocServer";
 
+export const HandleUpdate = Symbol("HandleUpdate");
+const Id = Symbol("Id");
 export abstract class RefField {
     @serializable(alias("id", primitive()))
-    readonly __id: string;
+    private __id: string;
+    readonly [Id]: string;
 
-    constructor(id: string) {
-        this.__id = id;
+    constructor(id?: string) {
+        this.__id = id || Utils.GenerateGuid();
+        this[Id] = this.__id;
     }
+
+    protected [HandleUpdate]?(diff: any): void;
 }
 
 const Update = Symbol("Update");
@@ -31,10 +39,10 @@ function url() {
     };
 }
 
-@Deserializable
+@Deserializable("url")
 export class URLField extends ObjectField {
     @serializable(url())
-    url: URL;
+    readonly url: URL;
 
     constructor(url: URL) {
         super();
@@ -42,7 +50,7 @@ export class URLField extends ObjectField {
     }
 }
 
-@Deserializable
+@Deserializable("proxy")
 export class ProxyField<T extends RefField> extends ObjectField {
     constructor();
     constructor(value: T);
@@ -50,7 +58,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
         super();
         if (value) {
             this.cache = value;
-            this.fieldId = value.__id;
+            this.fieldId = value[Id];
         }
     }
 
@@ -94,19 +102,26 @@ export class ProxyField<T extends RefField> extends ObjectField {
 }
 
 export type Field = number | string | boolean | ObjectField | RefField;
+export type Opt<T> = T | undefined;
+export type FieldWaiting = null;
+export const FieldWaiting: FieldWaiting = null;
 
 const Self = Symbol("Self");
 
-@Deserializable
+@Deserializable("doc").withFields(["id"])
 export class Doc extends RefField {
 
     private static setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
-        if (prop === "__id" || prop === "__fields") {
+        if (SerializationHelper.IsSerializing()) {
+            target[prop] = value;
+            return true;
+        }
+        if (typeof prop === "symbol") {
             target[prop] = value;
             return true;
         }
         const curValue = target.__fields[prop];
-        if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value.__id)) {
+        if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
             // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
             // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way
             return true;
@@ -120,7 +135,7 @@ export class Doc extends RefField {
             }
             value[Parent] = target;
             value[Update] = (diff?: any) => {
-                if (!diff) diff = serialize(value);
+                if (!diff) diff = SerializationHelper.Serialize(value);
                 target[Update]({ [prop]: diff });
             };
         }
@@ -129,7 +144,7 @@ export class Doc extends RefField {
             delete curValue[Update];
         }
         target.__fields[prop] = value;
-        target[Update]({ [prop]: typeof value === "object" ? serialize(value) : value });
+        target[Update]({ ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) });
         UndoManager.AddEvent({
             redo: () => receiver[prop] = value,
             undo: () => receiver[prop] = curValue
@@ -141,7 +156,7 @@ export class Doc extends RefField {
         if (typeof prop === "symbol") {
             return target[prop];
         }
-        if (prop === "__id" || prop === "__fields") {
+        if (SerializationHelper.IsSerializing()) {
             return target[prop];
         }
         return Doc.getField(target, prop, receiver);
@@ -174,7 +189,7 @@ export class Doc extends RefField {
         return SerializationHelper.Serialize(doc[Self]);
     }
 
-    constructor(id: string) {
+    constructor(id?: string, forceSave?: boolean) {
         super(id);
         const doc = new Proxy<this>(this, {
             set: Doc.setter,
@@ -182,6 +197,9 @@ export class Doc extends RefField {
             deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); },
             defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
         });
+        if (!id || forceSave) {
+            DocServer.CreateField(SerializationHelper.Serialize(doc));
+        }
         return doc;
     }
 
@@ -191,8 +209,8 @@ export class Doc extends RefField {
     @observable
     private __fields: { [key: string]: Field | null | undefined } = {};
 
-    private [Update] = (diff?: any) => {
-        console.log(JSON.stringify(diff || this));
+    private [Update] = (diff: any) => {
+        DocServer.UpdateField(this[Id], diff);
     }
 
     private [Self] = this;
diff --git a/src/server/Message.ts b/src/server/Message.ts
index bbe4ffcad..843a923d1 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -24,6 +24,14 @@ export interface Transferable {
     readonly data?: any;
 }
 
+export interface Reference {
+    readonly id: string;
+}
+
+export interface Diff extends Reference {
+    readonly diff: any;
+}
+
 export namespace MessageStore {
     export const Foo = new Message<string>("Foo");
     export const Bar = new Message<string>("Bar");
@@ -32,4 +40,8 @@ export namespace MessageStore {
     export const GetFields = new Message<string[]>("Get Fields"); // send string[] of 'id' get Transferable[] back
     export const GetDocument = new Message<string>("Get Document");
     export const DeleteAll = new Message<any>("Delete All");
+
+    export const GetRefField = new Message<string>("Get Ref Field");
+    export const UpdateField = new Message<Diff>("Update Ref Field");
+    export const CreateField = new Message<Reference>("Create Ref Field");
 }
diff --git a/src/server/database.ts b/src/server/database.ts
index 5457e4dd5..a61b4d823 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -13,14 +13,14 @@ export class Database {
         this.MongoClient.connect(this.url, (err, client) => this.db = client.db());
     }
 
-    public update(id: string, value: any, callback: () => void) {
+    public update(id: string, value: any, callback: () => void, upsert = true, collectionName = Database.DocumentsCollection) {
         if (this.db) {
-            let collection = this.db.collection('documents');
+            let collection = this.db.collection(collectionName);
             const prom = this.currentWrites[id];
             let newProm: Promise<void>;
             const run = (): Promise<void> => {
                 return new Promise<void>(resolve => {
-                    collection.updateOne({ _id: id }, { $set: value }, { upsert: true }
+                    collection.updateOne({ _id: id }, { $set: value }, { upsert }
                         , (err, res) => {
                             if (err) {
                                 console.log(err.message);
@@ -51,10 +51,12 @@ export class Database {
             this.db && this.db.collection(collectionName).deleteMany({}, res));
     }
 
-    public insert(kvpairs: any, collectionName = Database.DocumentsCollection) {
-        this.db && this.db.collection(collectionName).insertOne(kvpairs, (err, res) =>
-            err // &&  console.log(err)
-        );
+    public insert(value: any, collectionName = Database.DocumentsCollection) {
+        if ("id" in value) {
+            value._id = value.id;
+            delete value.id;
+        }
+        this.db && this.db.collection(collectionName).insertOne(value);
     }
 
     public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = Database.DocumentsCollection) {
diff --git a/src/server/index.ts b/src/server/index.ts
index 70a7d266c..d6d5f0e55 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -22,7 +22,7 @@ import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLo
 import { DashUserModel } from './authentication/models/user_model';
 import { Client } from './Client';
 import { Database } from './database';
-import { MessageStore, Transferable } from "./Message";
+import { MessageStore, Transferable, Diff } from "./Message";
 import { RouteStore } from './RouteStore';
 const app = express();
 const config = require('../../webpack.config');
@@ -232,6 +232,10 @@ server.on("connection", function (socket: Socket) {
     Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
     Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
     Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields);
+
+    Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField);
+    Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));
+    Utils.AddServerHandler(socket, MessageStore.GetRefField, GetRefField);
 });
 
 function deleteFields() {
@@ -262,5 +266,18 @@ function setField(socket: Socket, newValue: Transferable) {
         socket.broadcast.emit(MessageStore.SetField.Message, newValue));
 }
 
+function GetRefField([id, callback]: [string, (result?: Transferable) => void]) {
+    Database.Instance.getDocument(id, callback, "newDocuments");
+}
+
+function UpdateField(socket: Socket, diff: Diff) {
+    Database.Instance.update(diff.id, diff.diff,
+        () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false, "newDocuments");
+}
+
+function CreateField(newValue: any) {
+    Database.Instance.insert(newValue, "newDocuments");
+}
+
 server.listen(serverPort);
 console.log(`listening on port ${serverPort}`);
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 8eebfed7906e1e2088d528e3af36af21094c38a9 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Thu, 18 Apr 2019 19:17:48 -0400
Subject: Implemented document schemas

---
 src/debug/Test.tsx   | 50 ++++++++++++++++++++++++++---
 src/fields/NewDoc.ts | 90 ++++++++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 129 insertions(+), 11 deletions(-)

(limited to 'src/debug/Test.tsx')

diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 660115453..b46eb4477 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,9 +1,35 @@
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { serialize, deserialize, map } from 'serializr';
-import { URLField, Doc } from '../fields/NewDoc';
+import { URLField, Doc, createSchema, makeInterface, makeStrictInterface } from '../fields/NewDoc';
 import { SerializationHelper } from '../client/util/SerializationHelper';
 
+const schema1 = createSchema({
+    hello: "number",
+    test: "string",
+    fields: "boolean",
+    url: URLField,
+    testDoc: Doc
+});
+
+const TestDoc = makeInterface(schema1);
+type TestDoc = makeInterface<typeof schema1>;
+
+const schema2 = createSchema({
+    hello: URLField,
+    test: "boolean",
+    fields: "string",
+    url: "number",
+    testDoc: URLField
+});
+
+const Test2Doc = makeStrictInterface(schema2);
+type Test2Doc = makeStrictInterface<typeof schema2>;
+
+const assert = (bool: boolean) => {
+    if (!bool) throw new Error();
+};
+
 class Test extends React.Component {
     onClick = () => {
         const url = new URLField(new URL("http://google.com"));
@@ -15,10 +41,24 @@ class Test extends React.Component {
         doc.url = url;
         doc.testDoc = doc2;
 
-        console.log("doc", doc);
-        const cereal = SerializationHelper.Serialize(doc);
-        console.log("cereal", cereal);
-        console.log("doc again", SerializationHelper.Deserialize(cereal));
+
+        const test1: TestDoc = TestDoc(doc);
+        const test2: Test2Doc = Test2Doc(doc);
+        assert(test1.hello === 5);
+        assert(test1.fields === undefined);
+        assert(test1.test === "hello doc");
+        assert(test1.url === url);
+        assert(test1.testDoc === doc2);
+        test1.myField = 20;
+        assert(test1.myField === 20);
+
+        assert(test2.hello === undefined);
+        assert(test2.fields === "test");
+        assert(test2.test === undefined);
+        assert(test2.url === undefined);
+        assert(test2.testDoc === undefined);
+        test2.url = 35;
+        assert(test2.url === 35);
     }
 
     render() {
diff --git a/src/fields/NewDoc.ts b/src/fields/NewDoc.ts
index 46f2e20e9..150b8dae8 100644
--- a/src/fields/NewDoc.ts
+++ b/src/fields/NewDoc.ts
@@ -1,5 +1,4 @@
 import { observable, action } from "mobx";
-import { Server } from "../client/Server";
 import { UndoManager } from "../client/util/UndoManager";
 import { serializable, primitive, map, alias } from "serializr";
 import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
@@ -185,10 +184,6 @@ export class Doc extends RefField {
         return new Promise(res => Doc.getField(self, key, ignoreProto, res));
     }
 
-    static Serialize(doc: Doc) {
-        return SerializationHelper.Serialize(doc[Self]);
-    }
-
     constructor(id?: string, forceSave?: boolean) {
         super(id);
         const doc = new Proxy<this>(this, {
@@ -220,4 +215,87 @@ export namespace Doc {
     export const Prototype = Symbol("Prototype");
 }
 
-export const GetAsync = Doc.GetAsync;
\ No newline at end of file
+export const GetAsync = Doc.GetAsync;
+
+interface IDoc {
+    [key: string]: Field | null | undefined;
+}
+
+interface ImageDocument extends IDoc {
+    data: URLField;
+    test: number;
+}
+
+export type ToType<T> =
+    T extends "string" ? string :
+    T extends "number" ? number :
+    T extends "boolean" ? boolean :
+    T extends { new(...args: any[]): infer R } ? R : undefined;
+
+export type ToInterface<T> = {
+    [P in keyof T]: ToType<T[P]>;
+};
+
+interface Interface {
+    [key: string]: { new(...args: any[]): (ObjectField | RefField) } | "number" | "boolean" | "string";
+}
+
+type FieldCtor<T extends Field> = { new(): T } | "number" | "string" | "boolean";
+
+function Cast<T extends Field>(field: Field | undefined, ctor: FieldCtor<T>): T | undefined {
+    if (field !== undefined) {
+        if (typeof ctor === "string") {
+            if (typeof field === ctor) {
+                return field as T;
+            }
+        } else if (field instanceof ctor) {
+            return field;
+        }
+    }
+    return undefined;
+}
+
+export type makeInterface<T extends Interface> = Partial<ToInterface<T>> & Doc;
+export function makeInterface<T extends Interface>(schema: T): (doc: Doc) => makeInterface<T> {
+    return function (doc: any) {
+        return new Proxy(doc, {
+            get(target, prop) {
+                const field = target[prop];
+                if (prop in schema) {
+                    return Cast(field, (schema as any)[prop]);
+                }
+                return field;
+            }
+        });
+    };
+}
+
+export type makeStrictInterface<T extends Interface> = Partial<ToInterface<T>>;
+export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc) => makeStrictInterface<T> {
+    const proto = {};
+    for (const key in schema) {
+        const type = schema[key];
+        Object.defineProperty(proto, key, {
+            get() {
+                return Cast(this.__doc[key], type);
+            },
+            set(value) {
+                value = Cast(value, type);
+                if (value !== undefined) {
+                    this.__doc[key] = value;
+                    return;
+                }
+                throw new TypeError("Expected type " + type);
+            }
+        });
+    }
+    return function (doc: any) {
+        const obj = Object.create(proto);
+        obj.__doc = doc;
+        return obj;
+    };
+}
+
+export function createSchema<T extends Interface>(schema: T): T {
+    return schema;
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From be5d2d30bdd98dfc32c28a84ad606eb2b4599932 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 19 Apr 2019 02:40:46 -0400
Subject: Kind of got list typing working for schemas

---
 src/client/views/nodes/ImageBox.tsx | 10 ++++-----
 src/debug/Test.tsx                  | 13 ++++++++---
 src/fields/NewDoc.ts                | 43 +++++++++++++++++++++++++++++--------
 3 files changed, 49 insertions(+), 17 deletions(-)

(limited to 'src/debug/Test.tsx')

diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index fe0b07bc0..71b431b84 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -38,7 +38,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
     onLoad = (target: any) => {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
-        if (this._photoIndex == 0) this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
+        if (this._photoIndex === 0) this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
     }
 
 
@@ -53,7 +53,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
     onDrop = (e: React.DragEvent) => {
         e.stopPropagation();
         e.preventDefault();
-        console.log("IMPLEMENT ME PLEASE")
+        console.log("IMPLEMENT ME PLEASE");
     }
 
 
@@ -145,9 +145,9 @@ export class ImageBox extends React.Component<FieldViewProps> {
         let left = (nativeWidth - paths.length * dist) / 2;
         return paths.map((p, i) =>
             <div className="imageBox-placer" key={i} >
-                <div className="imageBox-dot" style={{ background: (i == this._photoIndex ? "black" : "gray"), transform: `translate(${i * dist + left}px, 0px)` }} onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); this.onDotDown(i); }} />
+                <div className="imageBox-dot" style={{ background: (i === this._photoIndex ? "black" : "gray"), transform: `translate(${i * dist + left}px, 0px)` }} onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); this.onDotDown(i); }} />
             </div>
-        )
+        );
     }
 
     render() {
@@ -159,7 +159,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
         let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
         return (
             <div className="imageBox-cont" onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
-                <img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex == 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
+                <img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
                 {paths.length > 1 ? this.dots(paths) : (null)}
                 {this.lightbox(paths)}
             </div>);
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index b46eb4477..033615be6 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { serialize, deserialize, map } from 'serializr';
-import { URLField, Doc, createSchema, makeInterface, makeStrictInterface } from '../fields/NewDoc';
+import { URLField, Doc, createSchema, makeInterface, makeStrictInterface, List, ListSpec } from '../fields/NewDoc';
 import { SerializationHelper } from '../client/util/SerializationHelper';
 
 const schema1 = createSchema({
@@ -18,7 +18,7 @@ type TestDoc = makeInterface<typeof schema1>;
 const schema2 = createSchema({
     hello: URLField,
     test: "boolean",
-    fields: "string",
+    fields: { List: "number" } as ListSpec<number>,
     url: "number",
     testDoc: URLField
 });
@@ -26,6 +26,13 @@ const schema2 = createSchema({
 const Test2Doc = makeStrictInterface(schema2);
 type Test2Doc = makeStrictInterface<typeof schema2>;
 
+const schema3 = createSchema({
+    test: "boolean",
+});
+
+const Test3Doc = makeStrictInterface(schema3);
+type Test3Doc = makeStrictInterface<typeof schema3>;
+
 const assert = (bool: boolean) => {
     if (!bool) throw new Error();
 };
@@ -53,7 +60,7 @@ class Test extends React.Component {
         assert(test1.myField === 20);
 
         assert(test2.hello === undefined);
-        assert(test2.fields === "test");
+        // assert(test2.fields === "test");
         assert(test2.test === undefined);
         assert(test2.url === undefined);
         assert(test2.testDoc === undefined);
diff --git a/src/fields/NewDoc.ts b/src/fields/NewDoc.ts
index 150b8dae8..7be0d5146 100644
--- a/src/fields/NewDoc.ts
+++ b/src/fields/NewDoc.ts
@@ -107,6 +107,10 @@ export const FieldWaiting: FieldWaiting = null;
 
 const Self = Symbol("Self");
 
+export class List<T extends Field> extends ObjectField {
+    [index: number]: T;
+}
+
 @Deserializable("doc").withFields(["id"])
 export class Doc extends RefField {
 
@@ -230,26 +234,47 @@ export type ToType<T> =
     T extends "string" ? string :
     T extends "number" ? number :
     T extends "boolean" ? boolean :
-    T extends { new(...args: any[]): infer R } ? R : undefined;
+    T extends ListSpec<infer U> ? List<U> :
+    T extends { new(...args: any[]): infer R } ? R : never;
+
+export type ToConstructor<T> =
+    T extends string ? "string" :
+    T extends number ? "number" :
+    T extends boolean ? "boolean" : { new(...args: any[]): T };
 
 export type ToInterface<T> = {
     [P in keyof T]: ToType<T[P]>;
 };
 
+// type ListSpec<T extends Field[]> = { List: FieldCtor<Head<T>> | ListSpec<Tail<T>> };
+export type ListSpec<T> = { List: FieldCtor<T> };
+
+// type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
+
+type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
+type Tail<T extends any[]> =
+    ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : [];
+type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
+
 interface Interface {
-    [key: string]: { new(...args: any[]): (ObjectField | RefField) } | "number" | "boolean" | "string";
+    [key: string]: ToConstructor<Field> | ListSpec<Field>;
+    // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
 }
 
-type FieldCtor<T extends Field> = { new(): T } | "number" | "string" | "boolean";
+type FieldCtor<T extends Field> = ToConstructor<T> | ListSpec<Field>;
 
-function Cast<T extends Field>(field: Field | undefined, ctor: FieldCtor<T>): T | undefined {
+function Cast<T extends Field>(field: Field | undefined, ctor: FieldCtor<T>): ToType<typeof ctor> | undefined {
     if (field !== undefined) {
         if (typeof ctor === "string") {
             if (typeof field === ctor) {
-                return field as T;
+                return field as ToType<typeof ctor>;
+            }
+        } else if (typeof ctor === "object") {
+            if (field instanceof List) {
+                return field as ToType<typeof ctor>;
             }
-        } else if (field instanceof ctor) {
-            return field;
+        } else if (field instanceof (ctor as any)) {
+            return field as ToType<typeof ctor>;
         }
     }
     return undefined;
@@ -277,10 +302,10 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc)
         const type = schema[key];
         Object.defineProperty(proto, key, {
             get() {
-                return Cast(this.__doc[key], type);
+                return Cast(this.__doc[key], type as any);
             },
             set(value) {
-                value = Cast(value, type);
+                value = Cast(value, type as any);
                 if (value !== undefined) {
                     this.__doc[key] = value;
                     return;
-- 
cgit v1.2.3-70-g09d2


From ecae4ae106be3e07471208cb93ec0965548d2d12 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 19 Apr 2019 05:40:10 -0400
Subject: Added decent  amount of list support

---
 src/debug/Test.tsx   |   8 +++
 src/fields/NewDoc.ts | 190 ++++++++++++++++++++++++++++-----------------------
 2 files changed, 111 insertions(+), 87 deletions(-)

(limited to 'src/debug/Test.tsx')

diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 033615be6..6a677f80f 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -66,6 +66,14 @@ class Test extends React.Component {
         assert(test2.testDoc === undefined);
         test2.url = 35;
         assert(test2.url === 35);
+        const l = new List<number>();
+        //TODO push, and other array functions don't go through the proxy
+        l.push(1);
+        //TODO currently length, and any other string fields will get serialized
+        l.length = 3;
+        l[2] = 5;
+        console.log(l.slice());
+        console.log(SerializationHelper.Serialize(l));
     }
 
     render() {
diff --git a/src/fields/NewDoc.ts b/src/fields/NewDoc.ts
index 7be0d5146..c22df4b70 100644
--- a/src/fields/NewDoc.ts
+++ b/src/fields/NewDoc.ts
@@ -1,6 +1,6 @@
 import { observable, action } from "mobx";
 import { UndoManager } from "../client/util/UndoManager";
-import { serializable, primitive, map, alias } from "serializr";
+import { serializable, primitive, map, alias, list } from "serializr";
 import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
 import { Utils } from "../Utils";
 import { DocServer } from "../client/DocServer";
@@ -21,9 +21,10 @@ export abstract class RefField {
 }
 
 const Update = Symbol("Update");
+const OnUpdate = Symbol("OnUpdate");
 const Parent = Symbol("Parent");
 export class ObjectField {
-    protected [Update]?: (diff?: any) => void;
+    protected [OnUpdate]?: (diff?: any) => void;
     private [Parent]?: Doc;
 }
 
@@ -107,92 +108,112 @@ export const FieldWaiting: FieldWaiting = null;
 
 const Self = Symbol("Self");
 
-export class List<T extends Field> extends ObjectField {
-    [index: number]: T;
+function setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+    if (SerializationHelper.IsSerializing()) {
+        target[prop] = value;
+        return true;
+    }
+    if (typeof prop === "symbol") {
+        target[prop] = value;
+        return true;
+    }
+    const curValue = target.__fields[prop];
+    if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
+        // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
+        // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way
+        return true;
+    }
+    if (value instanceof RefField) {
+        value = new ProxyField(value);
+    }
+    if (value instanceof ObjectField) {
+        if (value[Parent] && value[Parent] !== target) {
+            throw new Error("Can't put the same object in multiple documents at the same time");
+        }
+        value[Parent] = target;
+        value[OnUpdate] = (diff?: any) => {
+            if (!diff) diff = SerializationHelper.Serialize(value);
+            target[Update]({ [prop]: diff });
+        };
+    }
+    if (curValue instanceof ObjectField) {
+        delete curValue[Parent];
+        delete curValue[OnUpdate];
+    }
+    target.__fields[prop] = value;
+    target[Update]({ ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) });
+    UndoManager.AddEvent({
+        redo: () => receiver[prop] = value,
+        undo: () => receiver[prop] = curValue
+    });
+    return true;
 }
 
-@Deserializable("doc").withFields(["id"])
-export class Doc extends RefField {
-
-    private static setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
-        if (SerializationHelper.IsSerializing()) {
-            target[prop] = value;
-            return true;
-        }
-        if (typeof prop === "symbol") {
-            target[prop] = value;
-            return true;
-        }
-        const curValue = target.__fields[prop];
-        if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
-            // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
-            // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way
-            return true;
-        }
-        if (value instanceof RefField) {
-            value = new ProxyField(value);
-        }
-        if (value instanceof ObjectField) {
-            if (value[Parent] && value[Parent] !== target) {
-                throw new Error("Can't put the same object in multiple documents at the same time");
-            }
-            value[Parent] = target;
-            value[Update] = (diff?: any) => {
-                if (!diff) diff = SerializationHelper.Serialize(value);
-                target[Update]({ [prop]: diff });
-            };
-        }
-        if (curValue instanceof ObjectField) {
-            delete curValue[Parent];
-            delete curValue[Update];
-        }
-        target.__fields[prop] = value;
-        target[Update]({ ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) });
-        UndoManager.AddEvent({
-            redo: () => receiver[prop] = value,
-            undo: () => receiver[prop] = curValue
-        });
-        return true;
+function getter(target: any, prop: string | symbol | number, receiver: any): any {
+    if (typeof prop === "symbol") {
+        return target.__fields[prop] || target[prop];
+    }
+    if (SerializationHelper.IsSerializing()) {
+        return target[prop];
     }
+    return getField(target, prop, receiver);
+}
 
-    private static getter(target: any, prop: string | symbol | number, receiver: any): any {
-        if (typeof prop === "symbol") {
-            return target[prop];
-        }
-        if (SerializationHelper.IsSerializing()) {
-            return target[prop];
+function getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
+    const field = target.__fields[prop];
+    if (field instanceof ProxyField) {
+        return field.value(callback);
+    }
+    if (field === undefined && !ignoreProto) {
+        const proto = getField(target, "prototype", true);
+        if (proto instanceof Doc) {
+            let field = proto[prop];
+            callback && callback(field === null ? undefined : field);
+            return field;
         }
-        return Doc.getField(target, prop, receiver);
     }
+    callback && callback(field);
+    return field;
 
-    private static getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
-        const field = target.__fields[prop];
-        if (field instanceof ProxyField) {
-            return field.value(callback);
-        }
-        if (field === undefined && !ignoreProto) {
-            const proto = Doc.getField(target, "prototype", true);
-            if (proto instanceof Doc) {
-                let field = proto[prop];
-                callback && callback(field === null ? undefined : field);
-                return field;
-            }
-        }
-        callback && callback(field);
-        return field;
+}
 
+@Deserializable("list")
+class ListImpl<T extends Field> extends ObjectField {
+    constructor() {
+        super();
+        const list = new Proxy<this>(this, {
+            set: function (a, b, c, d) { return setter(a, b, c, d); },
+            get: getter,
+            deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); },
+            defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
+        });
+        return list;
     }
 
-    static GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
-        const self = doc[Self];
-        return new Promise(res => Doc.getField(self, key, ignoreProto, res));
+    [key: number]: T | null | undefined;
+
+    @serializable(alias("fields", list(autoObject())))
+    @observable
+    private __fields: (T | null | undefined)[] = [];
+
+    private [Update] = (diff: any) => {
+        console.log(diff);
+        const update = this[OnUpdate];
+        update && update(diff);
     }
 
+    private [Self] = this;
+}
+export type List<T extends Field> = ListImpl<T> & T[];
+export const List: { new <T extends Field>(): List<T> } = ListImpl as any;
+
+@Deserializable("doc").withFields(["id"])
+export class Doc extends RefField {
     constructor(id?: string, forceSave?: boolean) {
         super(id);
         const doc = new Proxy<this>(this, {
-            set: Doc.setter,
-            get: Doc.getter,
+            set: setter,
+            get: getter,
             deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); },
             defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
         });
@@ -216,20 +237,15 @@ export class Doc extends RefField {
 }
 
 export namespace Doc {
+    export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
+        const self = doc[Self];
+        return new Promise(res => getField(self, key, ignoreProto, res));
+    }
     export const Prototype = Symbol("Prototype");
 }
 
 export const GetAsync = Doc.GetAsync;
 
-interface IDoc {
-    [key: string]: Field | null | undefined;
-}
-
-interface ImageDocument extends IDoc {
-    data: URLField;
-    test: number;
-}
-
 export type ToType<T> =
     T extends "string" ? string :
     T extends "number" ? number :
@@ -261,20 +277,20 @@ interface Interface {
     // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
 }
 
-type FieldCtor<T extends Field> = ToConstructor<T> | ListSpec<Field>;
+type FieldCtor<T extends Field> = ToConstructor<T> | ListSpec<T>;
 
-function Cast<T extends Field>(field: Field | undefined, ctor: FieldCtor<T>): ToType<typeof ctor> | undefined {
+function Cast<T extends FieldCtor<Field>>(field: Field | undefined, ctor: T): ToType<T> | undefined {
     if (field !== undefined) {
         if (typeof ctor === "string") {
             if (typeof field === ctor) {
-                return field as ToType<typeof ctor>;
+                return field as ToType<T>;
             }
         } else if (typeof ctor === "object") {
             if (field instanceof List) {
-                return field as ToType<typeof ctor>;
+                return field as ToType<T>;
             }
         } else if (field instanceof (ctor as any)) {
-            return field as ToType<typeof ctor>;
+            return field as ToType<T>;
         }
     }
     return undefined;
-- 
cgit v1.2.3-70-g09d2


From e678ac7f21e0c44eaa8ad88577093cdb313e21bb Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 19 Apr 2019 23:13:17 -0400
Subject: Deleted more old fields and split new stuff into multiple files

---
 src/client/DocServer.ts                |   2 +-
 src/client/util/SerializationHelper.ts |   2 +-
 src/debug/Test.tsx                     |   8 +-
 src/fields/HtmlField.ts                |  25 ---
 src/fields/ListField.ts                | 196 ------------------
 src/fields/NewDoc.ts                   | 358 ---------------------------------
 src/fields/TupleField.ts               |  59 ------
 src/new_fields/Doc.ts                  |  90 +++++++++
 src/new_fields/HtmlField.ts            |  14 ++
 src/new_fields/List.ts                 |  35 ++++
 src/new_fields/Proxy.ts                |  55 +++++
 src/new_fields/Schema.ts               |  47 +++++
 src/new_fields/Types.ts                |  58 ++++++
 src/new_fields/URLField.ts             |  25 +++
 src/new_fields/util.ts                 |  73 +++++++
 15 files changed, 405 insertions(+), 642 deletions(-)
 delete mode 100644 src/fields/HtmlField.ts
 delete mode 100644 src/fields/ListField.ts
 delete mode 100644 src/fields/NewDoc.ts
 delete mode 100644 src/fields/TupleField.ts
 create mode 100644 src/new_fields/Doc.ts
 create mode 100644 src/new_fields/HtmlField.ts
 create mode 100644 src/new_fields/List.ts
 create mode 100644 src/new_fields/Proxy.ts
 create mode 100644 src/new_fields/Schema.ts
 create mode 100644 src/new_fields/Types.ts
 create mode 100644 src/new_fields/URLField.ts
 create mode 100644 src/new_fields/util.ts

(limited to 'src/debug/Test.tsx')

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 9a3e122e8..615e48af0 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,6 +1,6 @@
 import * as OpenSocket from 'socket.io-client';
 import { MessageStore, Types } from "./../server/Message";
-import { Opt, FieldWaiting, RefField, HandleUpdate } from '../fields/NewDoc';
+import { Opt, FieldWaiting, RefField, HandleUpdate } from '../new_fields/Doc';
 import { Utils } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
 
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 7273c3fe4..ac70aba9d 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,5 +1,5 @@
 import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema, primitive, SKIP } from "serializr";
-import { Field } from "../../fields/NewDoc";
+import { Field } from "../../new_fields/Doc";
 
 export namespace SerializationHelper {
     let serializing: number = 0;
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 6a677f80f..8b9c9fa0b 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,8 +1,12 @@
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
-import { serialize, deserialize, map } from 'serializr';
-import { URLField, Doc, createSchema, makeInterface, makeStrictInterface, List, ListSpec } from '../fields/NewDoc';
 import { SerializationHelper } from '../client/util/SerializationHelper';
+import { createSchema, makeInterface, makeStrictInterface } from '../new_fields/Schema';
+import { URLField } from '../new_fields/URLField';
+import { Doc } from '../new_fields/Doc';
+import { ListSpec } from '../new_fields/Types';
+import { List } from '../new_fields/List';
+const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 const schema1 = createSchema({
     hello: "number",
diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts
deleted file mode 100644
index a1d880070..000000000
--- a/src/fields/HtmlField.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-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() {
-        return {
-            type: Types.Html,
-            data: this.Data,
-            id: this.Id,
-        };
-    }
-}
\ No newline at end of file
diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts
deleted file mode 100644
index e24099126..000000000
--- a/src/fields/ListField.ts
+++ /dev/null
@@ -1,196 +0,0 @@
-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";
-import { FieldMap } from "../client/SocketStub";
-import { ScriptField } from "./ScriptField";
-
-export class ListField<T extends Field> extends BasicField<T[]> {
-    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);
-        }
-        this.observeList();
-    }
-
-    private _processingServerUpdate: boolean = false;
-
-    private observeDisposer: Lambda | undefined;
-    private observeList(): void {
-        if (this.observeDisposer) {
-            this.observeDisposer();
-        }
-        this.observeDisposer = observe(this.Data as IObservableArray<T>, (change: IArrayChange<T> | IArraySplice<T>) => {
-            const target = change.object;
-            this.updateProxies();
-            if (change.type === "splice") {
-                this.runScripts(change.removed, false);
-                UndoManager.AddEvent({
-                    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: () => target[change.index] = change.oldValue,
-                    redo: () => target[change.index] = change.newValue
-                });
-                this.runScripts([change.newValue], true);
-            }
-            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);
-    }
-
-    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) {
-        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;
-                }
-
-                this._processingServerUpdate = true;
-                for (let i = 0; i < proxies.length && added; i++) {
-                    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) {
-                        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;
-            }
-        }));
-
-        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 {
-        return "new ListField([" + this.Data.map(field => field.ToScriptString()).join(", ") + "])";
-    }
-
-    Copy(): Field {
-        return new ListField<T>(this.Data);
-    }
-
-
-    UpdateFromServer(data: { fields: string[], scripts: string[] }) {
-        this._proxies = data.fields;
-        this._scriptIds = data.scripts;
-    }
-    ToJson() {
-        return {
-            type: Types.List,
-            data: {
-                fields: this._proxies,
-                scripts: this._scriptIds,
-            },
-            id: this.Id
-        };
-    }
-
-    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/NewDoc.ts b/src/fields/NewDoc.ts
deleted file mode 100644
index 70dc1867c..000000000
--- a/src/fields/NewDoc.ts
+++ /dev/null
@@ -1,358 +0,0 @@
-import { observable, action } from "mobx";
-import { UndoManager } from "../client/util/UndoManager";
-import { serializable, primitive, map, alias, list } from "serializr";
-import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
-import { Utils } from "../Utils";
-import { DocServer } from "../client/DocServer";
-
-export const HandleUpdate = Symbol("HandleUpdate");
-const Id = Symbol("Id");
-export abstract class RefField {
-    @serializable(alias("id", primitive()))
-    private __id: string;
-    readonly [Id]: string;
-
-    constructor(id?: string) {
-        this.__id = id || Utils.GenerateGuid();
-        this[Id] = this.__id;
-    }
-
-    protected [HandleUpdate]?(diff: any): void;
-}
-
-const Update = Symbol("Update");
-const OnUpdate = Symbol("OnUpdate");
-const Parent = Symbol("Parent");
-export class ObjectField {
-    protected [OnUpdate]?: (diff?: any) => void;
-    private [Parent]?: Doc;
-}
-
-function url() {
-    return {
-        serializer: function (value: URL) {
-            return value.href;
-        },
-        deserializer: function (jsonValue: string, done: (err: any, val: any) => void) {
-            done(undefined, new URL(jsonValue));
-        }
-    };
-}
-
-@Deserializable("url")
-export class URLField extends ObjectField {
-    @serializable(url())
-    readonly url: URL;
-
-    constructor(url: URL) {
-        super();
-        this.url = url;
-    }
-}
-
-@Deserializable("proxy")
-export class ProxyField<T extends RefField> extends ObjectField {
-    constructor();
-    constructor(value: T);
-    constructor(value?: T) {
-        super();
-        if (value) {
-            this.cache = value;
-            this.fieldId = value[Id];
-        }
-    }
-
-    @serializable(primitive())
-    readonly fieldId: string = "";
-
-    // This getter/setter and nested object thing is 
-    // because mobx doesn't play well with observable proxies
-    @observable.ref
-    private _cache: { readonly field: T | undefined } = { field: undefined };
-    private get cache(): T | undefined {
-        return this._cache.field;
-    }
-    private set cache(field: T | undefined) {
-        this._cache = { field };
-    }
-
-    private failed = false;
-    private promise?: Promise<any>;
-
-    value(callback?: ((field: T | undefined) => void)): T | undefined | null {
-        if (this.cache) {
-            callback && callback(this.cache);
-            return this.cache;
-        }
-        if (this.failed) {
-            return undefined;
-        }
-        if (!this.promise) {
-            // this.promise = Server.GetField(this.fieldId).then(action((field: any) => {
-            //     this.promise = undefined;
-            //     this.cache = field;
-            //     if (field === undefined) this.failed = true;
-            //     return field;
-            // }));
-            this.promise = new Promise(r => r());
-        }
-        callback && this.promise.then(callback);
-        return null;
-    }
-}
-
-export type Field = number | string | boolean | ObjectField | RefField;
-export type Opt<T> = T | undefined;
-export type FieldWaiting = null;
-export const FieldWaiting: FieldWaiting = null;
-
-const Self = Symbol("Self");
-
-function setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
-    if (SerializationHelper.IsSerializing()) {
-        target[prop] = value;
-        return true;
-    }
-    if (typeof prop === "symbol") {
-        target[prop] = value;
-        return true;
-    }
-    const curValue = target.__fields[prop];
-    if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
-        // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
-        // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way
-        return true;
-    }
-    if (value instanceof RefField) {
-        value = new ProxyField(value);
-    }
-    if (value instanceof ObjectField) {
-        if (value[Parent] && value[Parent] !== target) {
-            throw new Error("Can't put the same object in multiple documents at the same time");
-        }
-        value[Parent] = target;
-        value[OnUpdate] = (diff?: any) => {
-            if (!diff) diff = SerializationHelper.Serialize(value);
-            target[Update]({ [prop]: diff });
-        };
-    }
-    if (curValue instanceof ObjectField) {
-        delete curValue[Parent];
-        delete curValue[OnUpdate];
-    }
-    target.__fields[prop] = value;
-    target[Update]({ ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) });
-    UndoManager.AddEvent({
-        redo: () => receiver[prop] = value,
-        undo: () => receiver[prop] = curValue
-    });
-    return true;
-}
-
-function getter(target: any, prop: string | symbol | number, receiver: any): any {
-    if (typeof prop === "symbol") {
-        return target.__fields[prop] || target[prop];
-    }
-    if (SerializationHelper.IsSerializing()) {
-        return target[prop];
-    }
-    return getField(target, prop, receiver);
-}
-
-function getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
-    const field = target.__fields[prop];
-    if (field instanceof ProxyField) {
-        return field.value(callback);
-    }
-    if (field === undefined && !ignoreProto) {
-        const proto = getField(target, "prototype", true);
-        if (proto instanceof Doc) {
-            let field = proto[prop];
-            callback && callback(field === null ? undefined : field);
-            return field;
-        }
-    }
-    callback && callback(field);
-    return field;
-
-}
-
-@Deserializable("list")
-class ListImpl<T extends Field> extends ObjectField {
-    constructor() {
-        super();
-        const list = new Proxy<this>(this, {
-            set: function (a, b, c, d) { return setter(a, b, c, d); },
-            get: getter,
-            deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); },
-            defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
-        });
-        return list;
-    }
-
-    [key: number]: T | null | undefined;
-
-    @serializable(alias("fields", list(autoObject())))
-    @observable
-    private __fields: (T | null | undefined)[] = [];
-
-    private [Update] = (diff: any) => {
-        console.log(diff);
-        const update = this[OnUpdate];
-        update && update(diff);
-    }
-
-    private [Self] = this;
-}
-export type List<T extends Field> = ListImpl<T> & T[];
-export const List: { new <T extends Field>(): List<T> } = ListImpl as any;
-
-@Deserializable("doc").withFields(["id"])
-export class Doc extends RefField {
-    constructor(id?: string, forceSave?: boolean) {
-        super(id);
-        const doc = new Proxy<this>(this, {
-            set: setter,
-            get: getter,
-            deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); },
-            defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
-        });
-        if (!id || forceSave) {
-            DocServer.CreateField(SerializationHelper.Serialize(doc));
-        }
-        return doc;
-    }
-
-    [key: string]: Field | null | undefined;
-
-    @serializable(alias("fields", map(autoObject())))
-    @observable
-    private __fields: { [key: string]: Field | null | undefined } = {};
-
-    private [Update] = (diff: any) => {
-        DocServer.UpdateField(this[Id], diff);
-    }
-
-    private [Self] = this;
-}
-
-export namespace Doc {
-    export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
-        const self = doc[Self];
-        return new Promise(res => getField(self, key, ignoreProto, res));
-    }
-    export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: FieldCtor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
-        const self = doc[Self];
-        return new Promise(async res => {
-            const field = await GetAsync(doc, key, ignoreProto);
-            return Cast(field, ctor);
-        });
-    }
-    export function Get(doc: Doc, key: string, ignoreProto: boolean = false): Field | null | undefined {
-        const self = doc[Self];
-        return getField(self, key, ignoreProto);
-    }
-    export function GetT<T extends Field>(doc: Doc, key: string, ctor: FieldCtor<T>, ignoreProto: boolean = false): Field | null | undefined {
-        return Cast(Get(doc, key, ignoreProto), ctor);
-    }
-    export const Prototype = Symbol("Prototype");
-}
-
-export const GetAsync = Doc.GetAsync;
-
-export type ToType<T> =
-    T extends "string" ? string :
-    T extends "number" ? number :
-    T extends "boolean" ? boolean :
-    T extends ListSpec<infer U> ? List<U> :
-    T extends { new(...args: any[]): infer R } ? R : never;
-
-export type ToConstructor<T> =
-    T extends string ? "string" :
-    T extends number ? "number" :
-    T extends boolean ? "boolean" : { new(...args: any[]): T };
-
-export type ToInterface<T> = {
-    [P in keyof T]: ToType<T[P]>;
-};
-
-// type ListSpec<T extends Field[]> = { List: FieldCtor<Head<T>> | ListSpec<Tail<T>> };
-export type ListSpec<T> = { List: FieldCtor<T> };
-
-// type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
-
-type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
-type Tail<T extends any[]> =
-    ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : [];
-type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
-
-interface Interface {
-    [key: string]: ToConstructor<Field> | ListSpec<Field>;
-    // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
-}
-
-type FieldCtor<T extends Field> = ToConstructor<T> | ListSpec<T>;
-
-function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T): ToType<T> | null | undefined {
-    if (field !== undefined && field !== null) {
-        if (typeof ctor === "string") {
-            if (typeof field === ctor) {
-                return field as ToType<T>;
-            }
-        } else if (typeof ctor === "object") {
-            if (field instanceof List) {
-                return field as ToType<T>;
-            }
-        } else if (field instanceof (ctor as any)) {
-            return field as ToType<T>;
-        }
-    } else {
-        return field;
-    }
-    return undefined;
-}
-
-export type makeInterface<T extends Interface> = Partial<ToInterface<T>> & Doc;
-export function makeInterface<T extends Interface>(schema: T): (doc: Doc) => makeInterface<T> {
-    return function (doc: any) {
-        return new Proxy(doc, {
-            get(target, prop) {
-                const field = target[prop];
-                if (prop in schema) {
-                    return Cast(field, (schema as any)[prop]);
-                }
-                return field;
-            }
-        });
-    };
-}
-
-export type makeStrictInterface<T extends Interface> = Partial<ToInterface<T>>;
-export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc) => makeStrictInterface<T> {
-    const proto = {};
-    for (const key in schema) {
-        const type = schema[key];
-        Object.defineProperty(proto, key, {
-            get() {
-                return Cast(this.__doc[key], type as any);
-            },
-            set(value) {
-                value = Cast(value, type as any);
-                if (value !== undefined) {
-                    this.__doc[key] = value;
-                    return;
-                }
-                throw new TypeError("Expected type " + type);
-            }
-        });
-    }
-    return function (doc: any) {
-        const obj = Object.create(proto);
-        obj.__doc = doc;
-        return obj;
-    };
-}
-
-export function createSchema<T extends Interface>(schema: T): T {
-    return schema;
-}
\ No newline at end of file
diff --git a/src/fields/TupleField.ts b/src/fields/TupleField.ts
deleted file mode 100644
index 347f1fa05..000000000
--- a/src/fields/TupleField.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-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 TupleField<T, U> extends BasicField<[T, U]> {
-    constructor(data: [T, U], id?: FieldId, save: boolean = true) {
-        super(data, save, id);
-        if (save) {
-            Server.UpdateField(this);
-        }
-        this.observeTuple();
-    }
-
-    private observeDisposer: Lambda | undefined;
-    private observeTuple(): void {
-        this.observeDisposer = observe(this.Data as (T | U)[] as IObservableArray<T | U>, (change: IArrayChange<T | U> | IArraySplice<T | U>) => {
-            if (change.type === "update") {
-                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?");
-            }
-        });
-    }
-
-    protected setData(value: [T, U]) {
-        if (this.observeDisposer) {
-            this.observeDisposer();
-        }
-        this.data = observable(value) as (T | U)[] as [T, U];
-        this.observeTuple();
-    }
-
-    UpdateFromServer(values: [T, U]) {
-        this.setData(values);
-    }
-
-    ToScriptString(): string {
-        return `new TupleField([${this.Data[0], this.Data[1]}])`;
-    }
-
-    Copy(): Field {
-        return new TupleField<T, U>(this.Data);
-    }
-
-    ToJson() {
-        return {
-            type: Types.Tuple,
-            data: this.Data,
-            id: this.Id
-        };
-    }
-}
\ No newline at end of file
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
new file mode 100644
index 000000000..c67170573
--- /dev/null
+++ b/src/new_fields/Doc.ts
@@ -0,0 +1,90 @@
+import { observable, action } from "mobx";
+import { serializable, primitive, map, alias, list } from "serializr";
+import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
+import { Utils } from "../Utils";
+import { DocServer } from "../client/DocServer";
+import { setter, getter, getField } from "./util";
+import { Cast, FieldCtor } from "./Types";
+
+export const HandleUpdate = Symbol("HandleUpdate");
+export const Id = Symbol("Id");
+export abstract class RefField {
+    @serializable(alias("id", primitive()))
+    private __id: string;
+    readonly [Id]: string;
+
+    constructor(id?: string) {
+        this.__id = id || Utils.GenerateGuid();
+        this[Id] = this.__id;
+    }
+
+    protected [HandleUpdate]?(diff: any): void;
+}
+
+export const Update = Symbol("Update");
+export const OnUpdate = Symbol("OnUpdate");
+export const Parent = Symbol("Parent");
+export class ObjectField {
+    protected [OnUpdate]?: (diff?: any) => void;
+    private [Parent]?: Doc;
+}
+
+export type Field = number | string | boolean | ObjectField | RefField;
+export type Opt<T> = T | undefined;
+export type FieldWaiting = null;
+export const FieldWaiting: FieldWaiting = null;
+
+export const Self = Symbol("Self");
+
+@Deserializable("doc").withFields(["id"])
+export class Doc extends RefField {
+    constructor(id?: string, forceSave?: boolean) {
+        super(id);
+        const doc = new Proxy<this>(this, {
+            set: setter,
+            get: getter,
+            deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); },
+            defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
+        });
+        if (!id || forceSave) {
+            DocServer.CreateField(SerializationHelper.Serialize(doc));
+        }
+        return doc;
+    }
+
+    [key: string]: Field | null | undefined;
+
+    @serializable(alias("fields", map(autoObject())))
+    @observable
+    private __fields: { [key: string]: Field | null | undefined } = {};
+
+    private [Update] = (diff: any) => {
+        DocServer.UpdateField(this[Id], diff);
+    }
+
+    private [Self] = this;
+}
+
+export namespace Doc {
+    export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
+        const self = doc[Self];
+        return new Promise(res => getField(self, key, ignoreProto, res));
+    }
+    export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: FieldCtor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
+        const self = doc[Self];
+        return new Promise(async res => {
+            const field = await GetAsync(doc, key, ignoreProto);
+            return Cast(field, ctor);
+        });
+    }
+    export function Get(doc: Doc, key: string, ignoreProto: boolean = false): Field | null | undefined {
+        const self = doc[Self];
+        return getField(self, key, ignoreProto);
+    }
+    export function GetT<T extends Field>(doc: Doc, key: string, ctor: FieldCtor<T>, ignoreProto: boolean = false): Field | null | undefined {
+        return Cast(Get(doc, key, ignoreProto), ctor);
+    }
+    export const Prototype = Symbol("Prototype");
+}
+
+export const GetAsync = Doc.GetAsync;
\ No newline at end of file
diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts
new file mode 100644
index 000000000..f8e54ade5
--- /dev/null
+++ b/src/new_fields/HtmlField.ts
@@ -0,0 +1,14 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, primitive } from "serializr";
+import { ObjectField } from "./Doc";
+
+@Deserializable("html")
+export class URLField extends ObjectField {
+    @serializable(primitive())
+    readonly html: string;
+
+    constructor(html: string) {
+        super();
+        this.html = html;
+    }
+}
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
new file mode 100644
index 000000000..a1a623f83
--- /dev/null
+++ b/src/new_fields/List.ts
@@ -0,0 +1,35 @@
+import { Deserializable, autoObject } from "../client/util/SerializationHelper";
+import { Field, ObjectField, Update, OnUpdate, Self } from "./Doc";
+import { setter, getter } from "./util";
+import { serializable, alias, list } from "serializr";
+import { observable } from "mobx";
+
+@Deserializable("list")
+class ListImpl<T extends Field> extends ObjectField {
+    constructor() {
+        super();
+        const list = new Proxy<this>(this, {
+            set: function (a, b, c, d) { return setter(a, b, c, d); },
+            get: getter,
+            deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); },
+            defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
+        });
+        return list;
+    }
+
+    [key: number]: T | null | undefined;
+
+    @serializable(alias("fields", list(autoObject())))
+    @observable
+    private __fields: (T | null | undefined)[] = [];
+
+    private [Update] = (diff: any) => {
+        console.log(diff);
+        const update = this[OnUpdate];
+        update && update(diff);
+    }
+
+    private [Self] = this;
+}
+export type List<T extends Field> = ListImpl<T> & T[];
+export const List: { new <T extends Field>(): List<T> } = ListImpl as any;
\ No newline at end of file
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
new file mode 100644
index 000000000..3b4b2e452
--- /dev/null
+++ b/src/new_fields/Proxy.ts
@@ -0,0 +1,55 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { RefField, Id, ObjectField } from "./Doc";
+import { primitive, serializable } from "serializr";
+import { observable } from "mobx";
+
+@Deserializable("proxy")
+export class ProxyField<T extends RefField> extends ObjectField {
+    constructor();
+    constructor(value: T);
+    constructor(value?: T) {
+        super();
+        if (value) {
+            this.cache = value;
+            this.fieldId = value[Id];
+        }
+    }
+
+    @serializable(primitive())
+    readonly fieldId: string = "";
+
+    // This getter/setter and nested object thing is 
+    // because mobx doesn't play well with observable proxies
+    @observable.ref
+    private _cache: { readonly field: T | undefined } = { field: undefined };
+    private get cache(): T | undefined {
+        return this._cache.field;
+    }
+    private set cache(field: T | undefined) {
+        this._cache = { field };
+    }
+
+    private failed = false;
+    private promise?: Promise<any>;
+
+    value(callback?: ((field: T | undefined) => void)): T | undefined | null {
+        if (this.cache) {
+            callback && callback(this.cache);
+            return this.cache;
+        }
+        if (this.failed) {
+            return undefined;
+        }
+        if (!this.promise) {
+            // this.promise = Server.GetField(this.fieldId).then(action((field: any) => {
+            //     this.promise = undefined;
+            //     this.cache = field;
+            //     if (field === undefined) this.failed = true;
+            //     return field;
+            // }));
+            this.promise = new Promise(r => r());
+        }
+        callback && this.promise.then(callback);
+        return null;
+    }
+}
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
new file mode 100644
index 000000000..c7d2f0801
--- /dev/null
+++ b/src/new_fields/Schema.ts
@@ -0,0 +1,47 @@
+import { Interface, ToInterface, Cast } from "./Types";
+import { Doc } from "./Doc";
+
+export type makeInterface<T extends Interface> = Partial<ToInterface<T>> & Doc;
+export function makeInterface<T extends Interface>(schema: T): (doc: Doc) => makeInterface<T> {
+    return function (doc: any) {
+        return new Proxy(doc, {
+            get(target, prop) {
+                const field = target[prop];
+                if (prop in schema) {
+                    return Cast(field, (schema as any)[prop]);
+                }
+                return field;
+            }
+        });
+    };
+}
+
+export type makeStrictInterface<T extends Interface> = Partial<ToInterface<T>>;
+export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc) => makeStrictInterface<T> {
+    const proto = {};
+    for (const key in schema) {
+        const type = schema[key];
+        Object.defineProperty(proto, key, {
+            get() {
+                return Cast(this.__doc[key], type as any);
+            },
+            set(value) {
+                value = Cast(value, type as any);
+                if (value !== undefined) {
+                    this.__doc[key] = value;
+                    return;
+                }
+                throw new TypeError("Expected type " + type);
+            }
+        });
+    }
+    return function (doc: any) {
+        const obj = Object.create(proto);
+        obj.__doc = doc;
+        return obj;
+    };
+}
+
+export function createSchema<T extends Interface>(schema: T): T {
+    return schema;
+}
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
new file mode 100644
index 000000000..416298a64
--- /dev/null
+++ b/src/new_fields/Types.ts
@@ -0,0 +1,58 @@
+import { Field, Opt } from "./Doc";
+import { List } from "./List";
+
+export type ToType<T> =
+    T extends "string" ? string :
+    T extends "number" ? number :
+    T extends "boolean" ? boolean :
+    T extends ListSpec<infer U> ? List<U> :
+    T extends { new(...args: any[]): infer R } ? R : never;
+
+export type ToConstructor<T> =
+    T extends string ? "string" :
+    T extends number ? "number" :
+    T extends boolean ? "boolean" : { new(...args: any[]): T };
+
+export type ToInterface<T> = {
+    [P in keyof T]: ToType<T[P]>;
+};
+
+// type ListSpec<T extends Field[]> = { List: FieldCtor<Head<T>> | ListSpec<Tail<T>> };
+export type ListSpec<T> = { List: FieldCtor<T> };
+
+// type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
+
+export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
+export type Tail<T extends any[]> =
+    ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : [];
+export type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
+
+export interface Interface {
+    [key: string]: ToConstructor<Field> | ListSpec<Field>;
+    // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
+}
+
+export type FieldCtor<T extends Field> = ToConstructor<T> | ListSpec<T>;
+
+export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T): ToType<T> | null | undefined {
+    if (field !== undefined && field !== null) {
+        if (typeof ctor === "string") {
+            if (typeof field === ctor) {
+                return field as ToType<T>;
+            }
+        } else if (typeof ctor === "object") {
+            if (field instanceof List) {
+                return field as ToType<T>;
+            }
+        } else if (field instanceof (ctor as any)) {
+            return field as ToType<T>;
+        }
+    } else {
+        return field;
+    }
+    return undefined;
+}
+
+export function FieldValue<T extends Field>(field: Opt<Field> | Promise<Opt<Field>>): Opt<Field> {
+    return field instanceof Promise ? undefined : field;
+}
diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts
new file mode 100644
index 000000000..d27a2b692
--- /dev/null
+++ b/src/new_fields/URLField.ts
@@ -0,0 +1,25 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable } from "serializr";
+import { ObjectField } from "./Doc";
+
+function url() {
+    return {
+        serializer: function (value: URL) {
+            return value.href;
+        },
+        deserializer: function (jsonValue: string, done: (err: any, val: any) => void) {
+            done(undefined, new URL(jsonValue));
+        }
+    };
+}
+
+@Deserializable("url")
+export class URLField extends ObjectField {
+    @serializable(url())
+    readonly url: URL;
+
+    constructor(url: URL) {
+        super();
+        this.url = url;
+    }
+}
\ No newline at end of file
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
new file mode 100644
index 000000000..0f08ecf03
--- /dev/null
+++ b/src/new_fields/util.ts
@@ -0,0 +1,73 @@
+import { UndoManager } from "../client/util/UndoManager";
+import { Update, OnUpdate, Parent, ObjectField, RefField, Doc, Id, Field } from "./Doc";
+import { SerializationHelper } from "../client/util/SerializationHelper";
+import { ProxyField } from "./Proxy";
+
+export function setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+    if (SerializationHelper.IsSerializing()) {
+        target[prop] = value;
+        return true;
+    }
+    if (typeof prop === "symbol") {
+        target[prop] = value;
+        return true;
+    }
+    const curValue = target.__fields[prop];
+    if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
+        // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
+        // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way
+        return true;
+    }
+    if (value instanceof RefField) {
+        value = new ProxyField(value);
+    }
+    if (value instanceof ObjectField) {
+        if (value[Parent] && value[Parent] !== target) {
+            throw new Error("Can't put the same object in multiple documents at the same time");
+        }
+        value[Parent] = target;
+        value[OnUpdate] = (diff?: any) => {
+            if (!diff) diff = SerializationHelper.Serialize(value);
+            target[Update]({ [prop]: diff });
+        };
+    }
+    if (curValue instanceof ObjectField) {
+        delete curValue[Parent];
+        delete curValue[OnUpdate];
+    }
+    target.__fields[prop] = value;
+    target[Update]({ ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) });
+    UndoManager.AddEvent({
+        redo: () => receiver[prop] = value,
+        undo: () => receiver[prop] = curValue
+    });
+    return true;
+}
+
+export function getter(target: any, prop: string | symbol | number, receiver: any): any {
+    if (typeof prop === "symbol") {
+        return target.__fields[prop] || target[prop];
+    }
+    if (SerializationHelper.IsSerializing()) {
+        return target[prop];
+    }
+    return getField(target, prop, receiver);
+}
+
+export function getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
+    const field = target.__fields[prop];
+    if (field instanceof ProxyField) {
+        return field.value(callback);
+    }
+    if (field === undefined && !ignoreProto) {
+        const proto = getField(target, "prototype", true);
+        if (proto instanceof Doc) {
+            let field = proto[prop];
+            callback && callback(field === null ? undefined : field);
+            return field;
+        }
+    }
+    callback && callback(field);
+    return field;
+
+}
-- 
cgit v1.2.3-70-g09d2


From 1eb965a5d9c8aaebf1970bc645edecfb7017b601 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sat, 20 Apr 2019 03:29:35 -0400
Subject: Made the switch in a couple more classes

---
 src/Utils.ts                                    |  3 --
 src/client/views/nodes/DocumentContentsView.tsx | 26 ++------------
 src/client/views/nodes/DocumentView.tsx         | 16 +++++++--
 src/client/views/nodes/FieldView.tsx            | 45 ++++++++++---------------
 src/client/views/nodes/ImageBox.tsx             | 41 +++++++++++++---------
 src/debug/Test.tsx                              |  4 +--
 src/new_fields/Doc.ts                           |  5 +--
 src/new_fields/HtmlField.ts                     |  2 +-
 src/new_fields/Schema.ts                        | 24 +++++++++++--
 src/new_fields/Types.ts                         | 19 +++++++----
 10 files changed, 98 insertions(+), 87 deletions(-)

(limited to 'src/debug/Test.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 98f75d3b9..066d653f5 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -2,7 +2,6 @@ import v4 = require('uuid/v4');
 import v5 = require("uuid/v5");
 import { Socket } from 'socket.io';
 import { Message } from './server/Message';
-import { Document } from './fields/Document';
 
 export class Utils {
 
@@ -108,6 +107,4 @@ export function returnZero() { return 0; }
 
 export function emptyFunction() { }
 
-export function emptyDocFunction(doc: Document) { }
-
 export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 07599c345..273401111 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,9 +1,5 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { FieldWaiting, Field } from "../../../fields/Field";
-import { Key } from "../../../fields/Key";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
@@ -22,46 +18,30 @@ import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
 import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import React = require("react");
-import { Document } from "../../../fields/Document";
 import { FieldViewProps } from "./FieldView";
 import { Without, OmitKeys } from "../../../Utils";
+import { Cast } from "../../../new_fields/Types";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
 export interface JsxBindings {
     props: BindingProps;
-    [keyName: string]: BindingProps | Field;
 }
 
 @observer
 export class DocumentContentsView extends React.Component<DocumentViewProps & {
     isSelected: () => boolean,
     select: (ctrl: boolean) => void,
-    layoutKey: Key
+    layoutKey: string
 }> {
-    @computed get layout(): string { return this.props.Document.GetText(this.props.layoutKey, "<p>Error loading layout data</p>"); }
-    @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
-    @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
-
+    @computed get layout(): string { return Cast(this.props.Document[this.props.layoutKey], "string", "<p>Error loading layout data</p>"); }
 
     CreateBindings(): JsxBindings {
         let bindings: JsxBindings = { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive) };
-
-        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
-        }
-        for (const key of this.layoutFields) {
-            let field = this.props.Document.Get(key);
-            bindings[key.Name] = field && field !== FieldWaiting ? field.GetValue() : field;
-        }
         return bindings;
     }
 
     render() {
-        let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
-        if (!lkeys || lkeys === FieldWaiting) {
-            return <p>Error loading layout keys</p>;
-        }
         return <JsxParser
             components={{ FormattedTextBox, ImageBox, IconBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c06fe4c8d..f1a12e6db 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -46,8 +46,17 @@ const schema = createSchema({
     backgroundColor: "string"
 });
 
-type Document = makeInterface<typeof schema>;
-const Document = makeInterface(schema);
+export const positionSchema = createSchema({
+    nativeWidth: "number",
+    nativeHeight: "number",
+    width: "number",
+    height: "number",
+    x: "number",
+    y: "number",
+});
+
+type Document = makeInterface<[typeof schema]>;
+const Document = makeInterface([schema]);
 
 @observer
 export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
@@ -146,7 +155,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         e.stopPropagation();
-        if (!SelectionManager.IsSelected(this) && e.button !== 2)
+        if (!SelectionManager.IsSelected(this) && e.button !== 2) {
             if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
                 if (this.props.Document.Get(KeyStore.MaximizedDoc) instanceof Document) {
                     this.props.Document.GetAsync(KeyStore.MaximizedDoc, maxdoc => {
@@ -159,6 +168,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     SelectionManager.SelectDoc(this, e.ctrlKey);
                 }
             }
+        }
     }
     stopPropagation = (e: React.SyntheticEvent) => {
         e.stopPropagation();
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 93e385821..d8de5afec 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,30 +1,21 @@
 import React = require("react");
 import { observer } from "mobx-react";
 import { computed } from "mobx";
-import { Field, FieldWaiting, FieldValue, Opt } from "../../../fields/Field";
-import { Document } from "../../../fields/Document";
-import { TextField } from "../../../fields/TextField";
-import { NumberField } from "../../../fields/NumberField";
-import { RichTextField } from "../../../fields/RichTextField";
-import { ImageField } from "../../../fields/ImageField";
-import { VideoField } from "../../../fields/VideoField";
-import { Key } from "../../../fields/Key";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { ImageBox } from "./ImageBox";
 import { WebBox } from "./WebBox";
 import { VideoBox } from "./VideoBox";
 import { AudioBox } from "./AudioBox";
-import { AudioField } from "../../../fields/AudioField";
-import { ListField } from "../../../fields/ListField";
 import { DocumentContentsView } from "./DocumentContentsView";
 import { Transform } from "../../util/Transform";
-import { KeyStore } from "../../../fields/KeyStore";
-import { returnFalse, emptyDocFunction, emptyFunction, returnOne } from "../../../Utils";
+import { returnFalse, emptyFunction, returnOne } from "../../../Utils";
 import { CollectionView } from "../collections/CollectionView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { IconField } from "../../../fields/IconFIeld";
 import { IconBox } from "./IconBox";
+import { Opt, Doc, Field, FieldValue, FieldWaiting } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
 
 
 //
@@ -33,9 +24,9 @@ import { IconBox } from "./IconBox";
 // See the LayoutString method on each field view :   ImageBox, FormattedTextBox, etc. 
 //
 export interface FieldViewProps {
-    fieldKey: Key;
+    fieldKey: string;
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
-    Document: Document;
+    Document: Doc;
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
     isTopMost: boolean;
@@ -56,17 +47,17 @@ export class FieldView extends React.Component<FieldViewProps> {
     }
 
     @computed
-    get field(): FieldValue<Field> {
-        const { Document: doc, fieldKey } = this.props;
-        return doc.Get(fieldKey);
+    get field(): FieldValue {
+        const { Document, fieldKey } = this.props;
+        return Document[fieldKey];
     }
     render() {
         const field = this.field;
         if (!field) {
             return <p>{'<null>'}</p>;
         }
-        if (field instanceof TextField) {
-            return <p>{field.Data}</p>;
+        if (typeof field === "string") {
+            return <p>{field}</p>;
         }
         else if (field instanceof RichTextField) {
             return <FormattedTextBox {...this.props} />;
@@ -83,7 +74,7 @@ export class FieldView extends React.Component<FieldViewProps> {
         else if (field instanceof AudioField) {
             return <AudioBox {...this.props} />;
         }
-        else if (field instanceof Document) {
+        else if (field instanceof Doc) {
             return (
                 <DocumentContentsView Document={field}
                     addDocument={undefined}
@@ -94,29 +85,29 @@ export class FieldView extends React.Component<FieldViewProps> {
                     PanelHeight={() => 100}
                     isTopMost={true} //TODO Why is this top most?
                     selectOnLoad={false}
-                    focus={emptyDocFunction}
+                    focus={emptyFunction}
                     isSelected={returnFalse}
                     select={returnFalse}
-                    layoutKey={KeyStore.Layout}
+                    layoutKey={"layout"}
                     ContainingCollectionView={this.props.ContainingCollectionView}
                     parentActive={this.props.active}
                     whenActiveChanged={this.props.whenActiveChanged} />
             );
         }
-        else if (field instanceof ListField) {
+        else if (field instanceof List) {
             return (<div>
-                {(field as ListField<Field>).Data.map(f => f instanceof Document ? f.Title : f.GetValue().toString()).join(", ")}
+                {field.map(f => f instanceof Doc ? f.title : f.toString()).join(", ")}
             </div>);
         }
         // bcz: this belongs here, but it doesn't render well so taking it out for now
         // else if (field instanceof HtmlField) {
         //     return <WebBox {...this.props} />
         // }
-        else if (field instanceof NumberField) {
-            return <p>{field.Data}</p>;
+        else if (typeof field === "number") {
+            return <p>{field}</p>;
         }
         else if (field !== FieldWaiting) {
-            return <p>{JSON.stringify(field.GetValue())}</p>;
+            return <p>{JSON.stringify(field)}</p>;
         }
         else {
             return <p> {"Waiting for server..."} </p>;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index fd5381b77..3b72e9f39 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -3,11 +3,6 @@ import { action, observable } from 'mobx';
 import { observer } from "mobx-react";
 import Lightbox from 'react-image-lightbox';
 import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { Document } from '../../../fields/Document';
-import { FieldWaiting } from '../../../fields/Field';
-import { ImageField } from '../../../fields/ImageField';
-import { KeyStore } from '../../../fields/KeyStore';
-import { ListField } from '../../../fields/ListField';
 import { Utils } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
@@ -15,9 +10,23 @@ import { ContextMenu } from "../../views/ContextMenu";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
+import { createSchema, makeInterface } from '../../../new_fields/Schema';
+import { DocComponent } from '../DocComponent';
+import { positionSchema } from './DocumentView';
+import { FieldValue, Cast } from '../../../new_fields/Types';
+import { URLField } from '../../../new_fields/URLField';
+import { Doc, FieldWaiting } from '../../../new_fields/Doc';
+import { List } from '../../../new_fields/List';
+
+const schema = createSchema({
+    curPage: "number"
+});
+
+type ImageDocument = makeInterface<[typeof schema, typeof positionSchema]>;
+const ImageDocument = makeInterface([schema, positionSchema]);
 
 @observer
-export class ImageBox extends React.Component<FieldViewProps> {
+export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) {
 
     public static LayoutString() { return FieldView.LayoutString(ImageBox); }
     private _imgRef: React.RefObject<HTMLImageElement>;
@@ -39,8 +48,8 @@ export class ImageBox extends React.Component<FieldViewProps> {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
         if (this._photoIndex === 0) {
-            this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
-            this.props.Document.SetNumber(KeyStore.Height, this.props.Document.Width() * h / w);
+            this.Document.nativeHeight = FieldValue(this.Document.nativeWidth, 0) * h / w;
+            this.Document.height = FieldValue(this.Document.width, 0) * h / w;
         }
     }
 
@@ -126,9 +135,9 @@ export class ImageBox extends React.Component<FieldViewProps> {
     }
 
     specificContextMenu = (e: React.MouseEvent): void => {
-        let field = this.props.Document.GetT(this.props.fieldKey, ImageField);
+        let field = Cast(this.Document[this.props.fieldKey], URLField);
         if (field && field !== FieldWaiting) {
-            let url = field.Data.href;
+            let url = field.url.href;
             ContextMenu.Instance.addItem({
                 description: "Copy path", event: () => {
                     Utils.CopyText(url);
@@ -140,11 +149,11 @@ export class ImageBox extends React.Component<FieldViewProps> {
     @action
     onDotDown(index: number) {
         this._photoIndex = index;
-        this.props.Document.SetNumber(KeyStore.CurPage, index);
+        this.Document.curPage = index;
     }
 
     dots(paths: string[]) {
-        let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
+        let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
         let dist = Math.min(nativeWidth / paths.length, 40);
         let left = (nativeWidth - paths.length * dist) / 2;
         return paths.map((p, i) =>
@@ -155,12 +164,12 @@ export class ImageBox extends React.Component<FieldViewProps> {
     }
 
     render() {
-        let field = this.props.Document.Get(this.props.fieldKey);
+        let field = this.Document[this.props.fieldKey];
         let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"];
         if (field === FieldWaiting) paths = ["https://image.flaticon.com/icons/svg/66/66163.svg"];
-        else if (field instanceof ImageField) paths = [field.Data.href];
-        else if (field instanceof ListField) paths = field.Data.filter(val => val as ImageField).map(p => (p as ImageField).Data.href);
-        let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
+        else if (field instanceof URLField) paths = [field.url.href];
+        else if (field instanceof List) paths = field.filter(val => val instanceof URLField).map(p => (p as URLField).url.href);
+        let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
         return (
             <div className="imageBox-cont" onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
                 <img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 8b9c9fa0b..81c69ca8e 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -16,8 +16,8 @@ const schema1 = createSchema({
     testDoc: Doc
 });
 
-const TestDoc = makeInterface(schema1);
-type TestDoc = makeInterface<typeof schema1>;
+const TestDoc = makeInterface([schema1]);
+type TestDoc = makeInterface<[typeof schema1]>;
 
 const schema2 = createSchema({
     hello: URLField,
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 23a8c05cc..e0eb44ee9 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -33,6 +33,7 @@ export type Field = number | string | boolean | ObjectField | RefField;
 export type Opt<T> = T | undefined;
 export type FieldWaiting = null;
 export const FieldWaiting: FieldWaiting = null;
+export type FieldValue<T extends Field = Field> = Opt<T> | FieldWaiting;
 
 export const Self = Symbol("Self");
 
@@ -81,8 +82,8 @@ export namespace Doc {
         const self = doc[Self];
         return getField(self, key, ignoreProto);
     }
-    export function GetT<T extends Field>(doc: Doc, key: string, ctor: FieldCtor<T>, ignoreProto: boolean = false): Field | null | undefined {
-        return Cast(Get(doc, key, ignoreProto), ctor);
+    export function GetT<T extends Field>(doc: Doc, key: string, ctor: FieldCtor<T>, ignoreProto: boolean = false): T | null | undefined {
+        return Cast(Get(doc, key, ignoreProto), ctor) as T | null | undefined;
     }
     export function MakeDelegate(doc: Opt<Doc>): Opt<Doc> {
         if (!doc) {
diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts
index f8e54ade5..76fdb1f62 100644
--- a/src/new_fields/HtmlField.ts
+++ b/src/new_fields/HtmlField.ts
@@ -3,7 +3,7 @@ import { serializable, primitive } from "serializr";
 import { ObjectField } from "./Doc";
 
 @Deserializable("html")
-export class URLField extends ObjectField {
+export class HtmlField extends ObjectField {
     @serializable(primitive())
     readonly html: string;
 
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
index 1607d4c15..1d1f56844 100644
--- a/src/new_fields/Schema.ts
+++ b/src/new_fields/Schema.ts
@@ -1,8 +1,26 @@
-import { Interface, ToInterface, Cast, FieldCtor, ToConstructor } from "./Types";
+import { Interface, ToInterface, Cast, FieldCtor, ToConstructor, HasTail, Head, Tail } from "./Types";
 import { Doc } from "./Doc";
 
-export type makeInterface<T extends Interface, U extends Doc = Doc> = Partial<ToInterface<T>> & U;
-export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U> {
+type All<T extends any[], U extends Doc> = {
+    1: makeInterface<Head<T>, U> & All<Tail<T>, U>,
+    0: makeInterface<Head<T>, U>
+}[HasTail<T> extends true ? 1 : 0];
+
+type AllToInterface<T extends any[]> = {
+    1: ToInterface<Head<T>> & AllToInterface<Tail<T>>,
+    0: ToInterface<Head<T>>
+}[HasTail<T> extends true ? 1 : 0];
+
+export type makeInterface<T extends Interface[], U extends Doc = Doc> = Partial<AllToInterface<T>> & U;
+// export function makeInterface<T extends Interface[], U extends Doc>(schemas: T): (doc: U) => All<T, U>;
+// export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U>; 
+export function makeInterface<T extends Interface[], U extends Doc>(schemas: T): (doc: U) => All<T, U> {
+    let schema: Interface = {};
+    for (const s of schemas) {
+        for (const key in s) {
+            schema[key] = s[key];
+        }
+    }
     return function (doc: any) {
         return new Proxy(doc, {
             get(target, prop) {
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index cafb208ce..6ffb3e02f 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -27,14 +27,17 @@ export type Tail<T extends any[]> =
     ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : [];
 export type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
 
+//TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
 export interface Interface {
     [key: string]: ToConstructor<Field> | ListSpec<Field>;
     // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
 }
 
-export type FieldCtor<T extends Field> = ToConstructor<T> | ListSpec<T>;
+export type FieldCtor<T extends Field> = T extends List<infer R> ? ListSpec<R> : ToConstructor<T>;
 
-export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T): ToType<T> | null | undefined {
+export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T): ToType<T> | null | undefined;
+export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T, defaultVal: ToType<T>): ToType<T>;
+export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T, defaultVal?: ToType<T>): ToType<T> | null | undefined {
     if (field !== undefined && field !== null) {
         if (typeof ctor === "string") {
             if (typeof field === ctor) {
@@ -42,17 +45,19 @@ export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined
             }
         } else if (typeof ctor === "object") {
             if (field instanceof List) {
-                return field as ToType<T>;
+                return field as any;
             }
         } else if (field instanceof (ctor as any)) {
             return field as ToType<T>;
         }
     } else {
-        return field;
+        return defaultVal;
     }
-    return undefined;
+    return defaultVal;
 }
 
-export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>): Opt<T> {
-    return field instanceof Promise ? undefined : field;
+export function FieldValue<T extends Field, U extends T>(field: Opt<T> | Promise<Opt<T>>, defaultValue: U): T;
+export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>): Opt<T>;
+export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>, defaultValue?: T): Opt<T> {
+    return field instanceof Promise ? defaultValue : field;
 }
-- 
cgit v1.2.3-70-g09d2


From 393d351420b3a0d28f4cd1ea8b674fa5d04bfcde Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sun, 21 Apr 2019 23:40:48 -0400
Subject: More fixes

---
 .../views/collections/CollectionDockingView.tsx    |  6 ++--
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FieldView.tsx               |  8 ++---
 src/client/views/nodes/ImageBox.tsx                | 15 +++++-----
 src/client/views/nodes/VideoBox.tsx                | 35 ++++++++++++----------
 src/client/views/nodes/WebBox.tsx                  |  4 +--
 src/debug/Test.tsx                                 | 12 ++++----
 src/new_fields/Doc.ts                              |  4 +--
 src/new_fields/Schema.ts                           | 22 +++-----------
 src/new_fields/Types.ts                            | 11 +++----
 src/new_fields/URLField.ts                         |  9 ++++--
 12 files changed, 62 insertions(+), 68 deletions(-)

(limited to 'src/debug/Test.tsx')

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 7b204cbdc..86ee7cea9 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -16,7 +16,7 @@ import { ServerUtils } from "../../../server/ServerUtil";
 import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
 import { Transform } from '../../util/Transform';
 import { Doc, Id, Opt, Field, FieldId } from "../../../new_fields/Doc";
-import { Cast, FieldValue } from "../../../new_fields/Types";
+import { Cast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { DocServer } from "../../DocServer";
 
@@ -336,8 +336,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
 
     ScreenToLocalTransform = () => {
         if (this._mainCont.current && this._mainCont.current.children) {
-            let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!.children[0].firstChild as HTMLElement);
-            scale = Utils.GetScreenTransform(this._mainCont.current!).scale;
+            let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement);
+            scale = Utils.GetScreenTransform(this._mainCont.current).scale;
             return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this.contentScaling());
         }
         return Transform.Identity();
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 27fd25b5c..c6eea5623 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -18,7 +18,7 @@ const schema = createSchema({
 });
 
 type FreeformDocument = makeInterface<[typeof schema, typeof positionSchema]>;
-const FreeformDocument = makeInterface([schema, positionSchema]);
+const FreeformDocument = makeInterface(schema, positionSchema);
 
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f1a12e6db..c0afc192e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -56,7 +56,7 @@ export const positionSchema = createSchema({
 });
 
 type Document = makeInterface<[typeof schema]>;
-const Document = makeInterface([schema]);
+const Document = makeInterface(schema);
 
 @observer
 export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 845213366..b11a87d75 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -3,19 +3,19 @@ import { observer } from "mobx-react";
 import { computed } from "mobx";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { ImageBox } from "./ImageBox";
-import { WebBox } from "./WebBox";
 import { VideoBox } from "./VideoBox";
 import { AudioBox } from "./AudioBox";
 import { DocumentContentsView } from "./DocumentContentsView";
 import { Transform } from "../../util/Transform";
-import { returnFalse, emptyFunction, returnOne } from "../../../Utils";
+import { returnFalse, emptyFunction } from "../../../Utils";
 import { CollectionView } from "../collections/CollectionView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { IconField } from "../../../fields/IconFIeld";
 import { IconBox } from "./IconBox";
-import { Opt, Doc, Field, FieldValue, FieldWaiting } from "../../../new_fields/Doc";
+import { Opt, Doc, FieldResult } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
+import { ImageField, VideoField, AudioField } from "../../../new_fields/URLField";
 
 
 //
@@ -47,7 +47,7 @@ export class FieldView extends React.Component<FieldViewProps> {
     }
 
     @computed
-    get field(): FieldValue {
+    get field(): FieldResult {
         const { Document, fieldKey } = this.props;
         return Document[fieldKey];
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 3b72e9f39..c11aee56e 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -14,16 +14,15 @@ import { createSchema, makeInterface } from '../../../new_fields/Schema';
 import { DocComponent } from '../DocComponent';
 import { positionSchema } from './DocumentView';
 import { FieldValue, Cast } from '../../../new_fields/Types';
-import { URLField } from '../../../new_fields/URLField';
-import { Doc, FieldWaiting } from '../../../new_fields/Doc';
+import { ImageField } from '../../../new_fields/URLField';
 import { List } from '../../../new_fields/List';
 
-const schema = createSchema({
+export const pageSchema = createSchema({
     curPage: "number"
 });
 
-type ImageDocument = makeInterface<[typeof schema, typeof positionSchema]>;
-const ImageDocument = makeInterface([schema, positionSchema]);
+type ImageDocument = makeInterface<[typeof pageSchema, typeof positionSchema]>;
+const ImageDocument = makeInterface(pageSchema, positionSchema);
 
 @observer
 export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) {
@@ -135,7 +134,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     }
 
     specificContextMenu = (e: React.MouseEvent): void => {
-        let field = Cast(this.Document[this.props.fieldKey], URLField);
+        let field = Cast(this.Document[this.props.fieldKey], ImageField);
         if (field && field !== FieldWaiting) {
             let url = field.url.href;
             ContextMenu.Instance.addItem({
@@ -167,8 +166,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         let field = this.Document[this.props.fieldKey];
         let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"];
         if (field === FieldWaiting) paths = ["https://image.flaticon.com/icons/svg/66/66163.svg"];
-        else if (field instanceof URLField) paths = [field.url.href];
-        else if (field instanceof List) paths = field.filter(val => val instanceof URLField).map(p => (p as URLField).url.href);
+        else if (field instanceof ImageField) paths = [field.url.href];
+        else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => (p as ImageField).url.href);
         let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
         return (
             <div className="imageBox-cont" onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 9d7c2bc56..7b922b620 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,18 +1,22 @@
 import React = require("react");
 import { observer } from "mobx-react";
-import { FieldWaiting, Opt } from '../../../fields/Field';
-import { VideoField } from '../../../fields/VideoField';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./VideoBox.scss";
 import Measure from "react-measure";
-import { action, trace, observable, IReactionDisposer, computed, reaction } from "mobx";
-import { KeyStore } from "../../../fields/KeyStore";
-import { number } from "prop-types";
+import { action, computed } from "mobx";
+import { DocComponent } from "../DocComponent";
+import { positionSchema } from "./DocumentView";
+import { makeInterface } from "../../../new_fields/Schema";
+import { pageSchema } from "./ImageBox";
+import { Cast, FieldValue } from "../../../new_fields/Types";
+import { VideoField } from "../../../new_fields/URLField";
+
+type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
+const VideoDocument = makeInterface(positionSchema, pageSchema);
 
 @observer
-export class VideoBox extends React.Component<FieldViewProps> {
+export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
 
-    private _reactionDisposer: Opt<IReactionDisposer>;
     private _videoRef = React.createRef<HTMLVideoElement>();
     public static LayoutString() { return FieldView.LayoutString(VideoBox); }
 
@@ -20,7 +24,7 @@ export class VideoBox extends React.Component<FieldViewProps> {
         super(props);
     }
 
-    @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); }
+    @computed private get curPage() { return FieldValue(this.Document.curPage, -1); }
 
 
     _loaded: boolean = false;
@@ -31,12 +35,12 @@ export class VideoBox extends React.Component<FieldViewProps> {
             // bcz: the nativeHeight should really be set when the document is imported.
             //      also, the native dimensions could be different for different pages of the PDF
             //      so this design is flawed.
-            var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
-            var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+            var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
+            var nativeHeight = FieldValue(this.Document.nativeHeight, 0);
             var newNativeHeight = nativeWidth * r.entry.height / r.entry.width;
             if (!nativeHeight && newNativeHeight !== nativeHeight && !isNaN(newNativeHeight)) {
-                this.props.Document.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.Document.GetNumber(KeyStore.Width, 0));
-                this.props.Document.SetNumber(KeyStore.NativeHeight, newNativeHeight);
+                this.Document.height = newNativeHeight / nativeWidth * FieldValue(this.Document.width, 0);
+                this.Document.nativeHeight = newNativeHeight;
             }
         } else {
             this._loaded = true;
@@ -56,12 +60,11 @@ export class VideoBox extends React.Component<FieldViewProps> {
     }
 
     render() {
-        let field = this.props.Document.GetT(this.props.fieldKey, VideoField);
-        if (!field || field === FieldWaiting) {
+        let field = FieldValue(Cast(this.Document[this.props.fieldKey], VideoField));
+        if (!field) {
             return <div>Loading</div>;
         }
-        let path = field.Data.href;
-        trace();
+        let path = field.url.href;
         return (
             <Measure onResize={this.setScaling}>
                 {({ measureRef }) =>
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index e3b6e1c05..63778fa50 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -4,7 +4,7 @@ import { FieldViewProps, FieldView } from './FieldView';
 import { observer } from "mobx-react";
 import { computed } from 'mobx';
 import { HtmlField } from "../../../new_fields/HtmlField";
-import { URLField } from "../../../new_fields/URLField";
+import { WebField } from "../../../new_fields/URLField";
 
 @observer
 export class WebBox extends React.Component<FieldViewProps> {
@@ -37,7 +37,7 @@ export class WebBox extends React.Component<FieldViewProps> {
         let view;
         if (field instanceof HtmlField) {
             view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />
-        } else if (field instanceof URLField) {
+        } else if (field instanceof WebField) {
             view = <iframe src={field.url.href} style={{ position: "absolute", width: "100%", height: "100%" }} />
         } else {
             view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%" }} />
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 81c69ca8e..22a39f31e 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { SerializationHelper } from '../client/util/SerializationHelper';
 import { createSchema, makeInterface, makeStrictInterface } from '../new_fields/Schema';
-import { URLField } from '../new_fields/URLField';
+import { ImageField } from '../new_fields/URLField';
 import { Doc } from '../new_fields/Doc';
 import { ListSpec } from '../new_fields/Types';
 import { List } from '../new_fields/List';
@@ -12,19 +12,19 @@ const schema1 = createSchema({
     hello: "number",
     test: "string",
     fields: "boolean",
-    url: URLField,
+    url: ImageField,
     testDoc: Doc
 });
 
-const TestDoc = makeInterface([schema1]);
+const TestDoc = makeInterface(schema1);
 type TestDoc = makeInterface<[typeof schema1]>;
 
 const schema2 = createSchema({
-    hello: URLField,
+    hello: ImageField,
     test: "boolean",
     fields: { List: "number" } as ListSpec<number>,
     url: "number",
-    testDoc: URLField
+    testDoc: ImageField
 });
 
 const Test2Doc = makeStrictInterface(schema2);
@@ -43,7 +43,7 @@ const assert = (bool: boolean) => {
 
 class Test extends React.Component {
     onClick = () => {
-        const url = new URLField(new URL("http://google.com"));
+        const url = new ImageField(new URL("http://google.com"));
         const doc = new Doc();
         const doc2 = new Doc();
         doc.hello = 5;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 10e8fe7ec..5d18cbb2e 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -33,7 +33,7 @@ export class ObjectField {
 export type Field = number | string | boolean | ObjectField | RefField;
 export type Opt<T> = T | undefined;
 export type FieldWaiting<T extends Field = Field> = Promise<T | undefined>;
-export type FieldValue<T extends Field = Field> = Opt<T> | FieldWaiting<T>;
+export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<T>;
 
 export const Self = Symbol("Self");
 
@@ -77,7 +77,7 @@ export namespace Doc {
             return Cast(field, ctor);
         });
     }
-    export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldValue {
+    export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult {
         const self = doc[Self];
         return getField(self, key, ignoreProto);
     }
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
index 696a5d2a8..3b7078cb0 100644
--- a/src/new_fields/Schema.ts
+++ b/src/new_fields/Schema.ts
@@ -1,13 +1,7 @@
 import { Interface, ToInterface, Cast, FieldCtor, ToConstructor, HasTail, Head, Tail } from "./Types";
 import { Doc, Field, ObjectField } from "./Doc";
-import { URLField } from "./URLField";
 
-type All<T extends Interface[], U extends Doc> = {
-    1: makeInterface<[Head<T>], U> & All<Tail<T>, U>,
-    0: makeInterface<[Head<T>], U>
-}[HasTail<T> extends true ? 1 : 0];
-
-type AllToInterface<T extends any[]> = {
+type AllToInterface<T extends Interface[]> = {
     1: ToInterface<Head<T>> & AllToInterface<Tail<T>>,
     0: ToInterface<Head<T>>
 }[HasTail<T> extends true ? 1 : 0];
@@ -16,7 +10,6 @@ export const emptySchema = createSchema({});
 export const Document = makeInterface(emptySchema);
 export type Document = makeInterface<[typeof emptySchema]>;
 
-const DocSymbol = Symbol("Doc");
 export type makeInterface<T extends Interface[], U extends Doc = Doc> = Partial<AllToInterface<T>> & U;
 // export function makeInterface<T extends Interface[], U extends Doc>(schemas: T): (doc: U) => All<T, U>;
 // export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U>; 
@@ -29,26 +22,19 @@ export function makeInterface<T extends Interface[], U extends Doc>(...schemas:
     }
     const proto = new Proxy({}, {
         get(target: any, prop) {
-            if (prop === DocSymbol) {
-                return target[prop];
-            }
-            const field = target[DocSymbol][prop];
+            const field = target.doc[prop];
             if (prop in schema) {
                 return Cast(field, (schema as any)[prop]);
             }
             return field;
         },
         set(target: any, prop, value) {
-            if (prop === DocSymbol) {
-                target[prop] = value;
-            }
-            target[DocSymbol][prop] = value;
+            target.doc[prop] = value;
             return true;
         }
     });
     return function (doc: any) {
-        const obj = Object.create(proto);
-        obj.__doc = doc;
+        const obj = Object.create(proto, { doc: { value: doc, writable: false } });
         return obj;
     };
 }
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index e4d15e276..f4f66fe5c 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -1,4 +1,4 @@
-import { Field, Opt, FieldWaiting, FieldValue } from "./Doc";
+import { Field, Opt, FieldWaiting, FieldResult } from "./Doc";
 import { List } from "./List";
 
 export type ToType<T> =
@@ -6,7 +6,8 @@ export type ToType<T> =
     T extends "number" ? number :
     T extends "boolean" ? boolean :
     T extends ListSpec<infer U> ? List<U> :
-    T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+    // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+    T extends { new(...args: any[]): infer R } ? R : never;
 
 export type ToConstructor<T> =
     T extends string ? "string" :
@@ -35,9 +36,9 @@ export interface Interface {
 
 export type FieldCtor<T extends Field> = T extends List<infer R> ? ListSpec<R> : ToConstructor<T>;
 
-export function Cast<T extends FieldCtor<Field>>(field: Field | FieldWaiting | undefined, ctor: T): FieldValue<ToType<T>>;
+export function Cast<T extends FieldCtor<Field>>(field: Field | FieldWaiting | undefined, ctor: T): FieldResult<ToType<T>>;
 export function Cast<T extends FieldCtor<Field>>(field: Field | FieldWaiting | undefined, ctor: T, defaultVal: ToType<T>): ToType<T>;
-export function Cast<T extends FieldCtor<Field>>(field: Field | FieldWaiting | undefined, ctor: T, defaultVal?: ToType<T>): FieldValue<ToType<T>> | undefined {
+export function Cast<T extends FieldCtor<Field>>(field: Field | FieldWaiting | undefined, ctor: T, defaultVal?: ToType<T>): FieldResult<ToType<T>> | undefined {
     if (field instanceof Promise) {
         return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) : defaultVal;
     }
@@ -66,6 +67,6 @@ export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>, def
 export interface PromiseLike<T> {
     then(callback: (field: Opt<T> | PromiseLike<T>) => void): void;
 }
-export function PromiseValue<T extends Field>(field: FieldValue<T>): PromiseLike<Opt<T>> {
+export function PromiseValue<T extends Field>(field: FieldResult<T>): PromiseLike<Opt<T>> {
     return field instanceof Promise ? field : { then(cb: ((field: Opt<T>) => void)) { return cb(field); } };
 }
\ No newline at end of file
diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts
index e456a7d16..1da245e73 100644
--- a/src/new_fields/URLField.ts
+++ b/src/new_fields/URLField.ts
@@ -13,7 +13,6 @@ function url() {
     );
 }
 
-@Deserializable("url")
 export class URLField extends ObjectField {
     @serializable(url())
     readonly url: URL;
@@ -22,4 +21,10 @@ export class URLField extends ObjectField {
         super();
         this.url = url;
     }
-}
\ No newline at end of file
+}
+
+@Deserializable("audio") export class AudioField extends URLField { }
+@Deserializable("image") export class ImageField extends URLField { }
+@Deserializable("video") export class VideoField extends URLField { }
+@Deserializable("pdf") export class PdfField extends URLField { }
+@Deserializable("web") export class WebField extends URLField { }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 5a9e437bbd175b36a161e1d96c8ae873dfe6d105 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 22 Apr 2019 03:39:39 -0400
Subject: More

---
 src/client/views/collections/CollectionSubView.tsx |  6 +++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 43 +++++++++++-----------
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 src/debug/Test.tsx                                 | 13 +------
 src/new_fields/Doc.ts                              | 13 +++++--
 src/new_fields/List.ts                             |  5 ++-
 src/new_fields/Schema.ts                           | 20 ++++++++--
 src/new_fields/Types.ts                            | 27 ++++++++------
 8 files changed, 74 insertions(+), 55 deletions(-)

(limited to 'src/debug/Test.tsx')

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 3add288fd..1a839fdcb 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -20,6 +20,8 @@ import { CollectionPDFView } from "./CollectionPDFView";
 import { CollectionVideoView } from "./CollectionVideoView";
 import { Doc } from "../../../new_fields/Doc";
 import { DocComponent } from "../DocComponent";
+import { listSpec } from "../../../new_fields/Schema";
+import { Cast } from "../../../new_fields/Types";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -50,6 +52,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             this.createDropTarget(ele);
         }
 
+        get children() {
+            return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc));
+        }
+
         @action
         protected setCursorPosition(position: [number, number]) {
             let ind;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 32104b49d..ee7d65d23 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -20,17 +20,19 @@ import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
-import { Doc } from "../../../../new_fields/Doc";
-import { FieldValue } from "../../../../new_fields/Types";
+import { Doc, Id } from "../../../../new_fields/Doc";
+import { FieldValue, Cast } from "../../../../new_fields/Types";
+import { pageSchema } from "../../nodes/ImageBox";
+import { List } from "../../../../new_fields/List";
 
 export const panZoomSchema = createSchema({
     panX: "number",
     panY: "number",
-    zoom: "number"
+    scale: "number"
 });
 
-type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema]>;
-const PanZoomDocument: (doc: Doc) => PanZoomDocument = makeInterface(panZoomSchema, positionSchema);
+type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>;
+const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema);
 
 @observer
 export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@@ -41,24 +43,24 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private get _pheight() { return this.props.PanelHeight(); }
 
     @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); }
-    @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
+    @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
-    private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
+    private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
     private childViews = () => this.views;
-    private panX = () => this.props.Document.GetNumber(KeyStore.PanX, 0);
-    private panY = () => this.props.Document.GetNumber(KeyStore.PanY, 0);
-    private zoomScaling = () => this.props.Document.GetNumber(KeyStore.Scale, 1);
+    private panX = () => FieldValue(this.Document.panX, 0);
+    private panY = () => FieldValue(this.Document.panY, 0);
+    private zoomScaling = () => FieldValue(this.Document.scale, 1);
     private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 : 0;  // shift so pan position is at center of window for non-overlay collections
     private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 : 0;// shift so pan position is at center of window for non-overlay collections
     private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
     private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
-    private addLiveTextBox = (newBox: Document) => {
-        this._selectOnLoaded = newBox.Id;// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+    private addLiveTextBox = (newBox: Doc) => {
+        this._selectOnLoaded = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
         this.addDocument(newBox, false);
     }
-    private addDocument = (newBox: Document, allowDuplicates: boolean) => {
-        newBox.SetNumber(KeyStore.Zoom, this.props.Document.GetNumber(KeyStore.Scale, 1));
+    private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
+        newBox.zoom = FieldValue(this.Document.scale, 1);
         return this.props.addDocument(this.bringToFront(newBox), false);
     }
     private selectDocuments = (docs: Document[]) => {
@@ -67,14 +69,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             SelectionManager.SelectDoc(dv!, true));
     }
     public getActiveDocuments = () => {
-        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
-        return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).reduce((active, doc) => {
-            var page = doc.GetNumber(KeyStore.Page, -1);
-            if (page === curPage || page === -1) {
-                active.push(doc);
-            }
-            return active;
-        }, [] as Document[]);
+        const curPage = FieldValue(this.Document.curPage, -1);
+        return FieldValue(this.children, [] as Doc[]).filter(doc => {
+            var page = Cast(doc.page, "number", -1);
+            return page === curPage || page === -1;
+        });
     }
 
     @undoBatch
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c11aee56e..cd003ba71 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -71,7 +71,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     @undoBatch
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.DocumentDragData) {
-            de.data.droppedDocuments.map(action((drop: Document) => {
+            de.data.droppedDocuments.forEach(action((drop: Document) => {
                 let layout = drop.GetText(KeyStore.BackgroundLayout, "");
                 if (layout.indexOf(ImageBox.name) !== -1) {
                     let imgData = this.props.Document.Get(KeyStore.Data);
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 22a39f31e..179f987e0 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,12 +1,10 @@
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { SerializationHelper } from '../client/util/SerializationHelper';
-import { createSchema, makeInterface, makeStrictInterface } from '../new_fields/Schema';
+import { createSchema, makeInterface, makeStrictInterface, listSpec } from '../new_fields/Schema';
 import { ImageField } from '../new_fields/URLField';
 import { Doc } from '../new_fields/Doc';
-import { ListSpec } from '../new_fields/Types';
 import { List } from '../new_fields/List';
-const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 const schema1 = createSchema({
     hello: "number",
@@ -22,7 +20,7 @@ type TestDoc = makeInterface<[typeof schema1]>;
 const schema2 = createSchema({
     hello: ImageField,
     test: "boolean",
-    fields: { List: "number" } as ListSpec<number>,
+    fields: listSpec("number"),
     url: "number",
     testDoc: ImageField
 });
@@ -30,13 +28,6 @@ const schema2 = createSchema({
 const Test2Doc = makeStrictInterface(schema2);
 type Test2Doc = makeStrictInterface<typeof schema2>;
 
-const schema3 = createSchema({
-    test: "boolean",
-});
-
-const Test3Doc = makeStrictInterface(schema3);
-type Test3Doc = makeStrictInterface<typeof schema3>;
-
 const assert = (bool: boolean) => {
     if (!bool) throw new Error();
 };
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 5d18cbb2e..60abccce6 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -4,7 +4,7 @@ import { autoObject, SerializationHelper, Deserializable } from "../client/util/
 import { Utils } from "../Utils";
 import { DocServer } from "../client/DocServer";
 import { setter, getter, getField } from "./util";
-import { Cast, FieldCtor } from "./Types";
+import { Cast, ToConstructor } from "./Types";
 
 export type FieldId = string;
 export const HandleUpdate = Symbol("HandleUpdate");
@@ -28,6 +28,7 @@ export const Parent = Symbol("Parent");
 export class ObjectField {
     protected [OnUpdate]?: (diff?: any) => void;
     private [Parent]?: Doc;
+    readonly [Id] = "";
 }
 
 export type Field = number | string | boolean | ObjectField | RefField;
@@ -71,7 +72,7 @@ export namespace Doc {
         const self = doc[Self];
         return new Promise(res => getField(self, key, ignoreProto, res));
     }
-    export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: FieldCtor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
+    export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
         return new Promise(async res => {
             const field = await GetAsync(doc, key, ignoreProto);
             return Cast(field, ctor);
@@ -81,9 +82,15 @@ export namespace Doc {
         const self = doc[Self];
         return getField(self, key, ignoreProto);
     }
-    export function GetT<T extends Field>(doc: Doc, key: string, ctor: FieldCtor<T>, ignoreProto: boolean = false): T | null | undefined {
+    export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): T | null | undefined {
         return Cast(Get(doc, key, ignoreProto), ctor) as T | null | undefined;
     }
+    export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
+        const proto = await Cast(doc.prototype, Doc);
+        if (proto) {
+            proto[key] = value;
+        }
+    }
     export function MakeDelegate(doc: Opt<Doc>): Opt<Doc> {
         if (!doc) {
             return undefined;
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index 58b252f7b..f3ec9e2c5 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -6,8 +6,9 @@ import { observable } from "mobx";
 
 @Deserializable("list")
 class ListImpl<T extends Field> extends ObjectField {
-    constructor() {
+    constructor(fields: T[] = []) {
         super();
+        this.__fields = fields;
         const list = new Proxy<this>(this, {
             set: setter,
             get: getter,
@@ -21,7 +22,7 @@ class ListImpl<T extends Field> extends ObjectField {
 
     @serializable(alias("fields", list(autoObject())))
     @observable
-    private __fields: (T | null | undefined)[] = [];
+    private __fields: (T | null | undefined)[];
 
     private [Update] = (diff: any) => {
         console.log(diff);
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
index 3b7078cb0..59c6db0bd 100644
--- a/src/new_fields/Schema.ts
+++ b/src/new_fields/Schema.ts
@@ -1,5 +1,7 @@
-import { Interface, ToInterface, Cast, FieldCtor, ToConstructor, HasTail, Head, Tail } from "./Types";
+import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType } from "./Types";
 import { Doc, Field, ObjectField } from "./Doc";
+import { URLField } from "./URLField";
+import { List } from "./List";
 
 type AllToInterface<T extends Interface[]> = {
     1: ToInterface<Head<T>> & AllToInterface<Tail<T>>,
@@ -10,10 +12,10 @@ export const emptySchema = createSchema({});
 export const Document = makeInterface(emptySchema);
 export type Document = makeInterface<[typeof emptySchema]>;
 
-export type makeInterface<T extends Interface[], U extends Doc = Doc> = Partial<AllToInterface<T>> & U;
+export type makeInterface<T extends Interface[]> = Partial<AllToInterface<T>> & Doc;
 // export function makeInterface<T extends Interface[], U extends Doc>(schemas: T): (doc: U) => All<T, U>;
 // export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U>; 
-export function makeInterface<T extends Interface[], U extends Doc>(...schemas: T): (doc: U) => makeInterface<T, U> {
+export function makeInterface<T extends Interface[]>(...schemas: T): (doc: Doc) => makeInterface<T> {
     let schema: Interface = {};
     for (const s of schemas) {
         for (const key in s) {
@@ -33,7 +35,10 @@ export function makeInterface<T extends Interface[], U extends Doc>(...schemas:
             return true;
         }
     });
-    return function (doc: any) {
+    return function (doc: Doc) {
+        if (!(doc instanceof Doc)) {
+            throw new Error("Currently wrapping a schema in another schema isn't supported");
+        }
         const obj = Object.create(proto, { doc: { value: doc, writable: false } });
         return obj;
     };
@@ -59,6 +64,9 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc)
         });
     }
     return function (doc: any) {
+        if (!(doc instanceof Doc)) {
+            throw new Error("Currently wrapping a schema in another schema isn't supported");
+        }
         const obj = Object.create(proto);
         obj.__doc = doc;
         return obj;
@@ -69,3 +77,7 @@ export function createSchema<T extends Interface>(schema: T): T & { prototype: T
     schema.prototype = Doc;
     return schema as any;
 }
+
+export function listSpec<U extends ToConstructor<Field>>(type: U): ListSpec<ToType<U>> {
+    return { List: type as any };//TODO Types
+}
\ No newline at end of file
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index f4f66fe5c..fbf002c84 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -1,25 +1,28 @@
 import { Field, Opt, FieldWaiting, FieldResult } from "./Doc";
 import { List } from "./List";
 
-export type ToType<T> =
+export type ToType<T extends ToConstructor<Field> | ListSpec<Field>> =
     T extends "string" ? string :
     T extends "number" ? number :
     T extends "boolean" ? boolean :
     T extends ListSpec<infer U> ? List<U> :
     // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+    T extends { new(...args: any[]): List<Field> } ? never :
     T extends { new(...args: any[]): infer R } ? R : never;
 
-export type ToConstructor<T> =
+export type ToConstructor<T extends Field> =
     T extends string ? "string" :
     T extends number ? "number" :
-    T extends boolean ? "boolean" : new (...args: any[]) => T;
+    T extends boolean ? "boolean" :
+    T extends List<infer U> ? ListSpec<U> :
+    new (...args: any[]) => T;
 
-export type ToInterface<T> = {
+export type ToInterface<T extends Interface> = {
     [P in keyof T]: ToType<T[P]>;
 };
 
-// type ListSpec<T extends Field[]> = { List: FieldCtor<Head<T>> | ListSpec<Tail<T>> };
-export type ListSpec<T> = { List: FieldCtor<T> };
+// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
+export type ListSpec<T extends Field> = { List: ToConstructor<T> };
 
 // type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
 
@@ -34,11 +37,9 @@ export interface Interface {
     // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
 }
 
-export type FieldCtor<T extends Field> = T extends List<infer R> ? ListSpec<R> : ToConstructor<T>;
-
-export function Cast<T extends FieldCtor<Field>>(field: Field | FieldWaiting | undefined, ctor: T): FieldResult<ToType<T>>;
-export function Cast<T extends FieldCtor<Field>>(field: Field | FieldWaiting | undefined, ctor: T, defaultVal: ToType<T>): ToType<T>;
-export function Cast<T extends FieldCtor<Field>>(field: Field | FieldWaiting | undefined, ctor: T, defaultVal?: ToType<T>): FieldResult<ToType<T>> | undefined {
+export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: Field | FieldWaiting | undefined, ctor: T): FieldResult<ToType<T>>;
+export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: Field | FieldWaiting | undefined, ctor: T, defaultVal: ToType<T>): ToType<T>;
+export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: Field | FieldWaiting | undefined, ctor: T, defaultVal?: ToType<T>): FieldResult<ToType<T>> | undefined {
     if (field instanceof Promise) {
         return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) : defaultVal;
     }
@@ -58,7 +59,9 @@ export function Cast<T extends FieldCtor<Field>>(field: Field | FieldWaiting | u
     return defaultVal;
 }
 
-export function FieldValue<T extends Field, U extends T>(field: Opt<T> | Promise<Opt<T>>, defaultValue: U): T;
+type WithoutList<T extends Field> = T extends List<infer R> ? R[] : T;
+
+export function FieldValue<T extends Field, U extends WithoutList<T>>(field: Opt<T> | Promise<Opt<T>>, defaultValue: U): WithoutList<T>;
 export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>): Opt<T>;
 export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>, defaultValue?: T): Opt<T> {
     return field instanceof Promise ? defaultValue : field;
-- 
cgit v1.2.3-70-g09d2


From f3aef5750b48f375b06540a85b46e404f607c37f Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 23 Apr 2019 18:16:05 -0400
Subject: Most of collection sub view

---
 src/client/views/DocComponent.tsx                  |  2 +-
 src/client/views/collections/CollectionSubView.tsx | 99 ++++++++++------------
 src/debug/Test.tsx                                 |  4 +-
 src/new_fields/Doc.ts                              | 20 ++---
 4 files changed, 60 insertions(+), 65 deletions(-)

(limited to 'src/debug/Test.tsx')

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 31282744b..d6562492f 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -6,7 +6,7 @@ export function DocComponent<P extends { Document: Doc }, T>(schemaCtor: (doc: D
     class Component extends React.Component<P> {
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         @computed
-        get Document() {
+        get Document(): T {
             return schemaCtor(this.props.Document);
         }
     }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 1a839fdcb..642e5aed6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,12 +1,8 @@
 import { action, runInAction } from "mobx";
-import { Document } from "../../../fields/Document";
-import { ListField } from "../../../fields/ListField";
 import React = require("react");
-import { KeyStore } from "../../../fields/KeyStore";
-import { FieldWaiting, Opt } from "../../../fields/Field";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DragManager } from "../../util/DragManager";
-import { Documents, DocumentOptions } from "../../documents/Documents";
+import { Docs, DocumentOptions } from "../../documents/Documents";
 import { RouteStore } from "../../../server/RouteStore";
 import { TupleField } from "../../../fields/TupleField";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
@@ -18,10 +14,12 @@ import * as rp from 'request-promise';
 import { CollectionView } from "./CollectionView";
 import { CollectionPDFView } from "./CollectionPDFView";
 import { CollectionVideoView } from "./CollectionVideoView";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, ObjectField, Opt } from "../../../new_fields/Doc";
 import { DocComponent } from "../DocComponent";
 import { listSpec } from "../../../new_fields/Schema";
-import { Cast } from "../../../new_fields/Types";
+import { Cast, PromiseValue } from "../../../new_fields/Types";
+import { List } from "../../../new_fields/List";
+import { DocServer } from "../../DocServer";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -57,27 +55,27 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         }
 
         @action
-        protected setCursorPosition(position: [number, number]) {
+        protected async setCursorPosition(position: [number, number]) {
             let ind;
             let doc = this.props.Document;
             let id = CurrentUserUtils.id;
             let email = CurrentUserUtils.email;
             if (id && email) {
                 let textInfo: [string, string] = [id, email];
-                doc.GetTAsync(KeyStore.Prototype, Document).then(proto => {
-                    if (!proto) {
-                        return;
-                    }
-                    proto.GetOrCreateAsync<ListField<CursorEntry>>(KeyStore.Cursors, ListField, action((field: ListField<CursorEntry>) => {
-                        let cursors = field.Data;
-                        if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) {
-                            cursors[ind].Data[1] = position;
-                        } else {
-                            let entry = new TupleField<[string, string], [number, number]>([textInfo, position]);
-                            cursors.push(entry);
-                        }
-                    }));
-                });
+                const proto = await doc.proto;
+                if (!proto) {
+                    return;
+                }
+                let cursors = await Cast(proto.cursors, listSpec(ObjectField));
+                if (!cursors) {
+                    proto.cursors = cursors = new List<ObjectField>();
+                }
+                if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) {
+                    cursors[ind].Data[1] = position;
+                } else {
+                    let entry = new TupleField<[string, string], [number, number]>([textInfo, position]);
+                    cursors.push(entry);
+                }
             }
         }
 
@@ -86,9 +84,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         protected drop(e: Event, de: DragManager.DropEvent): boolean {
             if (de.data instanceof DragManager.DocumentDragData) {
                 if (de.data.aliasOnDrop || de.data.copyOnDrop) {
-                    [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key =>
+                    ["width", "height", "curPage"].map(key =>
                         de.data.draggedDocuments.map((draggedDocument: Document, i: number) =>
-                            draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null)));
+                            PromiseValue(Cast(draggedDocument[key], "number")).then(f => f && (de.data.droppedDocuments[i][key] = f))));
                 }
                 let added = false;
                 if (de.data.aliasOnDrop || de.data.copyOnDrop) {
@@ -114,42 +112,42 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             return false;
         }
 
-        protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Document>> {
-            let ctor: ((path: string, options: DocumentOptions) => (Document | Promise<Document | undefined>)) | undefined = undefined;
+        protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> {
+            let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
             if (type.indexOf("image") !== -1) {
-                ctor = Documents.ImageDocument;
+                ctor = Docs.ImageDocument;
             }
             if (type.indexOf("video") !== -1) {
-                ctor = Documents.VideoDocument;
+                ctor = Docs.VideoDocument;
             }
             if (type.indexOf("audio") !== -1) {
-                ctor = Documents.AudioDocument;
+                ctor = Docs.AudioDocument;
             }
             if (type.indexOf("pdf") !== -1) {
-                ctor = Documents.PdfDocument;
+                ctor = Docs.PdfDocument;
                 options.nativeWidth = 1200;
             }
             if (type.indexOf("excel") !== -1) {
-                ctor = Documents.DBDocument;
+                ctor = Docs.DBDocument;
                 options.copyDraggedItems = true;
             }
             if (type.indexOf("html") !== -1) {
                 if (path.includes('localhost')) {
                     let s = path.split('/');
                     let id = s[s.length - 1];
-                    Server.GetField(id).then(field => {
-                        if (field instanceof Document) {
-                            let alias = field.CreateAlias();
-                            alias.SetNumber(KeyStore.X, options.x || 0);
-                            alias.SetNumber(KeyStore.Y, options.y || 0);
-                            alias.SetNumber(KeyStore.Width, options.width || 300);
-                            alias.SetNumber(KeyStore.Height, options.height || options.width || 300);
+                    DocServer.GetRefField(id).then(field => {
+                        if (field instanceof Doc) {
+                            let alias = Doc.MakeAlias(field);
+                            alias.x = options.x || 0;
+                            alias.y = options.y || 0;
+                            alias.width = options.width || 300;
+                            alias.height = options.height || options.width || 300;
                             this.props.addDocument(alias, false);
                         }
                     });
                     return undefined;
                 }
-                ctor = Documents.WebDocument;
+                ctor = Docs.WebDocument;
                 options = { height: options.width, ...options, title: path };
             }
             return ctor ? ctor(path, options) : undefined;
@@ -169,8 +167,8 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
             if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
                 console.log("not good");
-                let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
-                htmlDoc.SetText(KeyStore.DocumentText, text);
+                let htmlDoc = Docs.HtmlDocument(html, { ...options, width: 300, height: 300 });
+                htmlDoc.documentText = text;
                 this.props.addDocument(htmlDoc, false);
                 return;
             }
@@ -212,18 +210,15 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                             let path = window.location.origin + file;
                             let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName });
 
-                            docPromise.then(action((doc?: Document) => {
-                                let docs = this.props.Document.GetT(KeyStore.Data, ListField);
-                                if (docs !== FieldWaiting) {
-                                    if (!docs) {
-                                        docs = new ListField<Document>();
-                                        this.props.Document.Set(KeyStore.Data, docs);
-                                    }
-                                    if (doc) {
-                                        docs.Data.push(doc);
-                                    }
+                            docPromise.then(async doc => {
+                                let docs = await Cast(this.props.Document[this.props.fieldKey], listSpec(Doc));
+                                if (!docs) {
+                                    this.props.Document[this.props.fieldKey] = docs = new List();
+                                }
+                                if (doc) {
+                                    docs.push(doc);
                                 }
-                            }));
+                            });
                         }));
                     });
                     promises.push(prom);
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 179f987e0..7415d4b28 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -14,8 +14,8 @@ const schema1 = createSchema({
     testDoc: Doc
 });
 
-const TestDoc = makeInterface(schema1);
 type TestDoc = makeInterface<[typeof schema1]>;
+const TestDoc: (doc?: Doc) => TestDoc = makeInterface(schema1);
 
 const schema2 = createSchema({
     hello: ImageField,
@@ -45,7 +45,6 @@ class Test extends React.Component {
 
 
         const test1: TestDoc = TestDoc(doc);
-        const test2: Test2Doc = Test2Doc(doc);
         assert(test1.hello === 5);
         assert(test1.fields === undefined);
         assert(test1.test === "hello doc");
@@ -54,6 +53,7 @@ class Test extends React.Component {
         test1.myField = 20;
         assert(test1.myField === 20);
 
+        const test2: Test2Doc = Test2Doc(doc);
         assert(test2.hello === undefined);
         // assert(test2.fields === "test");
         assert(test2.test === undefined);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 987cb2cc4..cce578b60 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -70,16 +70,16 @@ export class Doc extends RefField {
 }
 
 export namespace Doc {
-    export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
-        const self = doc[Self];
-        return new Promise(res => getField(self, key, ignoreProto, res));
-    }
-    export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
-        return new Promise(async res => {
-            const field = await GetAsync(doc, key, ignoreProto);
-            return Cast(field, ctor);
-        });
-    }
+    // export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
+    //     const self = doc[Self];
+    //     return new Promise(res => getField(self, key, ignoreProto, res));
+    // }
+    // export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
+    //     return new Promise(async res => {
+    //         const field = await GetAsync(doc, key, ignoreProto);
+    //         return Cast(field, ctor);
+    //     });
+    // }
     export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult {
         const self = doc[Self];
         return getField(self, key, ignoreProto);
-- 
cgit v1.2.3-70-g09d2


From 877b104a61d2ab072e3b6a006168ec03e2c46365 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 30 Apr 2019 00:11:27 -0400
Subject: Mostly fixed lists

---
 src/client/util/UndoManager.ts                     |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 +
 src/debug/Test.tsx                                 |   8 +-
 src/new_fields/Doc.ts                              |  10 +-
 src/new_fields/List.ts                             | 192 +++++++++++++++++----
 src/new_fields/util.ts                             |   8 +
 6 files changed, 179 insertions(+), 44 deletions(-)

(limited to 'src/debug/Test.tsx')

diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index f7c3e5a7b..0b5280c4a 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -141,10 +141,10 @@ export namespace UndoManager {
     });
 
     //TODO Make this return the return value
-    export function RunInBatch(fn: () => void, batchName: string) {
+    export function RunInBatch<T>(fn: () => T, batchName: string) {
         let batch = StartBatch(batchName);
         try {
-            runInAction(fn);
+            return runInAction(fn);
         } finally {
             batch.end();
         }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dcded7648..d796bd8d5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -272,6 +272,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     get views() {
         let curPage = FieldValue(this.Document.curPage, -1);
         let docviews = (this.children || []).filter(doc => doc).reduce((prev, doc) => {
+            if (!FieldValue(doc)) return prev;
             var page = Cast(doc.page, "number", -1);
             if (page === curPage || page === -1) {
                 let minim = Cast(doc.isMinimized, "boolean");
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 7415d4b28..04ef00722 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -61,14 +61,12 @@ class Test extends React.Component {
         assert(test2.testDoc === undefined);
         test2.url = 35;
         assert(test2.url === 35);
-        const l = new List<number>();
+        const l = new List<Doc>();
         //TODO push, and other array functions don't go through the proxy
-        l.push(1);
+        l.push(doc2);
         //TODO currently length, and any other string fields will get serialized
-        l.length = 3;
-        l[2] = 5;
+        doc.list = l;
         console.log(l.slice());
-        console.log(SerializationHelper.Serialize(l));
     }
 
     render() {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index d15b6309d..6ddb0df89 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -3,7 +3,7 @@ import { serializable, primitive, map, alias, list } from "serializr";
 import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
 import { Utils } from "../Utils";
 import { DocServer } from "../client/DocServer";
-import { setter, getter, getField, updateFunction } from "./util";
+import { setter, getter, getField, updateFunction, deleteProperty } from "./util";
 import { Cast, ToConstructor, PromiseValue, FieldValue } from "./Types";
 import { UndoManager, undoBatch } from "../client/util/UndoManager";
 import { listSpec } from "./Schema";
@@ -34,7 +34,7 @@ export class Doc extends RefField {
             set: setter,
             get: getter,
             ownKeys: target => Object.keys(target.__fields),
-            deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); },
+            deleteProperty: deleteProperty,
             defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
         });
         if (!id || forceSave) {
@@ -151,8 +151,8 @@ export namespace Doc {
     }
 
     export function MakeLink(source: Doc, target: Doc): Doc {
-        let linkDoc = new Doc;
-        UndoManager.RunInBatch(() => {
+        return UndoManager.RunInBatch(() => {
+            let linkDoc = new Doc;
             linkDoc.title = "New Link";
             linkDoc.linkDescription = "";
             linkDoc.linkTags = "Default";
@@ -171,8 +171,8 @@ export namespace Doc {
                 source.linkedToDocs = linkedTo = new List<Doc>();
             }
             linkedTo.push(linkDoc);
+            return linkDoc;
         }, "make link");
-        return linkDoc;
     }
 
     export function MakeDelegate(doc: Doc): Doc;
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index e4a80f7a1..ec1bf44a9 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -1,21 +1,173 @@
 import { Deserializable, autoObject } from "../client/util/SerializationHelper";
 import { Field, Update, Self } from "./Doc";
-import { setter, getter } from "./util";
+import { setter, getter, deleteProperty } from "./util";
 import { serializable, alias, list } from "serializr";
 import { observable, observe, IArrayChange, IArraySplice, IObservableArray, Lambda, reaction } from "mobx";
 import { ObjectField, OnUpdate } from "./ObjectField";
+import { RefField } from "./RefField";
+import { ProxyField } from "./Proxy";
 
 const listHandlers: any = {
-    push(...items: any[]) {
-        // console.log("push");
-        // console.log(...items);
-        return this[Self].__fields.push(...items);
+    /// Mutator methods
+    copyWithin() {
+        throw new Error("copyWithin not supported yet");
+    },
+    fill(value: any, start?: number, end?: number) {
+        if (value instanceof RefField) {
+            throw new Error("fill with RefFields not supported yet");
+        }
+        const res = this[Self].__fields.fill(value, start, end);
+        this[Update]();
+        return res;
     },
     pop(): any {
-        return this[Self].__fields.pop();
+        const field = toRealField(this[Self].__fields.pop());
+        this[Update]();
+        return field;
+    },
+    push(...items: any[]) {
+        items = items.map(toObjectField);
+        const res = this[Self].__fields.push(...items);
+        this[Update]();
+        return res;
+    },
+    reverse() {
+        const res = this[Self].__fields.reverse();
+        this[Update]();
+        return res;
+    },
+    shift() {
+        const res = toRealField(this[Self].__fields.shift());
+        this[Update]();
+        return res;
+    },
+    sort(cmpFunc: any) {
+        const res = this[Self].__fields.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
+        this[Update]();
+        return res;
+    },
+    splice(start: number, deleteCount: number, ...items: any[]) {
+        items = items.map(toObjectField);
+        const res = this[Self].__fields.splice(start, deleteCount, ...items);
+        this[Update]();
+        return res.map(toRealField);
+    },
+    unshift(...items: any[]) {
+        items = items.map(toObjectField);
+        const res = this[Self].__fields.unshift(...items);
+        this[Update]();
+        return res;
+
+    },
+    /// Accessor methods
+    concat(...items: any[]) {
+        return this[Self].__fields.map(toRealField).concat(...items);
+    },
+    includes(valueToFind: any, fromIndex: number) {
+        const fields = this[Self].__fields;
+        if (valueToFind instanceof RefField) {
+            return fields.map(toRealField).includes(valueToFind, fromIndex);
+        } else {
+            return fields.includes(valueToFind, fromIndex);
+        }
+    },
+    indexOf(valueToFind: any, fromIndex: number) {
+        const fields = this[Self].__fields;
+        if (valueToFind instanceof RefField) {
+            return fields.map(toRealField).indexOf(valueToFind, fromIndex);
+        } else {
+            return fields.indexOf(valueToFind, fromIndex);
+        }
+    },
+    join(separator: any) {
+        return this[Self].__fields.map(toRealField).join(separator);
+    },
+    lastIndexOf(valueToFind: any, fromIndex: number) {
+        const fields = this[Self].__fields;
+        if (valueToFind instanceof RefField) {
+            return fields.map(toRealField).lastIndexOf(valueToFind, fromIndex);
+        } else {
+            return fields.lastIndexOf(valueToFind, fromIndex);
+        }
+    },
+    slice(begin: number, end: number) {
+        return this[Self].__fields.slice(begin, end).map(toRealField);
+    },
+
+    /// Iteration methods
+    entries() {
+        return this[Self].__fields.map(toRealField).entries();
+    },
+    every(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).every(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    filter(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).filter(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    find(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).find(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    findIndex(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).findIndex(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    forEach(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).forEach(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    map(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).map(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    reduce(callback: any, initialValue: any) {
+        return this[Self].__fields.map(toRealField).reduce(callback, initialValue);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+    },
+    reduceRight(callback: any, initialValue: any) {
+        return this[Self].__fields.map(toRealField).reduceRight(callback, initialValue);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+    },
+    some(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).some(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    values() {
+        return this[Self].__fields.map(toRealField).values();
+    },
+    [Symbol.iterator]() {
+        return this[Self].__fields.map(toRealField).values();
     }
 };
 
+function toObjectField(field: Field) {
+    return field instanceof RefField ? new ProxyField(field) : field;
+}
+
+function toRealField(field: Field) {
+    return field instanceof ProxyField ? field.value() : field;
+}
+
 function listGetter(target: any, prop: string | number | symbol, receiver: any): any {
     if (listHandlers.hasOwnProperty(prop)) {
         return listHandlers[prop];
@@ -38,36 +190,15 @@ interface ListIndexUpdate<T> {
 
 type ListUpdate<T> = ListSpliceUpdate<T> | ListIndexUpdate<T>;
 
-const ObserveDisposer = Symbol("Observe Disposer");
-
-function listObserver<T extends Field>(this: ListImpl<T>, change: IArrayChange<T> | IArraySplice<T>) {
-    if (change.type === "splice") {
-        this[Update]({
-            index: change.index,
-            removedCount: change.removedCount,
-            added: change.added,
-            type: change.type
-        });
-    } else {
-        //This should already be handled by the getter for the Proxy
-        // this[Update]({
-        //     index: change.index,
-        //     newValue: change.newValue,
-        //     type: change.type
-        // });
-    }
-}
-
 @Deserializable("list")
 class ListImpl<T extends Field> extends ObjectField {
     constructor(fields: T[] = []) {
         super();
         this.___fields = fields;
-        this[ObserveDisposer] = observe(this.__fields as IObservableArray<T>, listObserver.bind(this));
         const list = new Proxy<this>(this, {
             set: setter,
-            get: getter,
-            deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); },
+            get: listGetter,
+            deleteProperty: deleteProperty,
             defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
         });
         return list;
@@ -82,8 +213,6 @@ class ListImpl<T extends Field> extends ObjectField {
 
     private set __fields(value) {
         this.___fields = value;
-        this[ObserveDisposer]();
-        this[ObserveDisposer] = observe(this.__fields as IObservableArray<T>, listObserver.bind(this));
     }
 
     // @serializable(alias("fields", list(autoObject())))
@@ -97,7 +226,6 @@ class ListImpl<T extends Field> extends ObjectField {
         update && update();
     }
 
-    private [ObserveDisposer]: Lambda;
     private [Self] = this;
 }
 export type List<T extends Field> = ListImpl<T> & T[];
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 511820115..128817ab8 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -78,6 +78,14 @@ export function getField(target: any, prop: string | number, ignoreProto: boolea
     return field;
 }
 
+export function deleteProperty(target: any, prop: string | number | symbol) {
+    if (typeof prop === "symbol") {
+        delete target[prop];
+        return true;
+    }
+    throw new Error("Currently properties can't be deleted from documents, assign to undefined instead");
+}
+
 export function updateFunction(target: any, prop: any, value: any) {
     return (diff?: any) => {
         if (!diff) diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } };
-- 
cgit v1.2.3-70-g09d2


From da792d534024d8e0d266e291f92b9a1e83be51c2 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Tue, 30 Apr 2019 18:38:05 -0400
Subject: set not working

---
 src/debug/Test.tsx  | 50 +++++++++++++++++++++++++-------------------------
 src/server/index.ts | 14 ++++++++++----
 2 files changed, 35 insertions(+), 29 deletions(-)

(limited to 'src/debug/Test.tsx')

diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 6a677f80f..47cfac2c1 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -46,34 +46,34 @@ class Test extends React.Component {
         doc.fields = "test";
         doc.test = "hello doc";
         doc.url = url;
-        doc.testDoc = doc2;
+        //doc.testDoc = doc2;
 
 
-        const test1: TestDoc = TestDoc(doc);
-        const test2: Test2Doc = Test2Doc(doc);
-        assert(test1.hello === 5);
-        assert(test1.fields === undefined);
-        assert(test1.test === "hello doc");
-        assert(test1.url === url);
-        assert(test1.testDoc === doc2);
-        test1.myField = 20;
-        assert(test1.myField === 20);
+        // const test1: TestDoc = TestDoc(doc);
+        // const test2: Test2Doc = Test2Doc(doc);
+        // assert(test1.hello === 5);
+        // assert(test1.fields === undefined);
+        // assert(test1.test === "hello doc");
+        // assert(test1.url === url);
+        // //assert(test1.testDoc === doc2);
+        // test1.myField = 20;
+        // assert(test1.myField === 20);
 
-        assert(test2.hello === undefined);
-        // assert(test2.fields === "test");
-        assert(test2.test === undefined);
-        assert(test2.url === undefined);
-        assert(test2.testDoc === undefined);
-        test2.url = 35;
-        assert(test2.url === 35);
-        const l = new List<number>();
-        //TODO push, and other array functions don't go through the proxy
-        l.push(1);
-        //TODO currently length, and any other string fields will get serialized
-        l.length = 3;
-        l[2] = 5;
-        console.log(l.slice());
-        console.log(SerializationHelper.Serialize(l));
+        // assert(test2.hello === undefined);
+        // // assert(test2.fields === "test");
+        // assert(test2.test === undefined);
+        // assert(test2.url === undefined);
+        // assert(test2.testDoc === undefined);
+        // test2.url = 35;
+        // assert(test2.url === 35);
+        // const l = new List<number>();
+        // //TODO push, and other array functions don't go through the proxy
+        // l.push(1);
+        // //TODO currently length, and any other string fields will get serialized
+        // l.length = 3;
+        // l[2] = 5;
+        // console.log(l.slice());
+        // console.log(SerializationHelper.Serialize(l));
     }
 
     render() {
diff --git a/src/server/index.ts b/src/server/index.ts
index 464d3f68f..b57e5c482 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -295,23 +295,29 @@ function UpdateField(socket: Socket, diff: Diff) {
     //console.log("set field");
     //}
     const docid = { id: diff.id };
-    const docfield = diff.diff;
+    var docfield = diff.diff;
+    docfield = JSON.parse(JSON.stringify(docfield).split("fields.").join(""));
     console.log("FIELD: ", docfield);
     var dynfield = false;
     for (var key in docfield) {
         const val = docfield[key];
         if (typeof val === 'number') {
-            key = key + "_n";
+            const new_key: string = key + "_n";
+            docfield = JSON.parse(JSON.stringify(docfield).split(key).join(new_key));
+            //docfield[new_key] = { 'set': val };
             dynfield = true;
         }
         else if (typeof val === 'string') {
-            key = key + "_t";
+            const new_key: string = key + "_t";
+            docfield = JSON.parse(JSON.stringify(docfield).split(key).join(new_key));
+            docfield[new_key] = { 'set': val };
             dynfield = true;
         }
-        console.log(key);
     }
     var merged = {};
     _.extend(merged, docid, docfield);
+    console.log(merged);
+    console.log(docfield);
     if (dynfield) {
         console.log("dynamic field detected!");
         Search.Instance.updateDocument(merged);
-- 
cgit v1.2.3-70-g09d2


From e19fdbba4cf672aee5bfb59b91b6162431d146d3 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Sat, 4 May 2019 18:48:01 -0400
Subject: queries semi work

---
 package.json         |  3 ++-
 src/debug/Test.tsx   | 19 ++++++++++++++++++-
 src/server/Search.ts | 25 +++++++++++++++++++++++++
 src/server/index.ts  |  3 +++
 4 files changed, 48 insertions(+), 2 deletions(-)

(limited to 'src/debug/Test.tsx')

diff --git a/package.json b/package.json
index fd794d062..3393eacc6 100644
--- a/package.json
+++ b/package.json
@@ -167,9 +167,10 @@
         "serializr": "^1.5.1",
         "socket.io": "^2.2.0",
         "socket.io-client": "^2.2.0",
+        "solr-node": "^1.1.3",
         "typescript-collections": "^1.3.2",
         "url-loader": "^1.1.2",
         "uuid": "^3.3.2",
         "xoauth2": "^1.2.0"
     }
-}
\ No newline at end of file
+}
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 47cfac2c1..7d72a1ba0 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -3,6 +3,9 @@ import * as ReactDOM from 'react-dom';
 import { serialize, deserialize, map } from 'serializr';
 import { URLField, Doc, createSchema, makeInterface, makeStrictInterface, List, ListSpec } from '../fields/NewDoc';
 import { SerializationHelper } from '../client/util/SerializationHelper';
+import { Search } from '../server/Search';
+import { restProperty } from 'babel-types';
+import * as rp from 'request-promise';
 
 const schema1 = createSchema({
     hello: "number",
@@ -76,8 +79,22 @@ class Test extends React.Component {
         // console.log(SerializationHelper.Serialize(l));
     }
 
+    onEnter = async (e: any) => {
+        var key = e.keyCode || e.which;
+        if (key === 13) {
+            var query = e.target.value;
+            await rp.get('http://localhost:1050/search', {
+                qs: {
+                    query
+                }
+            });
+        }
+    }
+
     render() {
-        return <button onClick={this.onClick}>Click me</button>;
+        return <div><button onClick={this.onClick}>Click me</button>
+            <input onKeyPress={this.onEnter}></input>
+        </div>;
     }
 }
 
diff --git a/src/server/Search.ts b/src/server/Search.ts
index 9e462f0ae..4911edd1d 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -5,6 +5,31 @@ import { thisExpression } from 'babel-types';
 export class Search {
     public static Instance = new Search();
     private url = 'http://localhost:8983/solr/';
+    private client: any;
+
+    constructor() {
+        console.log("Search Instantiated!");
+        var SolrNode = require('solr-node');
+        this.client = new SolrNode({
+            host: 'localhost',
+            port: '8983',
+            core: 'dash',
+            protocol: 'http'
+        });
+        var strQuery = this.client.query().q('text:test');
+
+        console.log(strQuery);
+
+        // Search documents using strQuery
+        // client.search(strQuery, (err: any, result: any) => {
+        //     if (err) {
+        //         console.log(err);
+        //         return;
+        //     }
+        //     console.log('Response:', result.response);
+        // });
+    }
+
 
     public async updateDocument(document: any) {
         console.log("UPDATE: ", JSON.stringify(document));
diff --git a/src/server/index.ts b/src/server/index.ts
index f90724152..f2bcd3f00 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -122,11 +122,14 @@ app.get("/pull", (req, res) =>
         res.redirect("/");
     }));
 
+// SEARCH
+
 // GETTERS
 
 app.get("/search", async (req, res) => {
     let query = req.query.query || "hello";
     let results = await Search.Instance.search(query);
+    console.log(results);
     res.send(results);
 });
 
-- 
cgit v1.2.3-70-g09d2

