From 74dee5f76ffc5bbdd07caafb4273aaf485dec1b9 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samuel_wilkins@brown.edu>
Date: Wed, 12 Jun 2019 23:22:58 -0400
Subject: beginnings of nested golden layout

---
 src/client/views/collections/CollectionTreeView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/client/views/collections/CollectionTreeView.tsx')

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 48da52ffa..b0a310ec1 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -123,7 +123,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     return true;
                 }}
             />);
-        let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []) : [];
+        let dataDocs = CollectionDockingView.TopLevel ? Cast(CollectionDockingView.TopLevel.props.Document.data, listSpec(Doc), []) : [];
         let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : (
             <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
                 <FontAwesomeIcon icon="angle-right" size="lg" />
-- 
cgit v1.2.3-70-g09d2


From d2c9550f23c4e5654822ac01b973bb965e3f6dec Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samuel_wilkins@brown.edu>
Date: Fri, 14 Jun 2019 20:49:12 -0400
Subject: cleaned up Docs namespace and thoroughly documented
 DocServer.GetRefFields

---
 .vscode/settings.json                              |   3 +-
 src/client/DocServer.ts                            |  80 ++-
 src/client/documents/Documents.ts                  | 758 ++++++++++++---------
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/Main.tsx                          |   2 +-
 src/client/views/MainView.tsx                      |  24 +-
 src/client/views/SearchBox.tsx                     |   2 +-
 .../views/collections/CollectionDockingView.tsx    | 386 ++++-------
 .../views/collections/CollectionSchemaView.tsx     |   2 +-
 src/client/views/collections/CollectionSubView.tsx |  16 +-
 .../views/collections/CollectionTreeView.tsx       |   2 +-
 .../views/collections/CollectionVideoView.tsx      |   2 +-
 .../views/collections/DockedFrameRenderer.tsx      | 116 ++++
 .../collections/collectionFreeForm/MarqueeView.tsx |  18 +-
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 src/mobile/ImageUpload.tsx                         |   4 +-
 src/new_fields/Doc.ts                              |  12 +
 src/new_fields/util.ts                             |   1 +
 .../authentication/models/current_user_utils.ts    |   8 +-
 19 files changed, 835 insertions(+), 607 deletions(-)
 create mode 100644 src/client/views/collections/DockedFrameRenderer.tsx

(limited to 'src/client/views/collections/CollectionTreeView.tsx')

diff --git a/.vscode/settings.json b/.vscode/settings.json
index fc315ffaf..5df697fee 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -9,5 +9,6 @@
     "editor.formatOnSave": true,
     "editor.detectIndentation": false,
     "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false,
-    "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true
+    "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true,
+    "search.usePCRE2": true
 }
\ No newline at end of file
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index cbcf751ee..d759b4757 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -47,36 +47,81 @@ export namespace DocServer {
         }
     }
 
+    /**
+     * Given a list of Doc GUIDs, this utility function will asynchronously attempt to fetch each document
+     * associated with a given input id, first looking in the RefField cache and then communicating with
+     * the server if the document was not found there.
+     * 
+     * @param ids the ids that map to the reqested documents
+     */
     export async function GetRefFields(ids: string[]): Promise<{ [id: string]: Opt<RefField> }> {
         const requestedIds: string[] = [];
         const waitingIds: string[] = [];
         const promises: Promise<Opt<RefField>>[] = [];
         const map: { [id: string]: Opt<RefField> } = {};
+
+        // 1) An initial pass through the cache to determine which documents need to be fetched,
+        // which are already in the process of being fetched and which already exist in the
+        // cache
         for (const id of ids) {
             const cached = _cache[id];
+
             if (cached === undefined) {
+                // NOT CACHED => we'll have to send a request to the server
                 requestedIds.push(id);
             } else if (cached instanceof Promise) {
+                // BEING CACHED => someone else previously (likely recently) called GetRefFields,
+                // and requested one of the documents I'm looking for. Shouldn't fetch again, just
+                // wait until this promise is resolved (see the second to last line of the function)
                 promises.push(cached);
                 waitingIds.push(id);
             } else {
+                // CACHED => great, let's just add it to the field map
                 map[id] = cached;
             }
         }
-        const prom = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds).then(fields => {
+
+        // 2) Synchronously, we emit a single callback to the server requesting the documents for the given ids.
+        // This returns a promise, which, when resolved, indicates that all the JSON serialized versions of
+        // the fields have been returned from the server
+        const fieldsReceived: Promise<any> = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds);
+
+        // 3) When the serialized RefFields have been received, go head and begin deserializing them into objects.
+        // Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all
+        // future .proto calls won't have to go farther than the cache to get their actual value.
+        const fieldsDeserialized = fieldsReceived.then(async fields => {
             const fieldMap: { [id: string]: RefField } = {};
+            const deserializedFields: any = [];
             for (const field of fields) {
                 if (field !== undefined) {
-                    fieldMap[field.id] = SerializationHelper.Deserialize(field);
+                    // deserialize
+                    let deserialized: any = SerializationHelper.Deserialize(field);
+                    fieldMap[field.id] = deserialized;
+                    deserializedFields.push(deserialized.proto);
                 }
             }
-
+            // this actually handles the loeading of prototypes
+            await Promise.all(deserializedFields);
             return fieldMap;
         });
-        requestedIds.forEach(id => _cache[id] = prom.then(fields => fields[id]));
-        const fields = await prom;
+
+        // 4) Here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
+        // we set the value at the field's id to a promise that will resolve to the field. 
+        // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
+        requestedIds.forEach(id => _cache[id] = fieldsDeserialized.then(fields => fields[id]));
+
+        // 5) At this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose
+        // prototype documents, if any, have also been fetched and cached.
+        const fields = await fieldsDeserialized;
+
+        // 6) With this confidence, we can now go through and update the cache at the ids of the fields that
+        // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given
+        // id to the soon to be returned field mapping.
         requestedIds.forEach(id => {
             const field = fields[id];
+            // either way, overwrite or delete any promises that we inserted as flags
+            // to indicate that the field was in the process of being fetched. Now everything
+            // should be an actual value within or entirely absent from the cache.
             if (field !== undefined) {
                 _cache[id] = field;
             } else {
@@ -84,14 +129,23 @@ export namespace DocServer {
             }
             map[id] = field;
         });
-        await Promise.all(requestedIds.map(async id => {
-            const field = fields[id];
-            if (field) {
-                await (field as any).proto;
-            }
-        }));
-        const otherFields = await Promise.all(promises);
-        waitingIds.forEach((id, index) => map[id] = otherFields[index]);
+
+        // 7) Those promises we encountered in the else if of 1), which represent
+        // other callers having already submitted a request to the server for (a) document(s)
+        // in which we're interested, must still be awaited so that we can return the proper
+        // values for those as well. 
+        //
+        // Fortunately, those other callers will also hit their own version of 6) and clean up
+        // the shared cache when these promises resolve, so all we have to do is...
+        const otherCallersFetching = await Promise.all(promises);
+        // ...extract the RefFields returned from the resolution of those promises and add them to our
+        // own map.
+        waitingIds.forEach((id, index) => map[id] = otherCallersFetching[index]);
+
+        // Now, we return our completed mapping from all of the ids that were passed into the method
+        // to their actual RefField | undefined values. This return value either becomes the input
+        // argument to the caller's promise (i.e. GetRefFields.then(map => //do something with map...))
+        // or it is the direct return result if the promise is awaited.
         return map;
     }
 
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b346e1570..b10954636 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -18,7 +18,6 @@ import { action } from "mobx";
 import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel";
 import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel";
 import { AggregateFunction } from "../northstar/model/idea/idea";
-import { Template } from "../views/Templates";
 import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
 import { IconBox } from "../views/nodes/IconBox";
 import { Field, Doc, Opt } from "../../new_fields/Doc";
@@ -30,7 +29,7 @@ import { Cast, NumCast } from "../../new_fields/Types";
 import { IconField } from "../../new_fields/IconField";
 import { listSpec } from "../../new_fields/Schema";
 import { DocServer } from "../DocServer";
-import { StrokeData, InkField } from "../../new_fields/InkField";
+import { InkField } from "../../new_fields/InkField";
 import { dropActionType } from "../util/DragManager";
 import { DateField } from "../../new_fields/DateField";
 import { UndoManager } from "../util/UndoManager";
@@ -67,345 +66,486 @@ export interface DocumentOptions {
     dbDoc?: Doc;
     // [key: string]: Opt<Field>;
 }
-const delegateKeys = ["x", "y", "width", "height", "panX", "panY"];
 
-export namespace DocUtils {
-    export function MakeLink(source: Doc, target: Doc) {
-        let protoSrc = source.proto ? source.proto : source;
-        let protoTarg = target.proto ? target.proto : target;
-        UndoManager.RunInBatch(() => {
-            let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 });
-            //let linkDoc = new Doc;
-            linkDoc.proto!.title = "-link name-";
-            linkDoc.proto!.linkDescription = "";
-            linkDoc.proto!.linkTags = "Default";
+export namespace Docs {
 
-            linkDoc.proto!.linkedTo = target;
-            linkDoc.proto!.linkedToPage = target.curPage;
-            linkDoc.proto!.linkedFrom = source;
-            linkDoc.proto!.linkedFromPage = source.curPage;
+    export namespace Prototypes {
 
-            let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc));
-            if (!linkedFrom) {
-                protoTarg.linkedFromDocs = linkedFrom = new List<Doc>();
-            }
-            linkedFrom.push(linkDoc);
+        // the complete list of document prototypes and their ids
+        export let textProto: Doc; const textProtoId = "textProto";
+        export let histoProto: Doc; const histoProtoId = "histoProto";
+        export let imageProto: Doc; const imageProtoId = "imageProto";
+        export let webProto: Doc; const webProtoId = "webProto";
+        export let collProto: Doc; const collProtoId = "collectionProto";
+        export let kvpProto: Doc; const kvpProtoId = "kvpProto";
+        export let videoProto: Doc; const videoProtoId = "videoProto";
+        export let audioProto: Doc; const audioProtoId = "audioProto";
+        export let pdfProto: Doc; const pdfProtoId = "pdfProto";
+        export let iconProto: Doc; const iconProtoId = "iconProto";
 
-            let linkedTo = Cast(protoSrc.linkedToDocs, listSpec(Doc));
-            if (!linkedTo) {
-                protoSrc.linkedToDocs = linkedTo = new List<Doc>();
+        /**
+         * This function loads or initializes the prototype for each docment type.
+         * 
+         * This is an asynchronous function because it has to attempt
+         * to fetch the prototype documents from the server.
+         * 
+         * Once we have this object that maps the prototype ids to a potentially
+         * undefined document, we either initialize our private prototype
+         * variables with the document returned from the server or, if prototypes
+         * haven't been initialized, the newly initialized prototype document.
+         */
+        export async function initialize(): Promise<void> {
+            // non-guid string ids for each document prototype
+            let protoIds = [textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId]
+            // fetch the actual prototype documents from the server
+            let actualProtos = await DocServer.GetRefFields(protoIds);
+
+            // initialize prototype documents
+            textProto = actualProtos[textProtoId] as Doc || CreateTextProto();
+            histoProto = actualProtos[histoProtoId] as Doc || CreateHistogramProto();
+            collProto = actualProtos[collProtoId] as Doc || CreateCollectionProto();
+            imageProto = actualProtos[imageProtoId] as Doc || CreateImageProto();
+            webProto = actualProtos[webProtoId] as Doc || CreateWebProto();
+            kvpProto = actualProtos[kvpProtoId] as Doc || CreateKVPProto();
+            videoProto = actualProtos[videoProtoId] as Doc || CreateVideoProto();
+            audioProto = actualProtos[audioProtoId] as Doc || CreateAudioProto();
+            pdfProto = actualProtos[pdfProtoId] as Doc || CreatePdfProto();
+            iconProto = actualProtos[iconProtoId] as Doc || CreateIconProto();
+        }
+
+        /**
+         * This is a convenience method that is used to initialize
+         * prototype documents for the first time.
+         * 
+         * @param protoId the id of the prototype, indicating the specific prototype
+         * to initialize (see the *protoId list at the top of the namespace)
+         * @param title the prototype document's title, follows *-PROTO
+         * @param layout the layout key for this prototype and thus the
+         * layout key that all delegates will inherit
+         * @param options any value specified in the DocumentOptions object likewise
+         * becomes the default value for that key for all delegates
+         */
+        function buildPrototype(protoId: string, title: string, layout: string, options: DocumentOptions): Doc {
+            return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout, baseLayout: layout });
+        }
+
+        // INDIVIDUAL INITIALIZERS
+
+        function CreateImageProto(): Doc {
+            let defaultAttrs = {
+                x: 0,
+                y: 0,
+                nativeWidth: 600,
+                width: 300,
+                backgroundLayout: ImageBox.LayoutString(),
+                curPage: 0
+            };
+            return buildPrototype(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("annotations"), defaultAttrs);
+        }
+
+        function CreateHistogramProto(): Doc {
+            let defaultAttrs = {
+                x: 0,
+                y: 0,
+                width: 300,
+                height: 300,
+                backgroundColor: "black",
+                backgroundLayout:
+                    HistogramBox.LayoutString()
+            };
+            return buildPrototype(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"), defaultAttrs);
+        }
+
+        function CreateIconProto(): Doc {
+            let defaultAttrs = {
+                x: 0,
+                y: 0,
+                width: Number(MINIMIZED_ICON_SIZE),
+                height: Number(MINIMIZED_ICON_SIZE)
+            };
+            return buildPrototype(iconProtoId, "ICON_PROTO", IconBox.LayoutString(), defaultAttrs);
+        }
+
+        function CreateTextProto(): Doc {
+            let defaultAttrs = {
+                x: 0,
+                y: 0,
+                width: 300,
+                height: 150,
+                backgroundColor: "#f1efeb"
+            };
+            return buildPrototype(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(), defaultAttrs);
+        }
+
+        function CreatePdfProto(): Doc {
+            let defaultAttrs = {
+                x: 0,
+                y: 0,
+                nativeWidth: 1200,
+                width: 300,
+                backgroundLayout: PDFBox.LayoutString(),
+                curPage: 1
+            };
+            return buildPrototype(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("annotations"), defaultAttrs);
+        }
+
+        function CreateWebProto(): Doc {
+            let defaultAttrs = {
+                x: 0,
+                y: 0,
+                width: 300,
+                height: 300
+            };
+            return buildPrototype(webProtoId, "WEB_PROTO", WebBox.LayoutString(), defaultAttrs);
+        }
+
+        function CreateCollectionProto(): Doc {
+            let defaultAttrs = {
+                panX: 0,
+                panY: 0,
+                scale: 1,
+                width: 500,
+                height: 500
+            };
+            return buildPrototype(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("data"), defaultAttrs);
+        }
+
+        function CreateKVPProto(): Doc {
+            let defaultAttrs = {
+                x: 0,
+                y: 0,
+                width: 300,
+                height: 150
+            };
+            return buildPrototype(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(), defaultAttrs);
+        }
+
+        function CreateVideoProto(): Doc {
+            let defaultAttrs = {
+                x: 0,
+                y: 0,
+                nativeWidth: 600,
+                width: 300,
+                backgroundLayout: VideoBox.LayoutString(),
+                curPage: 0
+            };
+            return buildPrototype(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("annotations"), defaultAttrs);
+        }
+
+        function CreateAudioProto(): Doc {
+            let defaultAttrs = {
+                x: 0,
+                y: 0,
+                width: 300,
+                height: 150
             }
-            linkedTo.push(linkDoc);
-            return linkDoc;
-        }, "make link");
+            return buildPrototype(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(), defaultAttrs);
+        }
     }
 
+    /**
+     * Encapsulates the factory used to create new document instances
+     * delegated from top-level prototypes
+     */
+    export namespace Create {
 
-}
+        const delegateKeys = ["x", "y", "width", "height", "panX", "panY"];
 
-export namespace Docs {
-    let textProto: Doc;
-    let histoProto: Doc;
-    let imageProto: Doc;
-    let webProto: Doc;
-    let collProto: Doc;
-    let kvpProto: Doc;
-    let videoProto: Doc;
-    let audioProto: Doc;
-    let pdfProto: Doc;
-    let iconProto: Doc;
-    const textProtoId = "textProto";
-    const histoProtoId = "histoProto";
-    const pdfProtoId = "pdfProto";
-    const imageProtoId = "imageProto";
-    const webProtoId = "webProto";
-    const collProtoId = "collectionProto";
-    const kvpProtoId = "kvpProto";
-    const videoProtoId = "videoProto";
-    const audioProtoId = "audioProto";
-    const iconProtoId = "iconProto";
-
-    export function initProtos(): Promise<void> {
-        return DocServer.GetRefFields([textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId]).then(fields => {
-            textProto = fields[textProtoId] as Doc || CreateTextPrototype();
-            histoProto = fields[histoProtoId] as Doc || CreateHistogramPrototype();
-            collProto = fields[collProtoId] as Doc || CreateCollectionPrototype();
-            imageProto = fields[imageProtoId] as Doc || CreateImagePrototype();
-            webProto = fields[webProtoId] as Doc || CreateWebPrototype();
-            kvpProto = fields[kvpProtoId] as Doc || CreateKVPPrototype();
-            videoProto = fields[videoProtoId] as Doc || CreateVideoPrototype();
-            audioProto = fields[audioProtoId] as Doc || CreateAudioPrototype();
-            pdfProto = fields[pdfProtoId] as Doc || CreatePdfPrototype();
-            iconProto = fields[iconProtoId] as Doc || CreateIconPrototype();
-        });
-    }
+        /**
+         * This function receives the relevant document prototype and uses
+         * it to create a new of that base-level prototype, or the
+         * underlying data document, which it then delegates again 
+         * to create the view document.
+         * 
+         * It also takes the opportunity to register the user
+         * that created the document and the time of creation.
+         * 
+         * @param proto the specific document prototype off of which to model
+         * this new instance (textProto, imageProto, etc.)
+         * @param data the Field to store at this new instance's data key
+         * @param options any initial values to provide for this new instance
+         * @param delegId if applicable, an existing document id. If undefined, Doc's
+         * constructor just generates a new GUID. This is currently used
+         * only when creating a DockDocument from the current user's already existing
+         * main document.
+         */
+        function CreateInstanceFromProto(proto: Doc, data: Field, options: DocumentOptions, delegId?: string) {
+            const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys);
 
-    function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Doc {
-        return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout, baseLayout: layout });
-    }
-    function SetInstanceOptions<U extends Field>(doc: Doc, options: DocumentOptions, value: U) {
-        const deleg = Doc.MakeDelegate(doc);
-        deleg.data = value;
-        return Doc.assign(deleg, options);
-    }
-    function SetDelegateOptions(doc: Doc, options: DocumentOptions, id?: string) {
-        const deleg = Doc.MakeDelegate(doc, id);
-        return Doc.assign(deleg, options);
-    }
+            if (!("author" in protoProps)) {
+                protoProps.author = CurrentUserUtils.email;
+            }
 
-    function CreateImagePrototype(): Doc {
-        let imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("annotations"),
-            { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: ImageBox.LayoutString(), curPage: 0 });
-        return imageProto;
-    }
+            if (!("creationDate" in protoProps)) {
+                protoProps.creationDate = new DateField;
+            }
 
-    function CreateHistogramPrototype(): Doc {
-        let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"),
-            { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", backgroundLayout: HistogramBox.LayoutString() });
-        return histoProto;
-    }
-    function CreateIconPrototype(): Doc {
-        let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(),
-            { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) });
-        return iconProto;
-    }
-    function CreateTextPrototype(): Doc {
-        let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 150, backgroundColor: "#f1efeb" });
-        return textProto;
-    }
-    function CreatePdfPrototype(): Doc {
-        let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("annotations"),
-            { x: 0, y: 0, nativeWidth: 1200, width: 300, backgroundLayout: PDFBox.LayoutString(), curPage: 1 });
-        return pdfProto;
-    }
-    function CreateWebPrototype(): Doc {
-        let webProto = setupPrototypeOptions(webProtoId, "WEB_PROTO", WebBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 300 });
-        return webProto;
-    }
-    function CreateCollectionPrototype(): Doc {
-        let collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("data"),
-            { panX: 0, panY: 0, scale: 1, width: 500, height: 500 });
-        return collProto;
-    }
+            protoProps.isPrototype = true;
 
-    function CreateKVPPrototype(): Doc {
-        let kvpProto = setupPrototypeOptions(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 150 });
-        return kvpProto;
-    }
-    function CreateVideoPrototype(): Doc {
-        let videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("annotations"),
-            { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: VideoBox.LayoutString(), curPage: 0 });
-        return videoProto;
-    }
-    function CreateAudioPrototype(): Doc {
-        let audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 150 });
-        return audioProto;
-    }
+            let dataDoc = MakeDataDelegate(proto, protoProps, data);
+            let viewDoc = Doc.MakeDelegate(dataDoc, delegId);
 
-    function CreateInstance(proto: Doc, data: Field, options: DocumentOptions, delegId?: string) {
-        const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys);
-        if (!("author" in protoProps)) {
-            protoProps.author = CurrentUserUtils.email;
+            return Doc.assign(viewDoc, delegateProps);
         }
-        if (!("creationDate" in protoProps)) {
-            protoProps.creationDate = new DateField;
+
+        /**
+         * This function receives the relevant top level document prototype
+         * and models a new instance by delegating from it.
+         * 
+         * Note that it stores the data it recieves at the delegate's data key,
+         * and applies any document options to this new delegate / instance.
+         * @param proto the prototype from which to model this new delegate
+         * @param options initial values to apply to this new delegate
+         * @param value the data to store in this new delegate
+         */
+        function MakeDataDelegate<D extends Field>(proto: Doc, options: DocumentOptions, value: D) {
+            const deleg = Doc.MakeDelegate(proto);
+            deleg.data = value;
+            return Doc.assign(deleg, options);
         }
-        protoProps.isPrototype = true;
 
-        return SetDelegateOptions(SetInstanceOptions(proto, protoProps, data), delegateProps, delegId);
-    }
+        export function ImageDocument(url: string, options: DocumentOptions = {}) {
+            let inst = CreateInstanceFromProto(Prototypes.imageProto, new ImageField(new URL(url)), { title: path.basename(url), ...options });
+            requestImageSize(window.origin + RouteStore.corsProxy + "/" + url)
+                .then((size: any) => {
+                    let aspect = size.height / size.width;
+                    if (!inst.proto!.nativeWidth) {
+                        inst.proto!.nativeWidth = size.width;
+                    }
+                    inst.proto!.nativeHeight = Number(inst.proto!.nativeWidth!) * aspect;
+                    inst.proto!.height = NumCast(inst.proto!.width) * aspect;
+                })
+                .catch((err: any) => console.log(err));
+            return inst;
 
-    export function ImageDocument(url: string, options: DocumentOptions = {}) {
-        let inst = CreateInstance(imageProto, new ImageField(new URL(url)), { title: path.basename(url), ...options });
-        requestImageSize(window.origin + RouteStore.corsProxy + "/" + url)
-            .then((size: any) => {
-                let aspect = size.height / size.width;
-                if (!inst.proto!.nativeWidth) {
-                    inst.proto!.nativeWidth = size.width;
+            // let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] },
+            //     [new URL(url), ImageField]);
+            // doc.SetText(KeyStore.Caption, "my caption...");
+            // doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption());
+            // doc.SetText(KeyStore.OverlayLayout, FixedCaption());
+            // return doc;
+        }
+
+        export function VideoDocument(url: string, options: DocumentOptions = {}) {
+            return CreateInstanceFromProto(Prototypes.videoProto, new VideoField(new URL(url)), options);
+        }
+
+        export function AudioDocument(url: string, options: DocumentOptions = {}) {
+            return CreateInstanceFromProto(Prototypes.audioProto, new AudioField(new URL(url)), options);
+        }
+
+        export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) {
+            return CreateInstanceFromProto(Prototypes.histoProto, new HistogramField(histoOp), options);
+        }
+
+        export function TextDocument(options: DocumentOptions = {}) {
+            return CreateInstanceFromProto(Prototypes.textProto, "", options);
+        }
+
+        export function IconDocument(icon: string, options: DocumentOptions = {}) {
+            return CreateInstanceFromProto(Prototypes.iconProto, new IconField(icon), options);
+        }
+
+        export function PdfDocument(url: string, options: DocumentOptions = {}) {
+            return CreateInstanceFromProto(Prototypes.pdfProto, new PdfField(new URL(url)), options);
+        }
+
+        export async function DBDocument(url: string, options: DocumentOptions = {}, columnOptions: DocumentOptions = {}) {
+            let schemaName = options.title ? options.title : "-no schema-";
+            let ctlog = await Gateway.Instance.GetSchema(url, schemaName);
+            if (ctlog && ctlog.schemas) {
+                let schema = ctlog.schemas[0];
+                let schemaDoc = Docs.Create.TreeDocument([], { ...options, nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: schema.displayName! });
+                let schemaDocuments = Cast(schemaDoc.data, listSpec(Doc), []);
+                if (!schemaDocuments) {
+                    return;
                 }
-                inst.proto!.nativeHeight = Number(inst.proto!.nativeWidth!) * aspect;
-                inst.proto!.height = NumCast(inst.proto!.width) * aspect;
-            })
-            .catch((err: any) => console.log(err));
-        return inst;
-        // let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] },
-        //     [new URL(url), ImageField]);
-        // doc.SetText(KeyStore.Caption, "my caption...");
-        // doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption());
-        // doc.SetText(KeyStore.OverlayLayout, FixedCaption());
-        // return doc;
-    }
-    export function VideoDocument(url: string, options: DocumentOptions = {}) {
-        return CreateInstance(videoProto, new VideoField(new URL(url)), options);
-    }
-    export function AudioDocument(url: string, options: DocumentOptions = {}) {
-        return CreateInstance(audioProto, new AudioField(new URL(url)), options);
-    }
+                CurrentUserUtils.AddNorthstarSchema(schema, schemaDoc);
+                const docs = schemaDocuments;
+                CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
+                    DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
+                        if (field instanceof Doc) {
+                            docs.push(field);
+                        } else {
+                            var atmod = new ColumnAttributeModel(attr);
+                            let histoOp = new HistogramOperation(schema.displayName!,
+                                new AttributeTransformationModel(atmod, AggregateFunction.None),
+                                new AttributeTransformationModel(atmod, AggregateFunction.Count),
+                                new AttributeTransformationModel(atmod, AggregateFunction.Count));
+                            docs.push(Docs.Create.HistogramDocument(histoOp, { ...columnOptions, width: 200, height: 200, title: attr.displayName! }));
+                        }
+                    }));
+                });
+                return schemaDoc;
+            }
+            return Docs.Create.TreeDocument([], { width: 50, height: 100, title: schemaName });
+        }
 
-    export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) {
-        return CreateInstance(histoProto, new HistogramField(histoOp), options);
-    }
-    export function TextDocument(options: DocumentOptions = {}) {
-        return CreateInstance(textProto, "", options);
-    }
-    export function IconDocument(icon: string, options: DocumentOptions = {}) {
-        return CreateInstance(iconProto, new IconField(icon), options);
-    }
-    export function PdfDocument(url: string, options: DocumentOptions = {}) {
-        return CreateInstance(pdfProto, new PdfField(new URL(url)), options);
-    }
+        export function WebDocument(url: string, options: DocumentOptions = {}) {
+            return CreateInstanceFromProto(Prototypes.webProto, new WebField(new URL(url)), options);
+        }
+
+        export function HtmlDocument(html: string, options: DocumentOptions = {}) {
+            return CreateInstanceFromProto(Prototypes.webProto, new HtmlField(html), options);
+        }
 
-    export async function DBDocument(url: string, options: DocumentOptions = {}, columnOptions: DocumentOptions = {}) {
-        let schemaName = options.title ? options.title : "-no schema-";
-        let ctlog = await Gateway.Instance.GetSchema(url, schemaName);
-        if (ctlog && ctlog.schemas) {
-            let schema = ctlog.schemas[0];
-            let schemaDoc = Docs.TreeDocument([], { ...options, nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: schema.displayName! });
-            let schemaDocuments = Cast(schemaDoc.data, listSpec(Doc), []);
-            if (!schemaDocuments) {
-                return;
+        export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
+            return CreateInstanceFromProto(Prototypes.kvpProto, document, { title: document.title + ".kvp", ...options });
+        }
+
+        export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, makePrototype: boolean = true) {
+            if (!makePrototype) {
+                return MakeDataDelegate(Prototypes.collProto, { ...options, viewType: CollectionViewType.Freeform }, new List(documents));
             }
-            CurrentUserUtils.AddNorthstarSchema(schema, schemaDoc);
-            const docs = schemaDocuments;
-            CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
-                DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
-                    if (field instanceof Doc) {
-                        docs.push(field);
-                    } else {
-                        var atmod = new ColumnAttributeModel(attr);
-                        let histoOp = new HistogramOperation(schema.displayName!,
-                            new AttributeTransformationModel(atmod, AggregateFunction.None),
-                            new AttributeTransformationModel(atmod, AggregateFunction.Count),
-                            new AttributeTransformationModel(atmod, AggregateFunction.Count));
-                        docs.push(Docs.HistogramDocument(histoOp, { ...columnOptions, width: 200, height: 200, title: attr.displayName! }));
+            return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Freeform });
+        }
+
+        export function SchemaDocument(schemaColumns: string[], documents: Array<Doc>, options: DocumentOptions) {
+            return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema });
+        }
+
+        export function TreeDocument(documents: Array<Doc>, options: DocumentOptions) {
+            return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree });
+        }
+
+        export function StackingDocument(documents: Array<Doc>, options: DocumentOptions) {
+            return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Stacking });
+        }
+
+        export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
+            return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
+        }
+
+        export type DocConfig = {
+            doc: Doc,
+            initialWidth?: number
+        }
+
+        export function StandardCollectionDockingDocument(configs: Array<DocConfig>, options: DocumentOptions, id?: string, type: string = "row") {
+            let layoutConfig = {
+                content: [
+                    {
+                        type: type,
+                        content: [
+                            ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth))
+                        ]
                     }
-                }));
-            });
-            return schemaDoc;
+                ]
+            };
+            return DockDocument(configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id);
         }
-        return Docs.TreeDocument([], { width: 50, height: 100, title: schemaName });
-    }
-    export function WebDocument(url: string, options: DocumentOptions = {}) {
-        return CreateInstance(webProto, new WebField(new URL(url)), options);
-    }
-    export function HtmlDocument(html: string, options: DocumentOptions = {}) {
-        return CreateInstance(webProto, new HtmlField(html), options);
-    }
-    export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
-        return CreateInstance(kvpProto, document, { title: document.title + ".kvp", ...options });
-    }
-    export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, makePrototype: boolean = true) {
-        if (!makePrototype) {
-            return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, new List(documents));
+
+        export function CaptionDocument(doc: Doc) {
+            const captionDoc = Doc.MakeAlias(doc);
+            captionDoc.overlayLayout = Templating.FixedCaption();
+            captionDoc.width = Cast(doc.width, "number", 0);
+            captionDoc.height = Cast(doc.height, "number", 0);
+            return captionDoc;
         }
-        return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Freeform });
-    }
-    export function SchemaDocument(schemaColumns: string[], documents: Array<Doc>, options: DocumentOptions) {
-        return CreateInstance(collProto, new List(documents), { schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema });
-    }
-    export function TreeDocument(documents: Array<Doc>, options: DocumentOptions) {
-        return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree });
-    }
-    export function StackingDocument(documents: Array<Doc>, options: DocumentOptions) {
-        return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Stacking });
-    }
-    export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
-        return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
-    }
-    export type DocConfig = {
-        doc: Doc,
-        initialWidth?: number
-    }
-    export function StandardCollectionDockingDocument(configs: Array<DocConfig>, options: DocumentOptions, id?: string, type: string = "row") {
-        let layoutConfig = {
-            content: [
-                {
-                    type: type,
-                    content: [
-                        ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth))
-                    ]
-                }
-            ]
-        };
-        return DockDocument(configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id);
     }
 
-    export function CaptionDocument(doc: Doc) {
-        const captionDoc = Doc.MakeAlias(doc);
-        captionDoc.overlayLayout = FixedCaption();
-        captionDoc.width = Cast(doc.width, "number", 0);
-        captionDoc.height = Cast(doc.height, "number", 0);
-        return captionDoc;
-    }
+    export namespace Templating {
 
-    // example of custom display string for an image that shows a caption.
-    function EmbeddedCaption() {
-        return `<div style="height:100%">
-            <div style="position:relative; margin:auto; height:85%; width:85%;" >`
-            + ImageBox.LayoutString() +
-            `</div>
-            <div style="position:relative; height:15%; text-align:center; ">`
-            + FormattedTextBox.LayoutString("caption") +
-            `</div> 
-        </div>`;
-    }
-    export function FixedCaption(fieldName: string = "caption") {
-        return `<div style="position:absolute; height:30px; bottom:0; width:100%">
-            <div style="position:absolute; width:100%; height:100%; text-align:center;bottom:0;">`
-            + FormattedTextBox.LayoutString(fieldName) +
-            `</div> 
-        </div>`;
-    }
+        // example of custom display string for an image that shows a caption.
+        export function EmbeddedCaption() {
+            return `<div style="height:100%">
+                <div style="position:relative; margin:auto; height:85%; width:85%;" >`
+                + ImageBox.LayoutString() +
+                `</div>
+                <div style="position:relative; height:15%; text-align:center; ">`
+                + FormattedTextBox.LayoutString("caption") +
+                `</div> 
+            </div>`;
+        }
 
-    function OuterCaption() {
-        return (`
-<div>
-    <div style="margin:auto; height:calc(100%); width:100%;">
-        {layout}
-    </div>
-    <div style="height:(100% + 25px); width:100%; position:absolute">
-        <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
-    </div>
-</div>       
-        `);
-    }
-    function InnerCaption() {
-        return (`
-    <div>
-        <div style="margin:auto; height:calc(100% - 25px); width:100%;">
-            {layout}
-        </div>
-        <div style="height:25px; width:100%; position:absolute">
-            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
-        </div>
-    </div>       
+        export function FixedCaption(fieldName: string = "caption") {
+            return `<div style="position:absolute; height:30px; bottom:0; width:100%">
+                <div style="position:absolute; width:100%; height:100%; text-align:center;bottom:0;">`
+                + FormattedTextBox.LayoutString(fieldName) +
+                `</div> 
+            </div>`;
+        }
+
+        export function OuterCaption() {
+            return (`
+                <div>
+                    <div style="margin:auto; height:calc(100%); width:100%;">
+                        {layout}
+                    </div>
+                    <div style="height:(100% + 25px); width:100%; position:absolute">
+                        <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+                    </div>
+                </div>       
             `);
-    }
+        }
 
-    /*
+        export function InnerCaption() {
+            return (`
+                <div>
+                    <div style="margin:auto; height:calc(100% - 25px); width:100%;">
+                        {layout}
+                    </div>
+                    <div style="height:25px; width:100%; position:absolute">
+                        <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+                    </div>
+                </div>       
+            `);
+        }
 
-    this template requires an additional style setting on the collectionView-cont to make the layout relative
-    
-.collectionView-cont {
-    position: relative;
-    width: 100%;
-    height: 100%;
-}
-    */
-    function Percentaption() {
-        return (`
-    <div>
-        <div style="margin:auto; height:85%; width:85%;">
-            {layout}
-        </div>
-        <div style="height:15%; width:100%; position:absolute">
-            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
-        </div>
-    </div>       
+        /*
+        this template requires an additional style setting on the collectionView-cont to make the layout relative
+            .collectionView-cont {
+                position: relative;
+                width: 100%;
+                height: 100%;
+            }
+        */
+        export function PercentCaption() {
+            return (`
+                <div>
+                    <div style="margin:auto; height:85%; width:85%;">
+                        {layout}
+                    </div>
+                    <div style="height:15%; width:100%; position:absolute">
+                        <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+                    </div>
+                </div>       
             `);
+        }
+
+    }
+}
+
+export namespace DocUtils {
+
+    export function MakeLink(source: Doc, target: Doc) {
+        let protoSrc = source.proto ? source.proto : source;
+        let protoTarg = target.proto ? target.proto : target;
+        UndoManager.RunInBatch(() => {
+            let linkDoc = Docs.Create.TextDocument({ width: 100, height: 30, borderRounding: -1 });
+            //let linkDoc = new Doc;
+            linkDoc.proto!.title = "-link name-";
+            linkDoc.proto!.linkDescription = "";
+            linkDoc.proto!.linkTags = "Default";
+
+            linkDoc.proto!.linkedTo = target;
+            linkDoc.proto!.linkedToPage = target.curPage;
+            linkDoc.proto!.linkedFrom = source;
+            linkDoc.proto!.linkedFromPage = source.curPage;
+
+            let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc));
+            if (!linkedFrom) {
+                protoTarg.linkedFromDocs = linkedFrom = new List<Doc>();
+            }
+            linkedFrom.push(linkDoc);
+
+            let linkedTo = Cast(protoSrc.linkedToDocs, listSpec(Doc));
+            if (!linkedTo) {
+                protoSrc.linkedToDocs = linkedTo = new List<Doc>();
+            }
+            linkedTo.push(linkDoc);
+            return linkDoc;
+        }, "make link");
     }
+
 }
\ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index da9b1253e..787033455 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -274,7 +274,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @undoBatch
     @action createIcon = (selected: DocumentView[], layoutString: string): Doc => {
         let doc = selected[0].props.Document;
-        let iconDoc = Docs.IconDocument(layoutString);
+        let iconDoc = Docs.Create.IconDocument(layoutString);
         iconDoc.isButton = true;
         iconDoc.proto!.title = selected.length > 1 ? "-multiple-.icon" : StrCast(doc.title) + ".icon";
         iconDoc.labelField = selected.length > 1 ? undefined : this._fieldKey;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 3d9750a85..98b14f9c8 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -5,7 +5,7 @@ import * as ReactDOM from 'react-dom';
 import * as React from 'react';
 
 (async () => {
-    await Docs.initProtos();
+    await Docs.Prototypes.initialize();
     await CurrentUserUtils.loadCurrentUser();
     ReactDOM.render(<MainView />, document.getElementById('root'));
 })();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 426e2440a..984db0426 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -55,7 +55,7 @@ export class MainView extends React.Component {
     private set mainContainer(doc: Opt<Doc>) {
         if (doc) {
             if (!("presentationView" in doc)) {
-                doc.presentationView = Docs.TreeDocument([], { title: "Presentation" });
+                doc.presentationView = Docs.Create.TreeDocument([], { title: "Presentation" });
             }
             CurrentUserUtils.UserDocument.activeWorkspace = doc;
         }
@@ -151,12 +151,12 @@ export class MainView extends React.Component {
     createNewWorkspace = async (id?: string) => {
         const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
         if (list) {
-            let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` });
+            let freeformDoc = Docs.Create.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` });
             let configs = [
                 { doc: CurrentUserUtils.UserDocument, initialWidth: 150 },
                 { doc: freeformDoc, initialWidth: 600 }
             ]
-            let mainDoc = Docs.StandardCollectionDockingDocument(configs, { title: `Workspace ${list.length + 1}` }, id);
+            let mainDoc = Docs.Create.StandardCollectionDockingDocument(configs, { title: `Workspace ${list.length + 1}` }, id);
             list.push(mainDoc);
             // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
             setTimeout(() => {
@@ -242,18 +242,18 @@ export class MainView extends React.Component {
         let audiourl = "http://techslides.com/demos/samples/sample.mp3";
         let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
 
-        let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
-        let addColNode = action(() => Docs.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
-        let addDockingNode = action(() => Docs.StandardCollectionDockingDocument([{ doc: addColNode(), initialWidth: 200 }], { width: 200, height: 200, title: "a nested docking freeform collection" }));
-        let addSchemaNode = action(() => Docs.SchemaDocument(["title"], [], { width: 200, height: 200, title: "a schema collection" }));
+        let addTextNode = action(() => Docs.Create.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
+        let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
+        let addDockingNode = action(() => Docs.Create.StandardCollectionDockingDocument([{ doc: addColNode(), initialWidth: 200 }], { width: 200, height: 200, title: "a nested docking freeform collection" }));
+        let addSchemaNode = action(() => Docs.Create.SchemaDocument(["title"], [], { width: 200, height: 200, title: "a schema collection" }));
         let addTreeNode = action(() => CurrentUserUtils.UserDocument);
         //let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" }));
         // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy"  }));
-        let addVideoNode = action(() => Docs.VideoDocument(videourl, { width: 200, title: "video node" }));
-        let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
-        let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
-        let addWebNode = action(() => Docs.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
-        let addAudioNode = action(() => Docs.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
+        let addVideoNode = action(() => Docs.Create.VideoDocument(videourl, { width: 200, title: "video node" }));
+        let addPDFNode = action(() => Docs.Create.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
+        let addImageNode = action(() => Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
+        let addWebNode = action(() => Docs.Create.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
+        let addAudioNode = action(() => Docs.Create.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
 
         let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
             [React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 63d2065e2..7164d98a4 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -166,7 +166,7 @@ export class SearchBox extends React.Component {
                 y += 300;
             }
         }
-        return Docs.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this.searchString}"` });
+        return Docs.Create.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this.searchString}"` });
     }
 
     // Useful queries:
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index dfb8fac35..e2bcb10ec 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -6,50 +6,32 @@ import * as ReactDOM from 'react-dom';
 import Measure, { ContentRect } from "react-measure";
 import * as GoldenLayout from "../../../client/goldenLayout";
 import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc";
-import { FieldId } from "../../../new_fields/RefField";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { emptyFunction, returnTrue, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { DragLinksAsDocuments, DragManager } from "../../util/DragManager";
-import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionDockingView.scss";
 import { SubCollectionViewProps } from "./CollectionSubView";
 import React = require("react");
 import { ParentDocSelector } from './ParentDocumentSelector';
 import { DocumentManager } from '../../util/DocumentManager';
-import { CollectionViewType } from './CollectionBaseView';
 import { Id } from '../../../new_fields/FieldSymbols';
-import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { DockedFrameRenderer } from './DockedFrameRenderer';
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
     public static TopLevel: CollectionDockingView;
-    public static makeDocumentConfig(document: Doc, width?: number) {
-        return {
-            type: 'react-component',
-            component: 'DocumentFrameRenderer',
-            title: document.title,
-            width: width,
-            props: {
-                documentId: document[Id],
-            }
-        };
-    }
-
-    private makeDocConfig = (document: Doc, width?: number) => {
-        const config = CollectionDockingView.makeDocumentConfig(document, width);
-        (config.props as any).parent = this;
-        return config;
-    }
-
     private _goldenLayout: any = null;
     private _containerRef = React.createRef<HTMLDivElement>();
+    reactionDisposer?: IReactionDisposer;
+    _removedDocs: Doc[] = [];
     private _flush: boolean = false;
     private _ignoreStateChange = "";
     private _isPointerDown = false;
+    hack: boolean = false;
+    undohack: any = null;
 
     constructor(props: SubCollectionViewProps) {
         super(props);
@@ -57,32 +39,93 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         (window as any).React = React;
         (window as any).ReactDOM = ReactDOM;
     }
-    hack: boolean = false;
-    undohack: any = null;
-    public StartOtherDrag(dragDocs: Doc[], e: any) {
-        this.hack = true;
-        this.undohack = UndoManager.StartBatch("goldenDrag");
-        dragDocs.map(dragDoc =>
-            CollectionDockingView.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.
-                onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
+
+    componentDidMount: () => void = () => {
+        if (this._containerRef.current) {
+            this.reactionDisposer = reaction(
+                () => StrCast(this.props.Document.dockingConfig),
+                () => {
+                    if (!this._goldenLayout || this._ignoreStateChange !== this.retrieveConfiguration()) {
+                        // Because this is in a set timeout, if this component unmounts right after mounting,
+                        // we will leak a GoldenLayout, because we try to destroy it before we ever create it
+                        setTimeout(() => this.setupGoldenLayout(), 1);
+                    }
+                    this._ignoreStateChange = "";
+                }, { fireImmediately: true });
+
+            // window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
+        }
     }
 
-    private openFullScreen = (document: Doc) => {
-        let newItemStackConfig = {
-            type: 'stack',
-            content: [this.makeDocConfig(document)]
-        };
-        var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
-        this._goldenLayout.root.contentItems[0].addChild(docconfig);
-        docconfig.callDownwards('_$init');
-        this._goldenLayout._$maximiseItem(docconfig);
-        this._ignoreStateChange = this.retrieveConfiguration();
-        this.stateChanged();
+    componentWillUnmount: () => void = () => {
+        try {
+            this._goldenLayout.unbind('itemDropped', this.itemDropped);
+            this._goldenLayout.unbind('tabCreated', this.tabCreated);
+            this._goldenLayout.unbind('stackCreated', this.stackCreated);
+            this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
+        } catch (e) {
+            console.log("Unable to unbind Golden Layout event listener...", e);
+        }
+        if (this._goldenLayout) this._goldenLayout.destroy();
+        this._goldenLayout = null;
+
+        if (this.reactionDisposer) {
+            this.reactionDisposer();
+        }
     }
 
-    @action
-    public static OpenFullScreen(document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) {
-        dockingView.openFullScreen(document);
+    setupGoldenLayout() {
+        var config = StrCast(this.props.Document.dockingConfig);
+        if (config) {
+            if (!this._goldenLayout) {
+                this.initializeConfiguration(config);
+            }
+            else {
+                if (config === this.retrieveConfiguration()) {
+                    return;
+                }
+                try {
+                    this._goldenLayout.unbind('itemDropped', this.itemDropped);
+                    this._goldenLayout.unbind('tabCreated', this.tabCreated);
+                    this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
+                    this._goldenLayout.unbind('stackCreated', this.stackCreated);
+                } catch (e) { }
+                this._goldenLayout.destroy();
+                this.initializeConfiguration(config);
+            }
+            this._goldenLayout.on('itemDropped', this.itemDropped);
+            this._goldenLayout.on('tabCreated', this.tabCreated);
+            this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
+            this._goldenLayout.on('stackCreated', this.stackCreated);
+            this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
+            this._goldenLayout.container = this._containerRef.current;
+            if (this._goldenLayout.config.maximisedItemId === '__glMaximised') {
+                try {
+                    this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise();
+                } catch (e) {
+                    this._goldenLayout.config.maximisedItemId = null;
+                }
+            }
+            this._goldenLayout.init();
+        }
+    }
+
+    private makeDocConfig = (document: Doc, width?: number) => {
+        const config = CollectionDockingView.makeDocumentConfig(document, width);
+        (config.props as any).parent = this;
+        return config;
+    }
+
+    public static makeDocumentConfig(document: Doc, width?: number) {
+        return {
+            type: 'react-component',
+            component: 'DocumentFrameRenderer',
+            title: document.title,
+            width: width,
+            props: {
+                documentId: document[Id],
+            }
+        };
     }
 
     initializeConfiguration = (configText: string) => {
@@ -109,44 +152,29 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         });
     }
 
-    @undoBatch
-    @action
-    public static CloseRightSplit = (document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel): boolean => {
-        let retVal = false;
-        if (dockingView._goldenLayout.root.contentItems[0].isRow) {
-            retVal = Array.from(dockingView._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
-                if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
-                    Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) {
-                    child.contentItems[0].remove();
-                    dockingView.layoutChanged(document);
-                    return true;
-                } else {
-                    Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
-                        if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {
-                            child.contentItems[j].remove();
-                            child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
-                            let docs = Cast(dockingView.props.Document.data, listSpec(Doc));
-                            docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1);
-                            return true;
-                        }
-                        return false;
-                    });
-                }
-                return false;
-            });
-        }
-        if (retVal) {
-            dockingView.stateChanged();
-        }
-        return retVal;
+    public StartOtherDrag(dragDocs: Doc[], e: any) {
+        this.hack = true;
+        this.undohack = UndoManager.StartBatch("goldenDrag");
+        dragDocs.map(dragDoc =>
+            CollectionDockingView.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.
+                onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
     }
 
     @action
-    layoutChanged(removed?: Doc) {
-        this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
-        this._goldenLayout.emit('stateChanged');
+    public static OpenFullScreen(document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) {
+        dockingView.openFullScreen(document);
+    }
+
+    private openFullScreen = (document: Doc) => {
+        let newItemStackConfig = {
+            type: 'stack',
+            content: [this.makeDocConfig(document)]
+        };
+        var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
+        this._goldenLayout.root.contentItems[0].addChild(docconfig);
+        docconfig.callDownwards('_$init');
+        this._goldenLayout._$maximiseItem(docconfig);
         this._ignoreStateChange = this.retrieveConfiguration();
-        if (removed) CollectionDockingView.TopLevel._removedDocs.push(removed);
         this.stateChanged();
     }
 
@@ -209,75 +237,47 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         this.layoutChanged();
     }
 
-    setupGoldenLayout() {
-        var config = StrCast(this.props.Document.dockingConfig);
-        if (config) {
-            if (!this._goldenLayout) {
-                this.initializeConfiguration(config);
-            }
-            else {
-                if (config === this.retrieveConfiguration()) {
-                    return;
-                }
-                try {
-                    this._goldenLayout.unbind('itemDropped', this.itemDropped);
-                    this._goldenLayout.unbind('tabCreated', this.tabCreated);
-                    this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
-                    this._goldenLayout.unbind('stackCreated', this.stackCreated);
-                } catch (e) { }
-                this._goldenLayout.destroy();
-                this.initializeConfiguration(config);
-            }
-            this._goldenLayout.on('itemDropped', this.itemDropped);
-            this._goldenLayout.on('tabCreated', this.tabCreated);
-            this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
-            this._goldenLayout.on('stackCreated', this.stackCreated);
-            this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
-            this._goldenLayout.container = this._containerRef.current;
-            if (this._goldenLayout.config.maximisedItemId === '__glMaximised') {
-                try {
-                    this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise();
-                } catch (e) {
-                    this._goldenLayout.config.maximisedItemId = null;
+    @undoBatch
+    @action
+    public static CloseRightSplit = (document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel): boolean => {
+        let retVal = false;
+        if (dockingView._goldenLayout.root.contentItems[0].isRow) {
+            retVal = Array.from(dockingView._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
+                if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
+                    Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) {
+                    child.contentItems[0].remove();
+                    dockingView.layoutChanged(document);
+                    return true;
+                } else {
+                    Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
+                        if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {
+                            child.contentItems[j].remove();
+                            child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
+                            let docs = Cast(dockingView.props.Document.data, listSpec(Doc));
+                            docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1);
+                            return true;
+                        }
+                        return false;
+                    });
                 }
-            }
-            this._goldenLayout.init();
+                return false;
+            });
         }
-    }
-    reactionDisposer?: IReactionDisposer;
-    componentDidMount: () => void = () => {
-        if (this._containerRef.current) {
-            this.reactionDisposer = reaction(
-                () => StrCast(this.props.Document.dockingConfig),
-                () => {
-                    if (!this._goldenLayout || this._ignoreStateChange !== this.retrieveConfiguration()) {
-                        // Because this is in a set timeout, if this component unmounts right after mounting,
-                        // we will leak a GoldenLayout, because we try to destroy it before we ever create it
-                        setTimeout(() => this.setupGoldenLayout(), 1);
-                    }
-                    this._ignoreStateChange = "";
-                }, { fireImmediately: true });
-
-            // window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
+        if (retVal) {
+            dockingView.stateChanged();
         }
+        return retVal;
     }
-    componentWillUnmount: () => void = () => {
-        try {
-            this._goldenLayout.unbind('itemDropped', this.itemDropped);
-            this._goldenLayout.unbind('tabCreated', this.tabCreated);
-            this._goldenLayout.unbind('stackCreated', this.stackCreated);
-            this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
-        } catch (e) {
 
-        }
-        if (this._goldenLayout) this._goldenLayout.destroy();
-        this._goldenLayout = null;
-        // window.removeEventListener('resize', this.onResize);
-
-        if (this.reactionDisposer) {
-            this.reactionDisposer();
-        }
+    @action
+    layoutChanged(removed?: Doc) {
+        this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
+        this._goldenLayout.emit('stateChanged');
+        this._ignoreStateChange = this.retrieveConfiguration();
+        if (removed) CollectionDockingView.TopLevel._removedDocs.push(removed);
+        this.stateChanged();
     }
+
     @action
     onResize = (size: ContentRect) => {
         // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
@@ -387,6 +387,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         }
                         tab.setActive(true);
                     }
+                    tab.header.element[0].ondrop = (e: any) => {
+                        console.log("DROPPPP THE BASS!", e);
+                    }
                     ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.AddTab(stack, doc)} />, upDiv);
                     tab.reactComponents = [upDiv];
                     tab.element.append(upDiv);
@@ -422,7 +425,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             }
         }
     }
-    _removedDocs: Doc[] = [];
 
     private stackCreated = (stack: any) => {
         //stack.header.controlsContainer.find('.lm_popout').hide();
@@ -462,102 +464,4 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         );
     }
 
-}
-
-interface DockedFrameProps {
-    documentId: FieldId;
-    glContainer: any;
-    glEventHub: any;
-    parent: CollectionDockingView;
-}
-
-@observer
-export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
-    _mainCont = React.createRef<HTMLDivElement>();
-    @observable private _panelWidth = 0;
-    @observable private _panelHeight = 0;
-    @observable private _document: Opt<Doc>;
-    private get parentProps(): SubCollectionViewProps {
-        return this.props.parent.props;
-    }
-
-    get _stack(): any {
-        let parent = this.props.glContainer.parent.parent;
-        if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1)
-            return parent.parent.contentItems[1];
-        return parent;
-    }
-    constructor(props: any) {
-        super(props);
-        DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
-    }
-
-    nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
-    nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight);
-    contentScaling = () => {
-        const nativeH = this.nativeHeight();
-        const nativeW = this.nativeWidth();
-        let wscale = this._panelWidth / nativeW;
-        return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
-    }
-
-    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;
-            return this.parentProps.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
-        }
-        return Transform.Identity();
-    }
-    get scaleToFitMultiplier() {
-        let docWidth = NumCast(this._document!.width);
-        let docHeight = NumCast(this._document!.height);
-        if (NumCast(this._document!.nativeWidth) || !docWidth || !this._panelWidth || !this._panelHeight) return 1;
-        if (StrCast(this._document!.layout).indexOf("Collection") === -1 ||
-            NumCast(this._document!.viewType) !== CollectionViewType.Freeform) return 1;
-        let scaling = Math.max(1, this._panelWidth / docWidth * docHeight > this._panelHeight ?
-            this._panelHeight / docHeight : this._panelWidth / docWidth);
-        return scaling;
-    }
-    get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
-
-    addDocTab = (doc: Doc, location: string) => {
-        if (location === "onRight") {
-            CollectionDockingView.AddRightSplit(doc);
-        } else {
-            CollectionDockingView.AddTab(this._stack, doc);
-        }
-    }
-    get content() {
-        if (!this._document) {
-            return (null);
-        }
-        return (
-            <div className="collectionDockingView-content" ref={this._mainCont}
-                style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier}, ${this.scaleToFitMultiplier})` }}>
-                <DocumentView key={this._document[Id]} Document={this._document}
-                    bringToFront={emptyFunction}
-                    addDocument={undefined}
-                    removeDocument={undefined}
-                    ContentScaling={this.contentScaling}
-                    PanelWidth={this.nativeWidth}
-                    PanelHeight={this.nativeHeight}
-                    ScreenToLocalTransform={this.ScreenToLocalTransform}
-                    isTopMost={true}
-                    selectOnLoad={false}
-                    parentActive={returnTrue}
-                    whenActiveChanged={emptyFunction}
-                    focus={emptyFunction}
-                    addDocTab={this.addDocTab}
-                    ContainingCollectionView={undefined} />
-            </div >);
-    }
-
-    render() {
-        let theContent = this.content;
-        return !this._document ? (null) :
-            <Measure offset onResize={action((r: any) => { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}>
-                {({ measureRef }) => <div ref={measureRef}>  {theContent} </div>}
-            </Measure>;
-    }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 11d71d023..477879b79 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -261,7 +261,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
         let dbName = StrCast(this.props.Document.title);
         let res = await Gateway.Instance.PostSchema(csv, dbName);
         if (self.props.CollectionView.props.addDocument) {
-            let schemaDoc = await Docs.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document });
+            let schemaDoc = await Docs.Create.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document });
             if (schemaDoc) {
                 //self.props.CollectionView.props.addDocument(schemaDoc, false);
                 self.props.Document.schemaDoc = schemaDoc;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index be37efd3d..440a2410b 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -113,20 +113,20 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         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 = Docs.ImageDocument;
+                ctor = Docs.Create.ImageDocument;
             }
             if (type.indexOf("video") !== -1) {
-                ctor = Docs.VideoDocument;
+                ctor = Docs.Create.VideoDocument;
             }
             if (type.indexOf("audio") !== -1) {
-                ctor = Docs.AudioDocument;
+                ctor = Docs.Create.AudioDocument;
             }
             if (type.indexOf("pdf") !== -1) {
-                ctor = Docs.PdfDocument;
+                ctor = Docs.Create.PdfDocument;
                 options.nativeWidth = 1200;
             }
             if (type.indexOf("excel") !== -1) {
-                ctor = Docs.DBDocument;
+                ctor = Docs.Create.DBDocument;
                 options.dropAction = "copy";
             }
             if (type.indexOf("html") !== -1) {
@@ -145,7 +145,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     });
                     return undefined;
                 }
-                ctor = Docs.WebDocument;
+                ctor = Docs.Create.WebDocument;
                 options = { height: options.width, ...options, title: path, nativeWidth: undefined };
             }
             return ctor ? ctor(path, options) : undefined;
@@ -175,13 +175,13 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 return;
             }
             if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
-                let htmlDoc = Docs.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text });
+                let htmlDoc = Docs.Create.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text });
                 this.props.addDocument(htmlDoc, false);
                 return;
             }
             if (text && text.indexOf("www.youtube.com/watch") !== -1) {
                 const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
-                this.props.addDocument(Docs.WebDocument(url, { ...options, width: 300, height: 300 }));
+                this.props.addDocument(Docs.Create.WebDocument(url, { ...options, width: 300, height: 300 }));
                 return;
             }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b0a310ec1..424042fee 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -142,7 +142,7 @@ class TreeView extends React.Component<TreeViewProps> {
     onWorkspaceContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.props.document)) });
-            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" });
+            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" });
             if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
                 ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, "inTab"), icon: "folder" });
                 ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight"), icon: "caret-square-right" });
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 7853544d5..bd5cd5450 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -98,7 +98,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
                 SearchBox.convertDataUri(dataUrl, filename).then((returnedFilename) => {
                     if (returnedFilename) {
                         let url = DocServer.prepend(returnedFilename);
-                        let imageSummary = Docs.ImageDocument(url, {
+                        let imageSummary = Docs.Create.ImageDocument(url, {
                             x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
                             width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
                         });
diff --git a/src/client/views/collections/DockedFrameRenderer.tsx b/src/client/views/collections/DockedFrameRenderer.tsx
new file mode 100644
index 000000000..25d4b2a49
--- /dev/null
+++ b/src/client/views/collections/DockedFrameRenderer.tsx
@@ -0,0 +1,116 @@
+import 'golden-layout/src/css/goldenlayout-base.css';
+import 'golden-layout/src/css/goldenlayout-dark-theme.css';
+import { action, observable, reaction, Lambda, IReactionDisposer } from "mobx";
+import { observer } from "mobx-react";
+import Measure, { ContentRect } from "react-measure";
+import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc";
+import { FieldId } from "../../../new_fields/RefField";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, returnTrue, Utils } from "../../../Utils";
+import { DocServer } from "../../DocServer";
+import { Transform } from '../../util/Transform';
+import { DocumentView } from "../nodes/DocumentView";
+import "./CollectionDockingView.scss";
+import { SubCollectionViewProps } from "./CollectionSubView";
+import React = require("react");
+import { CollectionViewType } from './CollectionBaseView';
+import { Id } from '../../../new_fields/FieldSymbols';
+import { CollectionDockingView } from './CollectionDockingView';
+
+interface DockedFrameProps {
+    documentId: FieldId;
+    glContainer: any;
+    glEventHub: any;
+    parent: CollectionDockingView;
+}
+
+@observer
+export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
+    _mainCont = React.createRef<HTMLDivElement>();
+    @observable private _panelWidth = 0;
+    @observable private _panelHeight = 0;
+    @observable private _document: Opt<Doc>;
+    private get parentProps(): SubCollectionViewProps {
+        return this.props.parent.props;
+    }
+
+    get _stack(): any {
+        let parent = this.props.glContainer.parent.parent;
+        if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1)
+            return parent.parent.contentItems[1];
+        return parent;
+    }
+    constructor(props: any) {
+        super(props);
+        DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
+    }
+
+    nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
+    nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight);
+    contentScaling = () => {
+        const nativeH = this.nativeHeight();
+        const nativeW = this.nativeWidth();
+        let wscale = this._panelWidth / nativeW;
+        return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
+    }
+
+    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;
+            return this.parentProps.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
+        }
+        return Transform.Identity();
+    }
+    get scaleToFitMultiplier() {
+        let docWidth = NumCast(this._document!.width);
+        let docHeight = NumCast(this._document!.height);
+        if (NumCast(this._document!.nativeWidth) || !docWidth || !this._panelWidth || !this._panelHeight) return 1;
+        if (StrCast(this._document!.layout).indexOf("Collection") === -1 ||
+            NumCast(this._document!.viewType) !== CollectionViewType.Freeform) return 1;
+        let scaling = Math.max(1, this._panelWidth / docWidth * docHeight > this._panelHeight ?
+            this._panelHeight / docHeight : this._panelWidth / docWidth);
+        return scaling;
+    }
+    get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
+
+    addDocTab = (doc: Doc, location: string) => {
+        if (location === "onRight") {
+            CollectionDockingView.AddRightSplit(doc);
+        } else {
+            CollectionDockingView.AddTab(this._stack, doc);
+        }
+    }
+    get content() {
+        if (!this._document) {
+            return (null);
+        }
+        return (
+            <div className="collectionDockingView-content" ref={this._mainCont}
+                style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier}, ${this.scaleToFitMultiplier})` }}>
+                <DocumentView key={this._document[Id]} Document={this._document}
+                    bringToFront={emptyFunction}
+                    addDocument={undefined}
+                    removeDocument={undefined}
+                    ContentScaling={this.contentScaling}
+                    PanelWidth={this.nativeWidth}
+                    PanelHeight={this.nativeHeight}
+                    ScreenToLocalTransform={this.ScreenToLocalTransform}
+                    isTopMost={true}
+                    selectOnLoad={false}
+                    parentActive={returnTrue}
+                    whenActiveChanged={emptyFunction}
+                    focus={emptyFunction}
+                    addDocTab={this.addDocTab}
+                    ContainingCollectionView={undefined} />
+            </div >);
+    }
+
+    render() {
+        let theContent = this.content;
+        return !this._document ? (null) :
+            <Measure offset onResize={action((r: any) => { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}>
+                {({ measureRef }) => <div ref={measureRef}>  {theContent} </div>}
+            </Measure>;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 29734fa19..cd386abfa 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -79,7 +79,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 }
                 ns.map(line => {
                     let indent = line.search(/\S|$/);
-                    let newBox = Docs.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line });
+                    let newBox = Docs.Create.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line });
                     this.props.addDocument(newBox, false);
                     y += 40 * this.props.getTransform().Scale;
                 });
@@ -89,13 +89,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             navigator.clipboard.readText().then(text => {
                 let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
                 if (ns.length === 1 && text.startsWith("http")) {
-                    this.props.addDocument(Docs.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer
+                    this.props.addDocument(Docs.Create.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer
                 } else {
                     this.pasteTable(ns, x, y);
                 }
             });
         } else {
-            let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" });
+            let newBox = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" });
             this.props.addLiveTextDocument(newBox);
         }
         e.stopPropagation();
@@ -136,7 +136,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 doc.width = 200;
                 docList.push(doc);
             }
-            let newCol = Docs.SchemaDocument([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 });
+            let newCol = Docs.Create.SchemaDocument([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 });
 
             this.props.addDocument(newCol, false);
         }
@@ -259,7 +259,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             let ink = Cast(this.props.container.props.Document.ink, InkField);
             let inkData = ink ? ink.inkData : undefined;
             let zoomBasis = NumCast(this.props.container.props.Document.scale, 1);
-            let newCollection = Docs.FreeformDocument(selected, {
+            let newCollection = Docs.Create.FreeformDocument(selected, {
                 x: bounds.left,
                 y: bounds.top,
                 panX: 0,
@@ -283,14 +283,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     d.page = -1;
                     return d;
                 });
-                let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
+                let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
                 newCollection.proto!.summaryDoc = summary;
                 selected = [newCollection];
                 newCollection.x = bounds.left + bounds.width;
                 summary.proto!.subBulletDocs = new List<Doc>(selected);
                 //summary.proto!.maximizeLocation = "inTab";  // or "inPlace", or "onRight"
                 summary.templates = new List<string>([Templates.Bullet.Layout]);
-                let container = Docs.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, title: "-summary-" });
+                let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, title: "-summary-" });
                 container.viewType = CollectionViewType.Stacking;
                 this.props.addLiveTextDocument(container);
                 // });
@@ -303,11 +303,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                         d.page = -1;
                         return d;
                     });
-                    let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
+                    let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
                     SearchBox.convertDataUri(dataUrl, "icon" + summary[Id] + "_image").then((returnedFilename) => {
                         if (returnedFilename) {
                             let url = DocServer.prepend(returnedFilename);
-                            let imageSummary = Docs.ImageDocument(url, {
+                            let imageSummary = Docs.Create.ImageDocument(url, {
                                 x: bounds.left, y: bounds.top + 100 / zoomBasis,
                                 width: 150, height: bounds.height / bounds.width * 150, title: "-summary image-"
                             });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index efba26c2c..16e40000d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -302,7 +302,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); }
-    fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight") };
+    fieldsClicked = (): void => { this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight") };
     makeBtnClicked = (): void => {
         let doc = Doc.GetProto(this.props.Document);
         doc.isButton = !BoolCast(doc.isButton, false);
@@ -418,7 +418,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({
             description: "Find aliases", event: async () => {
                 const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
-                this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), "onRight");
+                this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), "onRight");
             }, icon: "search"
         });
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document), icon: "crosshairs" });
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index bfc1738fc..a8f94b746 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -33,7 +33,7 @@ class Uploader extends React.Component {
     onClick = async () => {
         try {
             this.status = "initializing protos";
-            await Docs.initProtos();
+            await Docs.Prototypes.initialize();
             let imgPrev = document.getElementById("img_preview");
             if (imgPrev) {
                 let files: FileList | null = inputRef.current!.files;
@@ -53,7 +53,7 @@ class Uploader extends React.Component {
                     const json = await res.json();
                     json.map(async (file: any) => {
                         let path = window.location.origin + file;
-                        var doc = Docs.ImageDocument(path, { nativeWidth: 200, width: 200, title: name });
+                        var doc = Docs.Create.ImageDocument(path, { nativeWidth: 200, width: 200, title: name });
 
                         this.status = "getting user document";
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 7f7263cf1..af65f5482 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -172,6 +172,18 @@ export namespace Doc {
         }
         return protos;
     }
+
+    /**
+     * This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies
+     * the values of the properties of a source object into the target.
+     * 
+     * This is just a specific, Dash-authored version that serves the same role for our
+     * Doc class.
+     * 
+     * @param doc the target document into which you'd like to insert the new fields 
+     * @param fields the fields to project onto the target. Its type signature defines a mapping from some string key
+     * to a potentially undefined field, where each entry in this mapping is optional. 
+     */
     export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>) {
         for (const key in fields) {
             if (fields.hasOwnProperty(key)) {
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 2b304c373..8caceb063 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -60,6 +60,7 @@ export function getter(target: any, prop: string | symbol | number, receiver: an
     }
     return getField(target, prop);
 }
+
 function getProtoField(protoField: Doc | undefined, prop: string | number, cb?: (field: Field | undefined) => void) {
     if (!protoField) return undefined;
     let field = protoField[prop];
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index e5b7a025b..169be3b99 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -33,8 +33,8 @@ export class CurrentUserUtils {
         doc.title = this.email;
         doc.data = new List<Doc>();
         doc.excludeFromLibrary = true;
-        doc.optionalRightCollection = Docs.StackingDocument([], { title: "New mobile uploads" });
-        // doc.library = Docs.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` });
+        doc.optionalRightCollection = Docs.Create.StackingDocument([], { title: "New mobile uploads" });
+        // doc.library = Docs.Create.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` });
         // (doc.library as Doc).excludeFromLibrary = true;
         return doc;
     }
@@ -94,12 +94,12 @@ export class CurrentUserUtils {
         //                         new AttributeTransformationModel(atmod, AggregateFunction.None),
         //                         new AttributeTransformationModel(atmod, AggregateFunction.Count),
         //                         new AttributeTransformationModel(atmod, AggregateFunction.Count));
-        //                     schemaDocuments.push(Docs.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }));
+        //                     schemaDocuments.push(Docs.Create.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }));
         //                 }
         //             })));
         //             return promises;
         //         }, [] as Promise<void>[]));
-        //         return CurrentUserUtils._northstarSchemas.push(Docs.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! }));
+        //         return CurrentUserUtils._northstarSchemas.push(Docs.Create.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! }));
         //     });
         // }
     }
-- 
cgit v1.2.3-70-g09d2


From 9c4ed0eba1ee65271435a950f50fcbc85417eb0b Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 11 Jul 2019 02:32:27 -0400
Subject: naming cleanup

---
 src/Utils.ts                                       |  6 +-
 src/client/DocServer.ts                            |  4 +-
 src/client/documents/Documents.ts                  | 84 +++++++++++-----------
 .../views/collections/CollectionStackingView.tsx   |  4 +-
 .../views/collections/CollectionTreeView.tsx       |  4 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/LinkMenu.tsx                |  2 +-
 src/client/views/search/FilterBox.tsx              |  4 +-
 src/client/views/search/IconBar.tsx                |  4 +-
 src/client/views/search/IconButton.tsx             | 42 +++++------
 src/client/views/search/SearchItem.tsx             | 26 +++----
 11 files changed, 93 insertions(+), 89 deletions(-)

(limited to 'src/client/views/collections/CollectionTreeView.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index a62f9b4ff..e8a80bdc3 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -78,9 +78,9 @@ export class Utils {
         socket.emit(message.Message, args);
     }
 
-    public static emitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T): Promise<any>;
-    public static emitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn: (args: any) => any): void;
-    public static emitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> {
+    public static EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T): Promise<any>;
+    public static EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn: (args: any) => any): void;
+    public static EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> {
         this.log("Emit", message.Name, args, false);
         if (fn) {
             socket.emit(message.Message, args, this.loggingCallback('Receiving', fn, message.Name));
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 895177841..d05793ea2 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -109,7 +109,7 @@ export namespace DocServer {
             // synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string)
             // field for the given ids. This returns a promise, which, when resolved, indicates the the JSON serialized version of
             // the field has been returned from the server
-            const getSerializedField = Utils.emitCallback(_socket, MessageStore.GetRefField, id);
+            const getSerializedField = Utils.EmitCallback(_socket, MessageStore.GetRefField, id);
 
             // when the serialized RefField has been received, go head and begin deserializing it into an object.
             // Here, once deserialized, we also invoke .proto to 'load' the document's prototype, which ensures that all
@@ -179,7 +179,7 @@ export namespace DocServer {
         // 2) synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string)
         // fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of
         // the fields have been returned from the server
-        const getSerializedFields: Promise<any> = Utils.emitCallback(_socket, MessageStore.GetRefFields, requestedIds);
+        const getSerializedFields: Promise<any> = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds);
 
         // 3) when the serialized RefFields have been received, go head and begin deserializing them into objects.
         // Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b2f69d7af..5a3f9574f 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -41,7 +41,7 @@ import { Scripting } from "../util/Scripting";
 var requestImageSize = require('../util/request-image-size');
 var path = require('path');
 
-export enum DocTypes {
+export enum DocumentType {
     NONE = "none",
     TEXT = "text",
     HIST = "histogram",
@@ -60,13 +60,13 @@ export enum DocTypes {
 export namespace DocTypeUtils {
 
     export function values(includeNone: boolean = true): string[] {
-        let types = Object.values(DocTypes);
-        return includeNone ? types : types.filter(key => key !== DocTypes.NONE);
+        let types = Object.values(DocumentType);
+        return includeNone ? types : types.filter(key => key !== DocumentType.NONE);
     }
 
     export function keys(includeNone: boolean = true): string[] {
-        let types = Object.keys(DocTypes);
-        return includeNone ? types : types.filter(key => key !== DocTypes.NONE);
+        let types = Object.keys(DocumentType);
+        return includeNone ? types : types.filter(key => key !== DocumentType.NONE);
     }
 
 }
@@ -104,55 +104,55 @@ export namespace Docs {
     export namespace Prototypes {
 
         type PrototypeTemplate = { options?: Partial<DocumentOptions>, primary: string, background?: string };
-        type TemplateMap = Map<DocTypes, PrototypeTemplate>;
-        type PrototypeMap = Map<DocTypes, Doc>;
+        type TemplateMap = Map<DocumentType, PrototypeTemplate>;
+        type PrototypeMap = Map<DocumentType, Doc>;
 
         const TemplateMap: TemplateMap = new Map([
-            [DocTypes.TEXT, {
+            [DocumentType.TEXT, {
                 options: { height: 150, backgroundColor: "#f1efeb" },
                 primary: FormattedTextBox.LayoutString()
             }],
-            [DocTypes.HIST, {
+            [DocumentType.HIST, {
                 options: { nativeWidth: 600, curPage: 0 },
                 primary: CollectionView.LayoutString("annotations"),
                 background: HistogramBox.LayoutString()
             }],
-            [DocTypes.IMG, {
+            [DocumentType.IMG, {
                 options: { height: 300, backgroundColor: "black" },
                 primary: CollectionView.LayoutString("annotations"),
                 background: ImageBox.LayoutString()
             }],
-            [DocTypes.WEB, {
+            [DocumentType.WEB, {
                 options: { height: 300 },
                 primary: WebBox.LayoutString()
             }],
-            [DocTypes.COL, {
+            [DocumentType.COL, {
                 options: { panX: 0, panY: 0, scale: 1, width: 500, height: 500 },
                 primary: CollectionView.LayoutString()
             }],
-            [DocTypes.KVP, {
+            [DocumentType.KVP, {
                 options: { height: 150 },
                 primary: KeyValueBox.LayoutString()
             }],
-            [DocTypes.VID, {
+            [DocumentType.VID, {
                 options: { nativeWidth: 600, curPage: 0 },
                 primary: CollectionVideoView.LayoutString("annotations"),
                 background: VideoBox.LayoutString()
             }],
-            [DocTypes.AUDIO, {
+            [DocumentType.AUDIO, {
                 options: { height: 150 },
                 primary: AudioBox.LayoutString()
             }],
-            [DocTypes.PDF, {
+            [DocumentType.PDF, {
                 options: { nativeWidth: 1200, curPage: 1 },
                 primary: CollectionPDFView.LayoutString("annotations"),
                 background: PDFBox.LayoutString()
             }],
-            [DocTypes.ICON, {
+            [DocumentType.ICON, {
                 options: { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) },
                 primary: IconBox.LayoutString()
             }],
-            [DocTypes.IMPORT, {
+            [DocumentType.IMPORT, {
                 options: { height: 150 },
                 primary: DirectoryImportBox.LayoutString()
             }]
@@ -178,16 +178,20 @@ export namespace Docs {
             // fetch the actual prototype documents from the server
             let actualProtos = await DocServer.GetRefFields(prototypeIds);
 
+            // update this object to include any default values: DocumentOptions for all prototypes
             let defaultOptions: DocumentOptions = { x: 0, y: 0, width: 300 };
             prototypeIds.map(id => {
                 let existing = actualProtos[id] as Doc;
-                let type = id.replace(suffix, "") as DocTypes;
+                let type = id.replace(suffix, "") as DocumentType;
+                // get or create prototype of the specified type...
                 let target = existing || buildPrototype(type, id, defaultOptions);
+                // ...and set it if not undefined (can be undefined only if TemplateMap does not contain
+                // an entry dedicated to the given DocumentType)
                 target && PrototypeMap.set(type, target);
             });
         }
 
-        export function get(type: DocTypes) {
+        export function get(type: DocumentType) {
             return PrototypeMap.get(type)!;
         }
 
@@ -203,7 +207,7 @@ export namespace Docs {
          * @param options any value specified in the DocumentOptions object likewise
          * becomes the default value for that key for all delegates
          */
-        function buildPrototype(type: DocTypes, prototypeId: string, defaultOptions: DocumentOptions): Opt<Doc> {
+        function buildPrototype(type: DocumentType, prototypeId: string, defaultOptions: DocumentOptions): Opt<Doc> {
             let template = TemplateMap.get(type);
             if (!template) {
                 return undefined;
@@ -279,7 +283,7 @@ export namespace Docs {
         }
 
         export function ImageDocument(url: string, options: DocumentOptions = {}) {
-            let inst = InstanceFromProto(Prototypes.get(DocTypes.IMG), new ImageField(new URL(url)), { title: path.basename(url), ...options });
+            let inst = InstanceFromProto(Prototypes.get(DocumentType.IMG), new ImageField(new URL(url)), { title: path.basename(url), ...options });
             requestImageSize(window.origin + RouteStore.corsProxy + "/" + url)
                 .then((size: any) => {
                     let aspect = size.height / size.width;
@@ -294,27 +298,27 @@ export namespace Docs {
         }
 
         export function VideoDocument(url: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocTypes.VID), new VideoField(new URL(url)), options);
+            return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(new URL(url)), options);
         }
 
         export function AudioDocument(url: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocTypes.AUDIO), new AudioField(new URL(url)), options);
+            return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)), options);
         }
 
         export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocTypes.HIST), new HistogramField(histoOp), options);
+            return InstanceFromProto(Prototypes.get(DocumentType.HIST), new HistogramField(histoOp), options);
         }
 
         export function TextDocument(options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocTypes.TEXT), "", options);
+            return InstanceFromProto(Prototypes.get(DocumentType.TEXT), "", options);
         }
 
         export function IconDocument(icon: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocTypes.ICON), new IconField(icon), options);
+            return InstanceFromProto(Prototypes.get(DocumentType.ICON), new IconField(icon), options);
         }
 
         export function PdfDocument(url: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocTypes.PDF), new PdfField(new URL(url)), options);
+            return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(new URL(url)), options);
         }
 
         export async function DBDocument(url: string, options: DocumentOptions = {}, columnOptions: DocumentOptions = {}) {
@@ -349,42 +353,42 @@ export namespace Docs {
         }
 
         export function WebDocument(url: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocTypes.WEB), new WebField(new URL(url)), options);
+            return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(new URL(url)), options);
         }
 
         export function HtmlDocument(html: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocTypes.WEB), new HtmlField(html), options);
+            return InstanceFromProto(Prototypes.get(DocumentType.WEB), new HtmlField(html), options);
         }
 
         export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocTypes.KVP), document, { title: document.title + ".kvp", ...options });
+            return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + ".kvp", ...options });
         }
 
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, makePrototype: boolean = true) {
             if (!makePrototype) {
-                return MakeDataDelegate(Prototypes.get(DocTypes.COL), { ...options, viewType: CollectionViewType.Freeform }, new List(documents));
+                return MakeDataDelegate(Prototypes.get(DocumentType.COL), { ...options, viewType: CollectionViewType.Freeform }, new List(documents));
             }
-            return InstanceFromProto(Prototypes.get(DocTypes.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Freeform });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Freeform });
         }
 
         export function SchemaDocument(schemaColumns: string[], documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocTypes.COL), new List(documents), { schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema });
         }
 
         export function TreeDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocTypes.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree });
         }
 
         export function StackingDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocTypes.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Stacking });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Stacking });
         }
 
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocTypes.COL), new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
         }
 
         export function DirectoryImportDocument(options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocTypes.IMPORT), new List<Doc>(), options);
+            return InstanceFromProto(Prototypes.get(DocumentType.IMPORT), new List<Doc>(), options);
         }
 
         export type DocConfig = {
@@ -538,14 +542,14 @@ export namespace DocUtils {
 
         UndoManager.RunInBatch(() => {
             let linkDoc = Docs.Create.TextDocument({ width: 100, height: 30, borderRounding: "100%" });
-            linkDoc.type = DocTypes.LINK;
+            linkDoc.type = DocumentType.LINK;
             let linkDocProto = Doc.GetProto(linkDoc);
 
             linkDocProto.context = targetContext;
             linkDocProto.title = title === "" ? source.title + " to " + target.title : title;
             linkDocProto.linkDescription = description;
             linkDocProto.linkTags = tags;
-            linkDocProto.type = DocTypes.LINK;
+            linkDocProto.type = DocumentType.LINK;
 
             linkDocProto.anchor1 = source;
             linkDocProto.anchor1Page = source.curPage;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9266fc8fd..d26bf5118 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -12,7 +12,7 @@ import "./CollectionStackingView.scss";
 import { CollectionSubView } from "./CollectionSubView";
 import { undoBatch } from "../../util/UndoManager";
 import { DragManager } from "../../util/DragManager";
-import { DocTypes } from "../../documents/Documents";
+import { DocumentType } from "../../documents/Documents";
 import { Transform } from "../../util/Transform";
 
 @observer
@@ -50,7 +50,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     overlays = (doc: Doc) => {
-        return doc.type === DocTypes.IMG ? { title: "title", caption: "caption" } : {};
+        return doc.type === DocumentType.IMG ? { title: "title", caption: "caption" } : {};
     }
 
     getDisplayDoc(layoutDoc: Doc, d: Doc, dxf: () => Transform) {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e88f1a9d0..c8c092760 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -9,7 +9,7 @@ import { List } from '../../../new_fields/List';
 import { Document, listSpec } from '../../../new_fields/Schema';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
 import { emptyFunction, Utils } from '../../../Utils';
-import { Docs, DocUtils, DocTypes } from '../../documents/Documents';
+import { Docs, DocUtils, DocumentType } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
 import { SelectionManager } from '../../util/SelectionManager';
@@ -316,7 +316,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     @computed get docBounds() {
-        if (StrCast(this.props.document.type).indexOf(DocTypes.COL) === -1) return undefined;
+        if (StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1) return undefined;
         let layoutDoc = Doc.expandTemplateLayout(this.props.document, this.props.dataDoc);
         return Doc.ComputeContentBounds(layoutDoc);
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 718552dc9..27b45db76 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -10,7 +10,7 @@ import { BoolCast, Cast, FieldValue, StrCast, NumCast, PromiseValue } from "../.
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { emptyFunction, Utils, returnFalse, returnTrue } from "../../../Utils";
 import { DocServer } from "../../DocServer";
-import { Docs, DocUtils, DocTypes } from "../../documents/Documents";
+import { Docs, DocUtils, DocumentType } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { SearchUtil } from "../../util/SearchUtil";
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index cccf3c329..1eda7d1fb 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -14,7 +14,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 library.add(faTrash);
 import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { Id } from "../../../new_fields/FieldSymbols";
-import { DocTypes } from "../../documents/Documents";
+import { DocumentType } from "../../documents/Documents";
 
 interface Props {
     docView: DocumentView;
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 23a1b31d8..c6c18f9b4 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -6,7 +6,7 @@ import { faTimes } from '@fortawesome/free-solid-svg-icons';
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { Doc } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
-import { DocTypes } from '../../documents/Documents';
+import { DocumentType } from '../../documents/Documents';
 import { Cast, StrCast } from '../../../new_fields/Types';
 import * as _ from "lodash";
 import { ToggleBar } from './ToggleBar';
@@ -32,7 +32,7 @@ export enum Keys {
 export class FilterBox extends React.Component {
 
     static Instance: FilterBox;
-    public _allIcons: string[] = [DocTypes.AUDIO, DocTypes.COL, DocTypes.HIST, DocTypes.IMG, DocTypes.LINK, DocTypes.PDF, DocTypes.TEXT, DocTypes.VID, DocTypes.WEB];
+    public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.HIST, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB];
 
     //if true, any keywords can be used. if false, all keywords are required.
     @observable private _basicWordStatus: boolean = true;
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
index 744dd898a..4712b0abc 100644
--- a/src/client/views/search/IconBar.tsx
+++ b/src/client/views/search/IconBar.tsx
@@ -4,7 +4,7 @@ import { observable, action } from 'mobx';
 // import "./SearchBox.scss";
 import "./IconBar.scss";
 import "./IconButton.scss";
-import { DocTypes } from '../../documents/Documents';
+import { DocumentType } from '../../documents/Documents';
 import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faTimesCircle, faCheckCircle } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { library } from '@fortawesome/fontawesome-svg-core';
@@ -63,7 +63,7 @@ export class IconBar extends React.Component {
                 <div className="type-outer">
                     <div className={"type-icon all"}
                         onClick={this.selectAll}>
-                    <FontAwesomeIcon className="fontawesome-icon" icon={faCheckCircle} />
+                        <FontAwesomeIcon className="fontawesome-icon" icon={faCheckCircle} />
                     </div>
                     <div className="filter-description">Select All</div>
                 </div>
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
index 23ab42de0..bfe2c7d0b 100644
--- a/src/client/views/search/IconButton.tsx
+++ b/src/client/views/search/IconButton.tsx
@@ -6,7 +6,7 @@ import "./IconButton.scss";
 import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faVideo, faCaretDown } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { library, icon } from '@fortawesome/fontawesome-svg-core';
-import { DocTypes } from '../../documents/Documents';
+import { DocumentType } from '../../documents/Documents';
 import '../globalCssVariables.scss';
 import * as _ from "lodash";
 import { IconBar } from './IconBar';
@@ -80,25 +80,25 @@ export class IconButton extends React.Component<IconButtonProps>{
     @action.bound
     getIcon() {
         switch (this.props.type) {
-            case (DocTypes.NONE):
+            case (DocumentType.NONE):
                 return faBan;
-            case (DocTypes.AUDIO):
+            case (DocumentType.AUDIO):
                 return faMusic;
-            case (DocTypes.COL):
+            case (DocumentType.COL):
                 return faObjectGroup;
-            case (DocTypes.HIST):
+            case (DocumentType.HIST):
                 return faChartBar;
-            case (DocTypes.IMG):
+            case (DocumentType.IMG):
                 return faImage;
-            case (DocTypes.LINK):
+            case (DocumentType.LINK):
                 return faLink;
-            case (DocTypes.PDF):
+            case (DocumentType.PDF):
                 return faFilePdf;
-            case (DocTypes.TEXT):
+            case (DocumentType.TEXT):
                 return faStickyNote;
-            case (DocTypes.VID):
+            case (DocumentType.VID):
                 return faVideo;
-            case (DocTypes.WEB):
+            case (DocumentType.WEB):
                 return faGlobeAsia;
             default:
                 return faCaretDown;
@@ -149,25 +149,25 @@ export class IconButton extends React.Component<IconButtonProps>{
 
     getFA = () => {
         switch (this.props.type) {
-            case (DocTypes.NONE):
+            case (DocumentType.NONE):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faBan} />);
-            case (DocTypes.AUDIO):
+            case (DocumentType.AUDIO):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faMusic} />);
-            case (DocTypes.COL):
+            case (DocumentType.COL):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faObjectGroup} />);
-            case (DocTypes.HIST):
+            case (DocumentType.HIST):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faChartBar} />);
-            case (DocTypes.IMG):
+            case (DocumentType.IMG):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faImage} />);
-            case (DocTypes.LINK):
+            case (DocumentType.LINK):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faLink} />);
-            case (DocTypes.PDF):
+            case (DocumentType.PDF):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faFilePdf} />);
-            case (DocTypes.TEXT):
+            case (DocumentType.TEXT):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faStickyNote} />);
-            case (DocTypes.VID):
+            case (DocumentType.VID):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faVideo} />);
-            case (DocTypes.WEB):
+            case (DocumentType.WEB):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faGlobeAsia} />);
             default:
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faCaretDown} />);
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 87cae5487..b34103254 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -8,7 +8,7 @@ import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils";
-import { DocTypes } from "../../documents/Documents";
+import { DocumentType } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
 import { SetupDrag, DragManager } from "../../util/DragManager";
 import { LinkManager } from "../../util/LinkManager";
@@ -119,7 +119,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
                 onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))}
                 onPointerLeave={action(() => this._displayDim = 50)} >
                 <DocumentView
-                    fitToBox={StrCast(this.props.doc.type).indexOf(DocTypes.COL) !== -1 ? this.fitToBox : undefined}
+                    fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1 ? this.fitToBox : undefined}
                     Document={this.props.doc}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
@@ -142,15 +142,15 @@ export class SearchItem extends React.Component<SearchItemProps> {
         }
 
         let layoutresult = StrCast(this.props.doc.type);
-        let button = layoutresult.indexOf(DocTypes.PDF) !== -1 ? faFilePdf :
-            layoutresult.indexOf(DocTypes.IMG) !== -1 ? faImage :
-                layoutresult.indexOf(DocTypes.TEXT) !== -1 ? faStickyNote :
-                    layoutresult.indexOf(DocTypes.VID) !== -1 ? faFilm :
-                        layoutresult.indexOf(DocTypes.COL) !== -1 ? faObjectGroup :
-                            layoutresult.indexOf(DocTypes.AUDIO) !== -1 ? faMusic :
-                                layoutresult.indexOf(DocTypes.LINK) !== -1 ? faLink :
-                                    layoutresult.indexOf(DocTypes.HIST) !== -1 ? faChartBar :
-                                        layoutresult.indexOf(DocTypes.WEB) !== -1 ? faGlobeAsia :
+        let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf :
+            layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage :
+                layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote :
+                    layoutresult.indexOf(DocumentType.VID) !== -1 ? faFilm :
+                        layoutresult.indexOf(DocumentType.COL) !== -1 ? faObjectGroup :
+                            layoutresult.indexOf(DocumentType.AUDIO) !== -1 ? faMusic :
+                                layoutresult.indexOf(DocumentType.LINK) !== -1 ? faLink :
+                                    layoutresult.indexOf(DocumentType.HIST) !== -1 ? faChartBar :
+                                        layoutresult.indexOf(DocumentType.WEB) !== -1 ? faGlobeAsia :
                                             faCaretUp;
         return <div onPointerDown={action(() => { this._useIcons = false; this._displayDim = Number(SEARCH_THUMBNAIL_SIZE); })} >
             <FontAwesomeIcon icon={button} size="2x" />
@@ -184,7 +184,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
     pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); }
 
     highlightDoc = (e: React.PointerEvent) => {
-        if (this.props.doc.type === DocTypes.LINK) {
+        if (this.props.doc.type === DocumentType.LINK) {
             if (this.props.doc.anchor1 && this.props.doc.anchor2) {
 
                 let doc1 = Cast(this.props.doc.anchor1, Doc, null);
@@ -201,7 +201,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
     }
 
     unHighlightDoc = (e: React.PointerEvent) => {
-        if (this.props.doc.type === DocTypes.LINK) {
+        if (this.props.doc.type === DocumentType.LINK) {
             if (this.props.doc.anchor1 && this.props.doc.anchor2) {
 
                 let doc1 = Cast(this.props.doc.anchor1, Doc, null);
-- 
cgit v1.2.3-70-g09d2

