From 9b942166ca6b09ce4f310928c3daf8503a63ee5c Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Thu, 7 Feb 2019 23:33:44 -0500
Subject: Restored Opt<T> and made a new FieldValue<T> type to replace old
 Opt<T>

---
 src/views/collections/CollectionDockingView.tsx | 5 -----
 1 file changed, 5 deletions(-)

(limited to 'src/views/collections/CollectionDockingView.tsx')

diff --git a/src/views/collections/CollectionDockingView.tsx b/src/views/collections/CollectionDockingView.tsx
index e489e319a..adef03357 100644
--- a/src/views/collections/CollectionDockingView.tsx
+++ b/src/views/collections/CollectionDockingView.tsx
@@ -15,7 +15,6 @@ import * as GoldenLayout from "golden-layout";
 import * as ReactDOM from 'react-dom';
 import { DragManager } from "../../util/DragManager";
 import { CollectionViewBase, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase";
-import { FieldWaiting } from "../../fields/Field";
 
 @observer
 export class CollectionDockingView extends CollectionViewBase {
@@ -70,8 +69,6 @@ export class CollectionDockingView extends CollectionViewBase {
 
     @action
     onResize = (event: any) => {
-        if (this.props.ContainingDocumentView == FieldWaiting)
-            return;
         var cur = this.props.ContainingDocumentView!.MainContent.current;
 
         // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
@@ -255,8 +252,6 @@ export class CollectionDockingView extends CollectionViewBase {
 
 
     render() {
-        if (this.props.ContainingDocumentView == FieldWaiting)
-            return;
         const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
         const value: Document[] = Document.GetData(fieldKey, ListField, []);
         // bcz: not sure why, but I need these to force the flexlayout to update when the collection size changes.
-- 
cgit v1.2.3-70-g09d2


From 11134bc5ce01d0a025d311a4f83e67ff6e63ce1c Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sat, 9 Feb 2019 19:13:24 -0500
Subject: Moved client code to client folder

---
 src/DocumentDecorations.scss                       |  32 --
 src/DocumentDecorations.tsx                        | 153 ----------
 src/Main.scss                                      |  31 --
 src/Main.tsx                                       |  92 ------
 src/Server.tsx                                     |  61 ----
 src/SocketStub.tsx                                 |  78 -----
 src/client/Server.ts                               |  61 ++++
 src/client/SocketStub.ts                           |  78 +++++
 src/client/documents/Documents.ts                  | 161 ++++++++++
 src/client/util/DragManager.ts                     | 131 ++++++++
 src/client/util/Scripting.ts                       |  58 ++++
 src/client/util/ScrollBox.tsx                      |  21 ++
 src/client/util/SelectionManager.ts                |  39 +++
 src/client/util/TypedEvent.ts                      |  42 +++
 src/client/views/ContextMenu.scss                  |  34 +++
 src/client/views/ContextMenu.tsx                   |  68 +++++
 src/client/views/ContextMenuItem.tsx               |  17 ++
 src/client/views/DocumentDecorations.scss          |  32 ++
 src/client/views/DocumentDecorations.tsx           | 153 ++++++++++
 src/client/views/EditableView.tsx                  |  39 +++
 src/client/views/Main.scss                         |  31 ++
 src/client/views/Main.tsx                          |  91 ++++++
 .../views/collections/CollectionDockingView.scss   | 336 +++++++++++++++++++++
 .../views/collections/CollectionDockingView.tsx    | 281 +++++++++++++++++
 .../views/collections/CollectionFreeFormView.scss  |  20 ++
 .../views/collections/CollectionFreeFormView.tsx   | 205 +++++++++++++
 .../views/collections/CollectionSchemaView.scss    | 108 +++++++
 .../views/collections/CollectionSchemaView.tsx     | 144 +++++++++
 .../views/collections/CollectionViewBase.tsx       |  57 ++++
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 223 ++++++++++++++
 src/client/views/nodes/DocumentView.tsx            | 153 ++++++++++
 src/client/views/nodes/FieldTextBox.scss           |  14 +
 src/client/views/nodes/FieldView.tsx               |  56 ++++
 src/client/views/nodes/FormattedTextBox.scss       |  14 +
 src/client/views/nodes/FormattedTextBox.tsx        | 127 ++++++++
 src/client/views/nodes/ImageBox.scss               |  11 +
 src/client/views/nodes/ImageBox.tsx                |  92 ++++++
 src/client/views/nodes/NodeView.scss               |  23 ++
 src/documents/Documents.ts                         | 161 ----------
 src/fields/Document.ts                             |   2 +-
 src/server/index.js                                |   0
 src/util/DragManager.ts                            | 137 ---------
 src/util/Scripting.ts                              |  58 ----
 src/util/ScrollBox.tsx                             |  21 --
 src/util/SelectionManager.ts                       |  39 ---
 src/util/TypedEvent.ts                             |  42 ---
 src/views/ContextMenu.scss                         |  34 ---
 src/views/ContextMenu.tsx                          |  68 -----
 src/views/ContextMenuItem.tsx                      |  17 --
 src/views/EditableView.tsx                         |  39 ---
 src/views/collections/CollectionDockingView.scss   | 336 ---------------------
 src/views/collections/CollectionDockingView.tsx    | 282 -----------------
 src/views/collections/CollectionFreeFormView.scss  |  20 --
 src/views/collections/CollectionFreeFormView.tsx   | 206 -------------
 src/views/collections/CollectionSchemaView.scss    | 108 -------
 src/views/collections/CollectionSchemaView.tsx     | 144 ---------
 src/views/collections/CollectionViewBase.tsx       |  57 ----
 src/views/nodes/CollectionFreeFormDocumentView.tsx | 223 --------------
 src/views/nodes/DocumentView.tsx                   | 153 ----------
 src/views/nodes/FieldTextBox.scss                  |  14 -
 src/views/nodes/FieldView.tsx                      |  56 ----
 src/views/nodes/FormattedTextBox.scss              |  14 -
 src/views/nodes/FormattedTextBox.tsx               | 127 --------
 src/views/nodes/ImageBox.scss                      |  11 -
 src/views/nodes/ImageBox.tsx                       |  92 ------
 src/views/nodes/NodeView.scss                      |  23 --
 webpack.config.js                                  |   2 +-
 67 files changed, 2922 insertions(+), 2931 deletions(-)
 delete mode 100644 src/DocumentDecorations.scss
 delete mode 100644 src/DocumentDecorations.tsx
 delete mode 100644 src/Main.scss
 delete mode 100644 src/Main.tsx
 delete mode 100644 src/Server.tsx
 delete mode 100644 src/SocketStub.tsx
 create mode 100644 src/client/Server.ts
 create mode 100644 src/client/SocketStub.ts
 create mode 100644 src/client/documents/Documents.ts
 create mode 100644 src/client/util/DragManager.ts
 create mode 100644 src/client/util/Scripting.ts
 create mode 100644 src/client/util/ScrollBox.tsx
 create mode 100644 src/client/util/SelectionManager.ts
 create mode 100644 src/client/util/TypedEvent.ts
 create mode 100644 src/client/views/ContextMenu.scss
 create mode 100644 src/client/views/ContextMenu.tsx
 create mode 100644 src/client/views/ContextMenuItem.tsx
 create mode 100644 src/client/views/DocumentDecorations.scss
 create mode 100644 src/client/views/DocumentDecorations.tsx
 create mode 100644 src/client/views/EditableView.tsx
 create mode 100644 src/client/views/Main.scss
 create mode 100644 src/client/views/Main.tsx
 create mode 100644 src/client/views/collections/CollectionDockingView.scss
 create mode 100644 src/client/views/collections/CollectionDockingView.tsx
 create mode 100644 src/client/views/collections/CollectionFreeFormView.scss
 create mode 100644 src/client/views/collections/CollectionFreeFormView.tsx
 create mode 100644 src/client/views/collections/CollectionSchemaView.scss
 create mode 100644 src/client/views/collections/CollectionSchemaView.tsx
 create mode 100644 src/client/views/collections/CollectionViewBase.tsx
 create mode 100644 src/client/views/nodes/CollectionFreeFormDocumentView.tsx
 create mode 100644 src/client/views/nodes/DocumentView.tsx
 create mode 100644 src/client/views/nodes/FieldTextBox.scss
 create mode 100644 src/client/views/nodes/FieldView.tsx
 create mode 100644 src/client/views/nodes/FormattedTextBox.scss
 create mode 100644 src/client/views/nodes/FormattedTextBox.tsx
 create mode 100644 src/client/views/nodes/ImageBox.scss
 create mode 100644 src/client/views/nodes/ImageBox.tsx
 create mode 100644 src/client/views/nodes/NodeView.scss
 delete mode 100644 src/documents/Documents.ts
 create mode 100644 src/server/index.js
 delete mode 100644 src/util/DragManager.ts
 delete mode 100644 src/util/Scripting.ts
 delete mode 100644 src/util/ScrollBox.tsx
 delete mode 100644 src/util/SelectionManager.ts
 delete mode 100644 src/util/TypedEvent.ts
 delete mode 100644 src/views/ContextMenu.scss
 delete mode 100644 src/views/ContextMenu.tsx
 delete mode 100644 src/views/ContextMenuItem.tsx
 delete mode 100644 src/views/EditableView.tsx
 delete mode 100644 src/views/collections/CollectionDockingView.scss
 delete mode 100644 src/views/collections/CollectionDockingView.tsx
 delete mode 100644 src/views/collections/CollectionFreeFormView.scss
 delete mode 100644 src/views/collections/CollectionFreeFormView.tsx
 delete mode 100644 src/views/collections/CollectionSchemaView.scss
 delete mode 100644 src/views/collections/CollectionSchemaView.tsx
 delete mode 100644 src/views/collections/CollectionViewBase.tsx
 delete mode 100644 src/views/nodes/CollectionFreeFormDocumentView.tsx
 delete mode 100644 src/views/nodes/DocumentView.tsx
 delete mode 100644 src/views/nodes/FieldTextBox.scss
 delete mode 100644 src/views/nodes/FieldView.tsx
 delete mode 100644 src/views/nodes/FormattedTextBox.scss
 delete mode 100644 src/views/nodes/FormattedTextBox.tsx
 delete mode 100644 src/views/nodes/ImageBox.scss
 delete mode 100644 src/views/nodes/ImageBox.tsx
 delete mode 100644 src/views/nodes/NodeView.scss

(limited to 'src/views/collections/CollectionDockingView.tsx')

diff --git a/src/DocumentDecorations.scss b/src/DocumentDecorations.scss
deleted file mode 100644
index e8b93a18b..000000000
--- a/src/DocumentDecorations.scss
+++ /dev/null
@@ -1,32 +0,0 @@
-#documentDecorations-container {
-    position: absolute;
-    display: grid;
-    z-index: 1000;
-    grid-template-rows: 20px 1fr 20px;
-    grid-template-columns: 20px 1fr 20px;
-    pointer-events: none;
-    #documentDecorations-centerCont {
-        background: none;
-    }
-    .documentDecorations-resizer {
-        pointer-events: auto;
-        background: lightblue;
-        opacity: 0.4;
-    }
-    #documentDecorations-topLeftResizer,
-    #documentDecorations-bottomRightResizer {
-        cursor: nwse-resize;
-    }
-    #documentDecorations-topRightResizer,
-    #documentDecorations-bottomLeftResizer {
-        cursor: nesw-resize;
-    }
-    #documentDecorations-topResizer,
-    #documentDecorations-bottomResizer {
-        cursor: ns-resize;
-    }
-    #documentDecorations-leftResizer,
-    #documentDecorations-rightResizer {
-        cursor: ew-resize;
-    }
-}
\ No newline at end of file
diff --git a/src/DocumentDecorations.tsx b/src/DocumentDecorations.tsx
deleted file mode 100644
index 1cf875ea5..000000000
--- a/src/DocumentDecorations.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import { observable, computed } from "mobx";
-import React = require("react");
-import { SelectionManager } from "./util/SelectionManager";
-import { observer } from "mobx-react";
-import './DocumentDecorations.scss'
-import { CollectionFreeFormView } from "./views/collections/CollectionFreeFormView";
-
-@observer
-export class DocumentDecorations extends React.Component {
-    static Instance: DocumentDecorations
-    private _resizer = ""
-    private _isPointerDown = false;
-    @observable private _opacity = 1;
-
-    constructor(props: Readonly<{}>) {
-        super(props)
-
-        DocumentDecorations.Instance = this
-    }
-
-    @computed
-    get Bounds(): { x: number, y: number, b: number, r: number } {
-        return SelectionManager.SelectedDocuments().reduce((bounds, element) => {
-            if (element.props.ContainingCollectionView != undefined &&
-                !(element.props.ContainingCollectionView instanceof CollectionFreeFormView)) {
-                return bounds;
-            }
-            var spt = element.TransformToScreenPoint(0, 0);
-            var bpt = element.TransformToScreenPoint(element.width, element.height);
-            return {
-                x: Math.min(spt.ScreenX, bounds.x), y: Math.min(spt.ScreenY, bounds.y),
-                r: Math.max(bpt.ScreenX, bounds.r), b: Math.max(bpt.ScreenY, bounds.b)
-            }
-        }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
-    }
-
-    @computed
-    get opacity(): number {
-        return this._opacity
-    }
-
-    set opacity(o: number) {
-        this._opacity = Math.min(Math.max(0, o), 1)
-    }
-
-    onPointerDown = (e: React.PointerEvent): void => {
-        e.stopPropagation();
-        if (e.button === 0) {
-            this._isPointerDown = true;
-            this._resizer = e.currentTarget.id;
-            document.removeEventListener("pointermove", this.onPointerMove);
-            document.addEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            document.addEventListener("pointerup", this.onPointerUp);
-        }
-    }
-
-    onPointerMove = (e: PointerEvent): void => {
-        e.stopPropagation();
-        e.preventDefault();
-        if (!this._isPointerDown) {
-            return;
-        }
-
-        let dX = 0, dY = 0, dW = 0, dH = 0;
-
-        switch (this._resizer) {
-            case "":
-                break;
-            case "documentDecorations-topLeftResizer":
-                dX = -1
-                dY = -1
-                dW = -(e.movementX)
-                dH = -(e.movementY)
-                break;
-            case "documentDecorations-topRightResizer":
-                dW = e.movementX
-                dY = -1
-                dH = -(e.movementY)
-                break;
-            case "documentDecorations-topResizer":
-                dY = -1
-                dH = -(e.movementY)
-                break;
-            case "documentDecorations-bottomLeftResizer":
-                dX = -1
-                dW = -(e.movementX)
-                dH = e.movementY
-                break;
-            case "documentDecorations-bottomRightResizer":
-                dW = e.movementX
-                dH = e.movementY
-                break;
-            case "documentDecorations-bottomResizer":
-                dH = e.movementY
-                break;
-            case "documentDecorations-leftResizer":
-                dX = -1
-                dW = -(e.movementX)
-                break;
-            case "documentDecorations-rightResizer":
-                dW = e.movementX
-                break;
-        }
-
-        SelectionManager.SelectedDocuments().forEach(element => {
-            const rect = element.screenRect;
-            if (rect.width !== 0) {
-                let scale = element.width / rect.width;
-                let actualdW = Math.max(element.width + (dW * scale), 20);
-                let actualdH = Math.max(element.height + (dH * scale), 20);
-                element.x += dX * (actualdW - element.width);
-                element.y += dY * (actualdH - element.height);
-                element.width = actualdW;
-                element.height = actualdH;
-            }
-        })
-    }
-
-    onPointerUp = (e: PointerEvent): void => {
-        e.stopPropagation();
-        if (e.button === 0) {
-            e.preventDefault();
-            this._isPointerDown = false;
-            document.removeEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp);
-        }
-    }
-
-    render() {
-        var bounds = this.Bounds;
-        return (
-            <div id="documentDecorations-container" style={{
-                width: (bounds.r - bounds.x + 40) + "px",
-                height: (bounds.b - bounds.y + 40) + "px",
-                left: bounds.x - 20,
-                top: bounds.y - 20,
-                opacity: this.opacity
-            }}>
-                <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-topResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-topRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-leftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-centerCont"></div>
-                <div id="documentDecorations-rightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-
-            </div>
-        )
-    }
-}
\ No newline at end of file
diff --git a/src/Main.scss b/src/Main.scss
deleted file mode 100644
index e73f62904..000000000
--- a/src/Main.scss
+++ /dev/null
@@ -1,31 +0,0 @@
-html,
-body {
-    width: 100%;
-    height: 100%;
-    overflow: hidden;
-    font-family: 'Hind Siliguri', sans-serif;
-    margin: 0;
-}
-
-h1 {
-    font-size: 50px;
-    position: fixed;
-    top: 30px;
-    left: 50%;
-    transform: translateX(-50%);
-    color: black;
-    text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
-    z-index: 9999;
-    font-family: 'Fjalla One', sans-serif;
-    -webkit-touch-callout: none;
-    -webkit-user-select: none;
-    -khtml-user-select: none;
-    -moz-user-select: none;
-    -ms-user-select: none;
-    user-select: none;
-}
-
-p {
-    margin: 0px;
-    padding: 0px;
-}
\ No newline at end of file
diff --git a/src/Main.tsx b/src/Main.tsx
deleted file mode 100644
index fa6230393..000000000
--- a/src/Main.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import { action, configure } from 'mobx';
-import "normalize.css";
-import * as React from 'react';
-import * as ReactDOM from 'react-dom';
-import { DocumentDecorations } from './DocumentDecorations';
-import { Documents } from './documents/Documents';
-import { Document } from './fields/Document';
-import { KeyStore, KeyStore as KS } from './fields/Key';
-import { ListField } from './fields/ListField';
-import { NumberField } from './fields/NumberField';
-import { TextField } from './fields/TextField';
-import "./Main.scss";
-import { ContextMenu } from './views/ContextMenu';
-import { DocumentView } from './views/nodes/DocumentView';
-import { CompileScript } from './util/Scripting';
-import { ImageField } from './fields/ImageField';
-
-
-configure({
-    enforceActions: "observed"
-});
-
-const mainNodeCollection = new Array<Document>();
-let mainContainer = Documents.DockDocument(mainNodeCollection, {
-    x: 0, y: 0, title: "main container"
-})
-
-window.addEventListener("drop", function (e) {
-    e.preventDefault();
-}, false)
-window.addEventListener("dragover", function (e) {
-    e.preventDefault();
-}, false)
-document.addEventListener("pointerdown", action(function (e: PointerEvent) {
-    if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
-        ContextMenu.Instance.clearItems()
-    }
-}), true)
-
-
-//runInAction(() => 
-{
-    let doc1 = Documents.TextDocument({ title: "hello" });
-    let doc2 = doc1.MakeDelegate();
-    doc2.Set(KS.X, new NumberField(150));
-    doc2.Set(KS.Y, new NumberField(20));
-    let doc3 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
-        x: 450, y: 500, title: "cat 1"
-    });
-    doc3.Set(KeyStore.Data, new ImageField);
-    const schemaDocs = Array.from(Array(5).keys()).map(v => Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
-        x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v
-    }));
-    schemaDocs[0].SetData(KS.Author, "Tyler", TextField);
-    schemaDocs[4].SetData(KS.Author, "Bob", TextField);
-    schemaDocs.push(doc2);
-    const doc7 = Documents.SchemaDocument(schemaDocs)
-    const docset = [doc1, doc2, doc3, doc7];
-    let doc4 = Documents.CollectionDocument(docset, {
-        x: 0, y: 400, title: "mini collection"
-    });
-    // let doc5 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
-    //     x: 650, y: 500, width: 600, height: 600, title: "cat 2"
-    // });
-    let docset2 = new Array<Document>(doc4);//, doc1, doc3);
-    let doc6 = Documents.CollectionDocument(docset2, {
-        x: 350, y: 100, width: 600, height: 600, title: "docking collection"
-    });
-    let mainNodes = null;// mainContainer.GetFieldT(KeyStore.Data, ListField);
-    if (!mainNodes) {
-        mainNodes = new ListField<Document>();
-    }
-    // mainNodes.Data.push(doc6);
-    // mainNodes.Data.push(doc2);
-    mainNodes.Data.push(doc4);
-    mainNodes.Data.push(doc3);
-    // mainNodes.Data.push(doc5);
-    // mainNodes.Data.push(doc1);
-    //mainNodes.Data.push(doc2);
-    //mainNodes.Data.push(doc6);
-    mainContainer.Set(KeyStore.Data, mainNodes);
-}
-//}
-//);
-
-ReactDOM.render((
-    <div style={{ position: "absolute", width: "100%", height: "100%" }}>
-        <DocumentView Document={mainContainer} ContainingCollectionView={undefined} DocumentView={undefined} />
-        <DocumentDecorations />
-        <ContextMenu />
-    </div>),
-    document.getElementById('root'));
\ No newline at end of file
diff --git a/src/Server.tsx b/src/Server.tsx
deleted file mode 100644
index 645420771..000000000
--- a/src/Server.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { Field, FieldWaiting, FIELD_ID, FIELD_WAITING, FieldValue } from "./fields/Field"
-import { Key, KeyStore } from "./fields/Key"
-import { ObservableMap, action } from "mobx";
-import { Document } from "./fields/Document"
-import { SocketStub } from "./SocketStub";
-
-export class Server {
-    private static ClientFieldsCached: ObservableMap<FIELD_ID, Field | FIELD_WAITING> = new ObservableMap();
-
-    // Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached).
-    // Call this is from within a reaction and test whether the return value is FieldWaiting.
-    // 'hackTimeout' is here temporarily for simplicity when debugging things.
-    public static GetField(fieldid: FIELD_ID, callback: (field: Field) => void = (f) => { }, hackTimeout: number = -1) {
-        if (!this.ClientFieldsCached.get(fieldid)) {
-            this.ClientFieldsCached.set(fieldid, FieldWaiting);
-            //simulating a server call with a registered callback action
-            SocketStub.SEND_FIELD_REQUEST(fieldid,
-                action((field: Field) => callback(Server.cacheField(field))),
-                hackTimeout);
-        } else if (this.ClientFieldsCached.get(fieldid) != FieldWaiting) {
-            callback(this.ClientFieldsCached.get(fieldid) as Field);
-        }
-        return this.ClientFieldsCached.get(fieldid);
-    }
-
-    static times = 0; // hack for testing
-    public static GetDocumentField(doc: Document, key: Key) {
-        var hackTimeout: number = key == KeyStore.Data ? (this.times++ == 0 ? 5000 : 1000) : key == KeyStore.X ? 2500 : 500;
-
-        return this.GetField(doc._proxies.get(key),
-            action((fieldfromserver: Field) => {
-                doc._proxies.delete(key);
-                doc.fields.set(key, fieldfromserver);
-            })
-            , hackTimeout);
-    }
-
-    public static AddDocument(document: Document) {
-        SocketStub.SEND_ADD_DOCUMENT(document);
-    }
-    public static AddDocumentField(doc: Document, key: Key, value: Field) {
-        SocketStub.SEND_ADD_DOCUMENT_FIELD(doc, key, value);
-    }
-    public static DeleteDocumentField(doc: Document, key: Key) {
-        SocketStub.SEND_DELETE_DOCUMENT_FIELD(doc, key);
-    }
-    public static SetFieldValue(field: Field, value: any) {
-        SocketStub.SEND_SET_FIELD(field, value);
-    }
-
-    @action
-    private static cacheField(clientField: Field) {
-        var cached = this.ClientFieldsCached.get(clientField.Id);
-        if (!cached || cached == FieldWaiting) {
-            this.ClientFieldsCached.set(clientField.Id, clientField);
-        } else {
-            // probably should overwrite the values within any field that was already here...
-        }
-        return this.ClientFieldsCached.get(clientField.Id) as Field;
-    }
-}
diff --git a/src/SocketStub.tsx b/src/SocketStub.tsx
deleted file mode 100644
index f9d48c067..000000000
--- a/src/SocketStub.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import { Field, FIELD_ID } from "./fields/Field"
-import { Key, KeyStore } from "./fields/Key"
-import { ObservableMap, action } from "mobx";
-import { Document } from "./fields/Document"
-
-export class SocketStub {
-
-    static FieldStore: ObservableMap<FIELD_ID, Field> = new ObservableMap();
-    public static SEND_ADD_DOCUMENT(document: Document) {
-
-        // Send a serialized version of the document to the server
-        // ...SOCKET(ADD_DOCUMENT, serialied document)
-
-        // server stores each document field in its repository of stored fields
-        document.fields.forEach((f, key) => this.FieldStore.set((f as Field).Id, f as Field));
-
-        // server stores stripped down document (w/ only field id proxies) in the field store
-        this.FieldStore.set(document.Id, new Document(document.Id));
-        document.fields.forEach((f, key) => (this.FieldStore.get(document.Id) as Document)._proxies.set(key, (f as Field).Id));
-    }
-
-    public static SEND_FIELD_REQUEST(fieldid: FIELD_ID, callback: (field: Field) => void, timeout: number) {
-
-        if (timeout < 0)// this is a hack to make things easier to setup until we have a server... won't be neededa fter that.
-            callback(this.FieldStore.get(fieldid) as Field);
-        else { // actual logic here... 
-
-            // Send a request for fieldid to the server
-            // ...SOCKET(RETRIEVE_FIELD, fieldid)
-
-            // server responds (simulated with a timeout) and the callback is invoked
-            setTimeout(() =>
-
-                // when the field data comes back, call the callback() function 
-                callback(this.FieldStore.get(fieldid) as Field),
-
-
-                timeout);
-        }
-    }
-
-    public static SEND_ADD_DOCUMENT_FIELD(doc: Document, key: Key, value: Field) {
-
-        // Send a serialized version of the field to the server along with the
-        // associated info of the document id and key where it is used.
-
-        // ...SOCKET(ADD_DOCUMENT_FIELD, document id, key id, serialized field)
-
-        // server updates its document to hold a proxy mapping from key => fieldId
-        var document = this.FieldStore.get(doc.Id) as Document;
-        if (document)
-            document._proxies.set(key, value.Id);
-
-        // server adds the field to its repository of fields
-        this.FieldStore.set(value.Id, value);
-    }
-
-    public static SEND_DELETE_DOCUMENT_FIELD(doc: Document, key: Key) {
-        // Send a request to delete the field stored under the specified key from the document
-
-        // ...SOCKET(DELETE_DOCUMENT_FIELD, document id, key id)
-
-        // Server removes the field id from the document's list of field proxies
-        var document = this.FieldStore.get(doc.Id) as Document;
-        if (document)
-            document._proxies.delete(key);
-    }
-
-    public static SEND_SET_FIELD(field: Field, value: any) {
-        // Send a request to set the value of a field
-
-        // ...SOCKET(SET_FIELD, field id, serialized field value)
-
-        // Server updates the value of the field in its fieldstore
-        if (this.FieldStore.get(field.Id))
-            this.FieldStore.get(field.Id)!.TrySetValue(value);
-    }
-}
diff --git a/src/client/Server.ts b/src/client/Server.ts
new file mode 100644
index 000000000..85e55a84e
--- /dev/null
+++ b/src/client/Server.ts
@@ -0,0 +1,61 @@
+import { Field, FieldWaiting, FIELD_ID, FIELD_WAITING, FieldValue } from "../fields/Field"
+import { Key, KeyStore } from "../fields/Key"
+import { ObservableMap, action } from "mobx";
+import { Document } from "../fields/Document"
+import { SocketStub } from "./SocketStub";
+
+export class Server {
+    private static ClientFieldsCached: ObservableMap<FIELD_ID, Field | FIELD_WAITING> = new ObservableMap();
+
+    // Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached).
+    // Call this is from within a reaction and test whether the return value is FieldWaiting.
+    // 'hackTimeout' is here temporarily for simplicity when debugging things.
+    public static GetField(fieldid: FIELD_ID, callback: (field: Field) => void = (f) => { }, hackTimeout: number = -1) {
+        if (!this.ClientFieldsCached.get(fieldid)) {
+            this.ClientFieldsCached.set(fieldid, FieldWaiting);
+            //simulating a server call with a registered callback action
+            SocketStub.SEND_FIELD_REQUEST(fieldid,
+                action((field: Field) => callback(Server.cacheField(field))),
+                hackTimeout);
+        } else if (this.ClientFieldsCached.get(fieldid) != FieldWaiting) {
+            callback(this.ClientFieldsCached.get(fieldid) as Field);
+        }
+        return this.ClientFieldsCached.get(fieldid);
+    }
+
+    static times = 0; // hack for testing
+    public static GetDocumentField(doc: Document, key: Key) {
+        var hackTimeout: number = key == KeyStore.Data ? (this.times++ == 0 ? 5000 : 1000) : key == KeyStore.X ? 2500 : 500;
+
+        return this.GetField(doc._proxies.get(key),
+            action((fieldfromserver: Field) => {
+                doc._proxies.delete(key);
+                doc.fields.set(key, fieldfromserver);
+            })
+            , hackTimeout);
+    }
+
+    public static AddDocument(document: Document) {
+        SocketStub.SEND_ADD_DOCUMENT(document);
+    }
+    public static AddDocumentField(doc: Document, key: Key, value: Field) {
+        SocketStub.SEND_ADD_DOCUMENT_FIELD(doc, key, value);
+    }
+    public static DeleteDocumentField(doc: Document, key: Key) {
+        SocketStub.SEND_DELETE_DOCUMENT_FIELD(doc, key);
+    }
+    public static SetFieldValue(field: Field, value: any) {
+        SocketStub.SEND_SET_FIELD(field, value);
+    }
+
+    @action
+    private static cacheField(clientField: Field) {
+        var cached = this.ClientFieldsCached.get(clientField.Id);
+        if (!cached || cached == FieldWaiting) {
+            this.ClientFieldsCached.set(clientField.Id, clientField);
+        } else {
+            // probably should overwrite the values within any field that was already here...
+        }
+        return this.ClientFieldsCached.get(clientField.Id) as Field;
+    }
+}
diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts
new file mode 100644
index 000000000..58dedbf82
--- /dev/null
+++ b/src/client/SocketStub.ts
@@ -0,0 +1,78 @@
+import { Field, FIELD_ID } from "../fields/Field"
+import { Key, KeyStore } from "../fields/Key"
+import { ObservableMap, action } from "mobx";
+import { Document } from "../fields/Document"
+
+export class SocketStub {
+
+    static FieldStore: ObservableMap<FIELD_ID, Field> = new ObservableMap();
+    public static SEND_ADD_DOCUMENT(document: Document) {
+
+        // Send a serialized version of the document to the server
+        // ...SOCKET(ADD_DOCUMENT, serialied document)
+
+        // server stores each document field in its repository of stored fields
+        document.fields.forEach((f, key) => this.FieldStore.set((f as Field).Id, f as Field));
+
+        // server stores stripped down document (w/ only field id proxies) in the field store
+        this.FieldStore.set(document.Id, new Document(document.Id));
+        document.fields.forEach((f, key) => (this.FieldStore.get(document.Id) as Document)._proxies.set(key, (f as Field).Id));
+    }
+
+    public static SEND_FIELD_REQUEST(fieldid: FIELD_ID, callback: (field: Field) => void, timeout: number) {
+
+        if (timeout < 0)// this is a hack to make things easier to setup until we have a server... won't be neededa fter that.
+            callback(this.FieldStore.get(fieldid) as Field);
+        else { // actual logic here... 
+
+            // Send a request for fieldid to the server
+            // ...SOCKET(RETRIEVE_FIELD, fieldid)
+
+            // server responds (simulated with a timeout) and the callback is invoked
+            setTimeout(() =>
+
+                // when the field data comes back, call the callback() function 
+                callback(this.FieldStore.get(fieldid) as Field),
+
+
+                timeout);
+        }
+    }
+
+    public static SEND_ADD_DOCUMENT_FIELD(doc: Document, key: Key, value: Field) {
+
+        // Send a serialized version of the field to the server along with the
+        // associated info of the document id and key where it is used.
+
+        // ...SOCKET(ADD_DOCUMENT_FIELD, document id, key id, serialized field)
+
+        // server updates its document to hold a proxy mapping from key => fieldId
+        var document = this.FieldStore.get(doc.Id) as Document;
+        if (document)
+            document._proxies.set(key, value.Id);
+
+        // server adds the field to its repository of fields
+        this.FieldStore.set(value.Id, value);
+    }
+
+    public static SEND_DELETE_DOCUMENT_FIELD(doc: Document, key: Key) {
+        // Send a request to delete the field stored under the specified key from the document
+
+        // ...SOCKET(DELETE_DOCUMENT_FIELD, document id, key id)
+
+        // Server removes the field id from the document's list of field proxies
+        var document = this.FieldStore.get(doc.Id) as Document;
+        if (document)
+            document._proxies.delete(key);
+    }
+
+    public static SEND_SET_FIELD(field: Field, value: any) {
+        // Send a request to set the value of a field
+
+        // ...SOCKET(SET_FIELD, field id, serialized field value)
+
+        // Server updates the value of the field in its fieldstore
+        if (this.FieldStore.get(field.Id))
+            this.FieldStore.get(field.Id)!.TrySetValue(value);
+    }
+}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
new file mode 100644
index 000000000..6925234fe
--- /dev/null
+++ b/src/client/documents/Documents.ts
@@ -0,0 +1,161 @@
+import { Document } from "../../fields/Document";
+import { Server } from "../Server";
+import { KeyStore } from "../../fields/Key";
+import { TextField } from "../../fields/TextField";
+import { NumberField } from "../../fields/NumberField";
+import { ListField } from "../../fields/ListField";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { CollectionSchemaView } from "../views/collections/CollectionSchemaView";
+import { ImageField } from "../../fields/ImageField";
+import { ImageBox } from "../views/nodes/ImageBox";
+import { CollectionFreeFormView } from "../views/collections/CollectionFreeFormView";
+import { FIELD_ID } from "../../fields/Field";
+
+interface DocumentOptions {
+    x?: number;
+    y?: number;
+    width?: number;
+    height?: number;
+    title?: string;
+}
+
+export namespace Documents {
+    function setupOptions(doc: Document, options: DocumentOptions): void {
+        if (options.x) {
+            doc.SetData(KeyStore.X, options.x, NumberField);
+        }
+        if (options.y) {
+            doc.SetData(KeyStore.Y, options.y, NumberField);
+        }
+        if (options.width) {
+            doc.SetData(KeyStore.Width, options.width, NumberField);
+        }
+        if (options.height) {
+            doc.SetData(KeyStore.Height, options.height, NumberField);
+        }
+        if (options.title) {
+            doc.SetData(KeyStore.Title, options.title, TextField);
+        }
+        doc.SetData(KeyStore.Scale, 1, NumberField);
+        doc.SetData(KeyStore.PanX, 0, NumberField);
+        doc.SetData(KeyStore.PanY, 0, NumberField);
+    }
+
+    let textProto: Document;
+    function GetTextPrototype(): Document {
+        if (!textProto) {
+            textProto = new Document();
+            textProto.Set(KeyStore.X, new NumberField(0));
+            textProto.Set(KeyStore.Y, new NumberField(0));
+            textProto.Set(KeyStore.Width, new NumberField(300));
+            textProto.Set(KeyStore.Height, new NumberField(150));
+            textProto.Set(KeyStore.Layout, new TextField(FormattedTextBox.LayoutString()));
+            textProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
+        }
+        return textProto;
+    }
+
+    export function TextDocument(options: DocumentOptions = {}): Document {
+        let doc = GetTextPrototype().MakeDelegate();
+        setupOptions(doc, options);
+        // doc.SetField(KeyStore.Data, new RichTextField());
+        return doc;
+    }
+
+    let schemaProto: Document;
+    function GetSchemaPrototype(): Document {
+        if (!schemaProto) {
+            schemaProto = new Document();
+            schemaProto.Set(KeyStore.X, new NumberField(0));
+            schemaProto.Set(KeyStore.Y, new NumberField(0));
+            schemaProto.Set(KeyStore.Width, new NumberField(300));
+            schemaProto.Set(KeyStore.Height, new NumberField(150));
+            schemaProto.Set(KeyStore.Layout, new TextField(CollectionSchemaView.LayoutString()));
+            schemaProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
+        }
+        return schemaProto;
+    }
+
+    export function SchemaDocument(documents: Array<Document>, options: DocumentOptions = {}): Document {
+        let doc = GetSchemaPrototype().MakeDelegate();
+        setupOptions(doc, options);
+        doc.Set(KeyStore.Data, new ListField(documents));
+        return doc;
+    }
+
+
+    let dockProto: Document;
+    function GetDockPrototype(): Document {
+        if (!dockProto) {
+            dockProto = new Document();
+            dockProto.Set(KeyStore.X, new NumberField(0));
+            dockProto.Set(KeyStore.Y, new NumberField(0));
+            dockProto.Set(KeyStore.Width, new NumberField(300));
+            dockProto.Set(KeyStore.Height, new NumberField(150));
+            dockProto.Set(KeyStore.Layout, new TextField(CollectionDockingView.LayoutString()));
+            dockProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
+        }
+        return dockProto;
+    }
+
+    export function DockDocument(documents: Array<Document>, options: DocumentOptions = {}): Document {
+        let doc = GetDockPrototype().MakeDelegate();
+        setupOptions(doc, options);
+        doc.Set(KeyStore.Data, new ListField(documents));
+        return doc;
+    }
+
+
+    let imageProtoId: FIELD_ID;
+    function GetImagePrototype(): Document {
+        if (imageProtoId === undefined) {
+            let imageProto = new Document();
+            imageProtoId = imageProto.Id;
+            imageProto.Set(KeyStore.Title, new TextField("IMAGE PROTO"));
+            imageProto.Set(KeyStore.X, new NumberField(0));
+            imageProto.Set(KeyStore.Y, new NumberField(0));
+            imageProto.Set(KeyStore.Width, new NumberField(300));
+            imageProto.Set(KeyStore.Height, new NumberField(300));
+            imageProto.Set(KeyStore.Layout, new TextField(ImageBox.LayoutString()));
+            // imageProto.SetField(KeyStore.Layout, new TextField('<div style={"background-image: " + {Data}} />'));
+            imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
+            Server.AddDocument(imageProto);
+            return imageProto;
+        }
+        return Server.GetField(imageProtoId) as Document;
+    }
+
+    export function ImageDocument(url: string, options: DocumentOptions = {}): Document {
+        let doc = GetImagePrototype().MakeDelegate();
+        setupOptions(doc, options);
+        doc.Set(KeyStore.Data, new ImageField(new URL(url)));
+        Server.AddDocument(doc);
+        var sdoc = Server.GetField(doc.Id) as Document;
+        return sdoc;
+    }
+
+    let collectionProto: Document;
+    function GetCollectionPrototype(): Document {
+        if (!collectionProto) {
+            collectionProto = new Document();
+            collectionProto.Set(KeyStore.X, new NumberField(0));
+            collectionProto.Set(KeyStore.Y, new NumberField(0));
+            collectionProto.Set(KeyStore.Scale, new NumberField(1));
+            collectionProto.Set(KeyStore.PanX, new NumberField(0));
+            collectionProto.Set(KeyStore.PanY, new NumberField(0));
+            collectionProto.Set(KeyStore.Width, new NumberField(300));
+            collectionProto.Set(KeyStore.Height, new NumberField(300));
+            collectionProto.Set(KeyStore.Layout, new TextField(CollectionFreeFormView.LayoutString()));
+            collectionProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
+        }
+        return collectionProto;
+    }
+
+    export function CollectionDocument(documents: Array<Document>, options: DocumentOptions = {}): Document {
+        let doc = GetCollectionPrototype().MakeDelegate();
+        setupOptions(doc, options);
+        doc.Set(KeyStore.Data, new ListField(documents));
+        return doc;
+    }
+}
\ No newline at end of file
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
new file mode 100644
index 000000000..f4dcce7c8
--- /dev/null
+++ b/src/client/util/DragManager.ts
@@ -0,0 +1,131 @@
+
+export namespace DragManager {
+    export function Root() {
+        const root = document.getElementById("root");
+        if (!root) {
+            throw new Error("No root element found");
+        }
+        return root;
+    }
+
+    let dragDiv: HTMLDivElement;
+
+    export enum DragButtons {
+        Left = 1, Right = 2, Both = Left | Right
+    }
+
+    interface DragOptions {
+        handlers: DragHandlers;
+
+        hideSource: boolean | (() => boolean);
+    }
+
+    export interface DragDropDisposer {
+        (): void;
+    }
+
+    export class DragCompleteEvent {
+    }
+
+    export interface DragHandlers {
+        dragComplete: (e: DragCompleteEvent) => void;
+    }
+
+    export interface DropOptions {
+        handlers: DropHandlers;
+    }
+
+    export class DropEvent {
+        constructor(readonly x: number, readonly y: number, readonly data: { [id: string]: any }) { }
+    }
+
+    export interface DropHandlers {
+        drop: (e: Event, de: DropEvent) => void;
+    }
+
+    export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer {
+        if ("canDrop" in element.dataset) {
+            throw new Error("Element is already droppable, can't make it droppable again");
+        }
+        element.dataset["canDrop"] = "true";
+        const handler = (e: Event) => {
+            const ce = e as CustomEvent<DropEvent>;
+            options.handlers.drop(e, ce.detail);
+        };
+        element.addEventListener("dashOnDrop", handler);
+        return () => {
+            element.removeEventListener("dashOnDrop", handler);
+            delete element.dataset["canDrop"]
+        };
+    }
+
+
+    let _lastPointerX: number = 0;
+    let _lastPointerY: number = 0;
+    export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options: DragOptions) {
+        if (!dragDiv) {
+            dragDiv = document.createElement("div");
+            DragManager.Root().appendChild(dragDiv);
+        }
+        const w = ele.offsetWidth, h = ele.offsetHeight;
+        const rect = ele.getBoundingClientRect();
+        const scaleX = rect.width / w, scaleY = rect.height / h;
+        let x = rect.left, y = rect.top;
+        // const offsetX = e.x - rect.left, offsetY = e.y - rect.top;
+        let dragElement = ele.cloneNode(true) as HTMLElement;
+        dragElement.style.opacity = "0.7";
+        dragElement.style.position = "absolute";
+        dragElement.style.transformOrigin = "0 0";
+        dragElement.style.zIndex = "1000";
+        dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+        dragDiv.appendChild(dragElement);
+        _lastPointerX = dragData["xOffset"] + rect.left;
+        _lastPointerY = dragData["yOffset"] + rect.top;
+
+        let hideSource = false;
+        if (typeof options.hideSource === "boolean") {
+            hideSource = options.hideSource;
+        } else {
+            hideSource = options.hideSource();
+        }
+        const wasHidden = ele.hidden;
+        if (hideSource) {
+            ele.hidden = true;
+        }
+
+        const moveHandler = (e: PointerEvent) => {
+            e.stopPropagation();
+            e.preventDefault();
+            x += e.clientX - _lastPointerX; _lastPointerX = e.clientX;
+            y += e.clientY - _lastPointerY; _lastPointerY = e.clientY;
+            dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+        };
+        const upHandler = (e: PointerEvent) => {
+            document.removeEventListener("pointermove", moveHandler, true);
+            document.removeEventListener("pointerup", upHandler);
+            FinishDrag(dragElement, e, options, dragData);
+            if (hideSource && !wasHidden) {
+                ele.hidden = false;
+            }
+        };
+        document.addEventListener("pointermove", moveHandler, true);
+        document.addEventListener("pointerup", upHandler);
+    }
+
+    function FinishDrag(dragEle: HTMLElement, e: PointerEvent, options: DragOptions, dragData: { [index: string]: any }) {
+        dragDiv.removeChild(dragEle);
+        const target = document.elementFromPoint(e.x, e.y);
+        if (!target) {
+            return;
+        }
+        target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", {
+            bubbles: true,
+            detail: {
+                x: e.x,
+                y: e.y,
+                data: dragData
+            }
+        }));
+        options.handlers.dragComplete({});
+    }
+}
\ No newline at end of file
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
new file mode 100644
index 000000000..6bc5fa412
--- /dev/null
+++ b/src/client/util/Scripting.ts
@@ -0,0 +1,58 @@
+// import * as ts from "typescript"
+let ts = (window as any).ts;
+import { Opt, Field } from "../../fields/Field";
+import { Document as DocumentImport } from "../../fields/Document";
+import { NumberField as NumberFieldImport, NumberField } from "../../fields/NumberField";
+import { ImageField as ImageFieldImport } from "../../fields/ImageField";
+import { TextField as TextFieldImport, TextField } from "../../fields/TextField";
+import { RichTextField as RichTextFieldImport } from "../../fields/RichTextField";
+import { KeyStore as KeyStoreImport } from "../../fields/Key";
+
+export interface ExecutableScript {
+    (): any;
+
+    compiled: boolean;
+}
+
+function ExecScript(script: string, diagnostics: Opt<any[]>): ExecutableScript {
+    const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error));
+
+    let func: () => Opt<Field>;
+    if (compiled) {
+        func = function (): Opt<Field> {
+            let KeyStore = KeyStoreImport;
+            let Document = DocumentImport;
+            let NumberField = NumberFieldImport;
+            let TextField = TextFieldImport;
+            let ImageField = ImageFieldImport;
+            let RichTextField = RichTextFieldImport;
+            let window = undefined;
+            let document = undefined;
+            let retVal = eval(script);
+
+            return retVal;
+        };
+    } else {
+        func = () => undefined;
+    }
+
+    return Object.assign(func,
+        {
+            compiled
+        });
+}
+
+export function CompileScript(script: string): ExecutableScript {
+    let result = (window as any).ts.transpileModule(script, {})
+
+    return ExecScript(result.outputText, result.diagnostics);
+}
+
+export function ToField(data: any): Opt<Field> {
+    if (typeof data == "string") {
+        return new TextField(data);
+    } else if (typeof data == "number") {
+        return new NumberField(data);
+    }
+    return undefined;
+}
\ No newline at end of file
diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx
new file mode 100644
index 000000000..b6b088170
--- /dev/null
+++ b/src/client/util/ScrollBox.tsx
@@ -0,0 +1,21 @@
+import React = require("react")
+
+export class ScrollBox extends React.Component {
+    onWheel = (e: React.WheelEvent) => {
+        if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { // If the element has a scroll bar, then we don't want the containing collection to zoom
+            e.stopPropagation();
+        }
+    }
+
+    render() {
+        return (
+            <div style={{
+                overflow: "auto",
+                width: "100%",
+                height: "100%",
+            }} onWheel={this.onWheel}>
+                {this.props.children}
+            </div>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
new file mode 100644
index 000000000..0759ae110
--- /dev/null
+++ b/src/client/util/SelectionManager.ts
@@ -0,0 +1,39 @@
+import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView";
+import { observable, action } from "mobx";
+
+export namespace SelectionManager {
+    class Manager {
+        @observable
+        SelectedDocuments: Array<CollectionFreeFormDocumentView> = [];
+
+        @action
+        SelectDoc(doc: CollectionFreeFormDocumentView, ctrlPressed: boolean): void {
+            // if doc is not in SelectedDocuments, add it
+            if (!ctrlPressed) {
+                manager.SelectedDocuments = [];
+            }
+
+            if (manager.SelectedDocuments.indexOf(doc) === -1) {
+                manager.SelectedDocuments.push(doc)
+            }
+        }
+    }
+
+    const manager = new Manager;
+
+    export function SelectDoc(doc: CollectionFreeFormDocumentView, ctrlPressed: boolean): void {
+        manager.SelectDoc(doc, ctrlPressed)
+    }
+
+    export function IsSelected(doc: CollectionFreeFormDocumentView): boolean {
+        return manager.SelectedDocuments.indexOf(doc) !== -1;
+    }
+
+    export function DeselectAll(): void {
+        manager.SelectedDocuments = []
+    }
+
+    export function SelectedDocuments(): Array<CollectionFreeFormDocumentView> {
+        return manager.SelectedDocuments;
+    }
+}
\ No newline at end of file
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
new file mode 100644
index 000000000..0714a7f5c
--- /dev/null
+++ b/src/client/util/TypedEvent.ts
@@ -0,0 +1,42 @@
+export interface Listener<T> {
+  (event: T): any;
+}
+
+export interface Disposable {
+  dispose(): void;
+}
+
+/** passes through events as they happen. You will not get events from before you start listening */
+export class TypedEvent<T> {
+  private listeners: Listener<T>[] = [];
+  private listenersOncer: Listener<T>[] = [];
+
+  on = (listener: Listener<T>): Disposable => {
+    this.listeners.push(listener);
+    return {
+      dispose: () => this.off(listener)
+    };
+  }
+
+  once = (listener: Listener<T>): void => {
+    this.listenersOncer.push(listener);
+  }
+
+  off = (listener: Listener<T>) => {
+    var callbackIndex = this.listeners.indexOf(listener);
+    if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
+  }
+
+  emit = (event: T) => {
+    /** Update any general listeners */
+    this.listeners.forEach((listener) => listener(event));
+
+    /** Clear the `once` queue */
+    this.listenersOncer.forEach((listener) => listener(event));
+    this.listenersOncer = [];
+  }
+
+  pipe = (te: TypedEvent<T>): Disposable => {
+    return this.on((e) => te.emit(e));
+  }
+}
\ No newline at end of file
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
new file mode 100644
index 000000000..234f82eb9
--- /dev/null
+++ b/src/client/views/ContextMenu.scss
@@ -0,0 +1,34 @@
+.contextMenu-cont {
+    position: absolute;
+    display: flex;
+    z-index: 1000;
+    box-shadow: #AAAAAA .2vw .2vw .4vw;
+}
+
+.contextMenu-item {
+    width: 10vw;
+    height: 4vh;
+    background: #DDDDDD;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    transition: all .1s;
+}
+
+.contextMenu-item:hover {
+    transition: all .1s;
+    background: #AAAAAA
+}
+
+.contextMenu-description {
+    font-size: 1.5vw;
+    text-align: left;
+    width: 8vw;
+}
\ No newline at end of file
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
new file mode 100644
index 000000000..4f26a75d2
--- /dev/null
+++ b/src/client/views/ContextMenu.tsx
@@ -0,0 +1,68 @@
+import React = require("react");
+import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
+import { observable } from "mobx";
+import { observer } from "mobx-react";
+import "./ContextMenu.scss"
+
+@observer
+export class ContextMenu extends React.Component {
+    static Instance: ContextMenu
+
+    @observable private _items: Array<ContextMenuProps> = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault() }];
+    @observable private _pageX: number = 0;
+    @observable private _pageY: number = 0;
+    @observable private _display: string = "none";
+
+    private ref: React.RefObject<HTMLDivElement>;
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+
+        this.ref = React.createRef()
+
+        ContextMenu.Instance = this;
+    }
+
+    clearItems() {
+        this._items = []
+        this._display = "none"
+    }
+
+    addItem(item: ContextMenuProps) {
+        if (this._items.indexOf(item) === -1) {
+            this._items.push(item);
+        }
+    }
+
+    getItems() {
+        return this._items;
+    }
+
+    displayMenu(x: number, y: number) {
+        this._pageX = x
+        this._pageY = y
+
+        this._display = "flex"
+    }
+
+    intersects = (x: number, y: number): boolean => {
+        if (this.ref.current && this._display !== "none") {
+            if (x >= this._pageX && x <= this._pageX + this.ref.current.getBoundingClientRect().width) {
+                if (y >= this._pageY && y <= this._pageY + this.ref.current.getBoundingClientRect().height) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    render() {
+        return (
+            <div className="contextMenu-cont" style={{ left: this._pageX, top: this._pageY, display: this._display }} ref={this.ref}>
+                {this._items.map(prop => {
+                    return <ContextMenuItem {...prop} key={prop.description} />
+                })}
+            </div>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
new file mode 100644
index 000000000..8f00f8b3d
--- /dev/null
+++ b/src/client/views/ContextMenuItem.tsx
@@ -0,0 +1,17 @@
+import React = require("react");
+import { ContextMenu } from "./ContextMenu";
+
+export interface ContextMenuProps {
+    description: string;
+    event: (e: React.MouseEvent<HTMLDivElement>) => void;
+}
+
+export class ContextMenuItem extends React.Component<ContextMenuProps> {
+    render() {
+        return (
+            <div className="contextMenu-item" onClick={this.props.event}>
+                <div className="contextMenu-description">{this.props.description}</div>
+            </div>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
new file mode 100644
index 000000000..e8b93a18b
--- /dev/null
+++ b/src/client/views/DocumentDecorations.scss
@@ -0,0 +1,32 @@
+#documentDecorations-container {
+    position: absolute;
+    display: grid;
+    z-index: 1000;
+    grid-template-rows: 20px 1fr 20px;
+    grid-template-columns: 20px 1fr 20px;
+    pointer-events: none;
+    #documentDecorations-centerCont {
+        background: none;
+    }
+    .documentDecorations-resizer {
+        pointer-events: auto;
+        background: lightblue;
+        opacity: 0.4;
+    }
+    #documentDecorations-topLeftResizer,
+    #documentDecorations-bottomRightResizer {
+        cursor: nwse-resize;
+    }
+    #documentDecorations-topRightResizer,
+    #documentDecorations-bottomLeftResizer {
+        cursor: nesw-resize;
+    }
+    #documentDecorations-topResizer,
+    #documentDecorations-bottomResizer {
+        cursor: ns-resize;
+    }
+    #documentDecorations-leftResizer,
+    #documentDecorations-rightResizer {
+        cursor: ew-resize;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
new file mode 100644
index 000000000..8a94bff36
--- /dev/null
+++ b/src/client/views/DocumentDecorations.tsx
@@ -0,0 +1,153 @@
+import { observable, computed } from "mobx";
+import React = require("react");
+import { SelectionManager } from "../util/SelectionManager";
+import { observer } from "mobx-react";
+import './DocumentDecorations.scss'
+import { CollectionFreeFormView } from "./collections/CollectionFreeFormView";
+
+@observer
+export class DocumentDecorations extends React.Component {
+    static Instance: DocumentDecorations
+    private _resizer = ""
+    private _isPointerDown = false;
+    @observable private _opacity = 1;
+
+    constructor(props: Readonly<{}>) {
+        super(props)
+
+        DocumentDecorations.Instance = this
+    }
+
+    @computed
+    get Bounds(): { x: number, y: number, b: number, r: number } {
+        return SelectionManager.SelectedDocuments().reduce((bounds, element) => {
+            if (element.props.ContainingCollectionView != undefined &&
+                !(element.props.ContainingCollectionView instanceof CollectionFreeFormView)) {
+                return bounds;
+            }
+            var spt = element.TransformToScreenPoint(0, 0);
+            var bpt = element.TransformToScreenPoint(element.width, element.height);
+            return {
+                x: Math.min(spt.ScreenX, bounds.x), y: Math.min(spt.ScreenY, bounds.y),
+                r: Math.max(bpt.ScreenX, bounds.r), b: Math.max(bpt.ScreenY, bounds.b)
+            }
+        }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
+    }
+
+    @computed
+    get opacity(): number {
+        return this._opacity
+    }
+
+    set opacity(o: number) {
+        this._opacity = Math.min(Math.max(0, o), 1)
+    }
+
+    onPointerDown = (e: React.PointerEvent): void => {
+        e.stopPropagation();
+        if (e.button === 0) {
+            this._isPointerDown = true;
+            this._resizer = e.currentTarget.id;
+            document.removeEventListener("pointermove", this.onPointerMove);
+            document.addEventListener("pointermove", this.onPointerMove);
+            document.removeEventListener("pointerup", this.onPointerUp);
+            document.addEventListener("pointerup", this.onPointerUp);
+        }
+    }
+
+    onPointerMove = (e: PointerEvent): void => {
+        e.stopPropagation();
+        e.preventDefault();
+        if (!this._isPointerDown) {
+            return;
+        }
+
+        let dX = 0, dY = 0, dW = 0, dH = 0;
+
+        switch (this._resizer) {
+            case "":
+                break;
+            case "documentDecorations-topLeftResizer":
+                dX = -1
+                dY = -1
+                dW = -(e.movementX)
+                dH = -(e.movementY)
+                break;
+            case "documentDecorations-topRightResizer":
+                dW = e.movementX
+                dY = -1
+                dH = -(e.movementY)
+                break;
+            case "documentDecorations-topResizer":
+                dY = -1
+                dH = -(e.movementY)
+                break;
+            case "documentDecorations-bottomLeftResizer":
+                dX = -1
+                dW = -(e.movementX)
+                dH = e.movementY
+                break;
+            case "documentDecorations-bottomRightResizer":
+                dW = e.movementX
+                dH = e.movementY
+                break;
+            case "documentDecorations-bottomResizer":
+                dH = e.movementY
+                break;
+            case "documentDecorations-leftResizer":
+                dX = -1
+                dW = -(e.movementX)
+                break;
+            case "documentDecorations-rightResizer":
+                dW = e.movementX
+                break;
+        }
+
+        SelectionManager.SelectedDocuments().forEach(element => {
+            const rect = element.screenRect;
+            if (rect.width !== 0) {
+                let scale = element.width / rect.width;
+                let actualdW = Math.max(element.width + (dW * scale), 20);
+                let actualdH = Math.max(element.height + (dH * scale), 20);
+                element.x += dX * (actualdW - element.width);
+                element.y += dY * (actualdH - element.height);
+                element.width = actualdW;
+                element.height = actualdH;
+            }
+        })
+    }
+
+    onPointerUp = (e: PointerEvent): void => {
+        e.stopPropagation();
+        if (e.button === 0) {
+            e.preventDefault();
+            this._isPointerDown = false;
+            document.removeEventListener("pointermove", this.onPointerMove);
+            document.removeEventListener("pointerup", this.onPointerUp);
+        }
+    }
+
+    render() {
+        var bounds = this.Bounds;
+        return (
+            <div id="documentDecorations-container" style={{
+                width: (bounds.r - bounds.x + 40) + "px",
+                height: (bounds.b - bounds.y + 40) + "px",
+                left: bounds.x - 20,
+                top: bounds.y - 20,
+                opacity: this.opacity
+            }}>
+                <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                <div id="documentDecorations-topResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                <div id="documentDecorations-topRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                <div id="documentDecorations-leftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                <div id="documentDecorations-centerCont"></div>
+                <div id="documentDecorations-rightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+
+            </div>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
new file mode 100644
index 000000000..2e784d3f9
--- /dev/null
+++ b/src/client/views/EditableView.tsx
@@ -0,0 +1,39 @@
+import React = require('react')
+import { observer } from 'mobx-react';
+import { observable, action } from 'mobx';
+
+export interface EditableProps {
+    GetValue(): string;
+    SetValue(value: string): boolean;
+    contents: any;
+}
+
+@observer
+export class EditableView extends React.Component<EditableProps> {
+    @observable
+    editing: boolean = false;
+
+    @action
+    onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+        if (e.key == "Enter" && !e.ctrlKey) {
+            this.props.SetValue(e.currentTarget.value);
+            this.editing = false;
+        } else if (e.key == "Escape") {
+            this.editing = false;
+        }
+    }
+
+    render() {
+        if (this.editing) {
+            return <input defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
+                style={{ width: "100%" }}></input>
+        } else {
+            return (
+                <div>
+                    {this.props.contents}
+                    <button onClick={action(() => this.editing = true)}>Edit</button>
+                </div>
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
new file mode 100644
index 000000000..e73f62904
--- /dev/null
+++ b/src/client/views/Main.scss
@@ -0,0 +1,31 @@
+html,
+body {
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+    font-family: 'Hind Siliguri', sans-serif;
+    margin: 0;
+}
+
+h1 {
+    font-size: 50px;
+    position: fixed;
+    top: 30px;
+    left: 50%;
+    transform: translateX(-50%);
+    color: black;
+    text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
+    z-index: 9999;
+    font-family: 'Fjalla One', sans-serif;
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+}
+
+p {
+    margin: 0px;
+    padding: 0px;
+}
\ No newline at end of file
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
new file mode 100644
index 000000000..fc6f0a208
--- /dev/null
+++ b/src/client/views/Main.tsx
@@ -0,0 +1,91 @@
+import { action, configure } from 'mobx';
+import "normalize.css";
+import * as React from 'react';
+import * as ReactDOM from 'react-dom';
+import { DocumentDecorations } from './DocumentDecorations';
+import { Documents } from '../documents/Documents';
+import { Document } from '../../fields/Document';
+import { KeyStore, KeyStore as KS } from '../../fields/Key';
+import { ListField } from '../../fields/ListField';
+import { NumberField } from '../../fields/NumberField';
+import { TextField } from '../../fields/TextField';
+import "./Main.scss";
+import { ContextMenu } from './ContextMenu';
+import { DocumentView } from './nodes/DocumentView';
+import { ImageField } from '../../fields/ImageField';
+
+
+configure({
+    enforceActions: "observed"
+});
+
+const mainNodeCollection = new Array<Document>();
+let mainContainer = Documents.DockDocument(mainNodeCollection, {
+    x: 0, y: 0, title: "main container"
+})
+
+window.addEventListener("drop", function (e) {
+    e.preventDefault();
+}, false)
+window.addEventListener("dragover", function (e) {
+    e.preventDefault();
+}, false)
+document.addEventListener("pointerdown", action(function (e: PointerEvent) {
+    if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
+        ContextMenu.Instance.clearItems()
+    }
+}), true)
+
+
+//runInAction(() => 
+{
+    let doc1 = Documents.TextDocument({ title: "hello" });
+    let doc2 = doc1.MakeDelegate();
+    doc2.Set(KS.X, new NumberField(150));
+    doc2.Set(KS.Y, new NumberField(20));
+    let doc3 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
+        x: 450, y: 500, title: "cat 1"
+    });
+    doc3.Set(KeyStore.Data, new ImageField);
+    const schemaDocs = Array.from(Array(5).keys()).map(v => Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
+        x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v
+    }));
+    schemaDocs[0].SetData(KS.Author, "Tyler", TextField);
+    schemaDocs[4].SetData(KS.Author, "Bob", TextField);
+    schemaDocs.push(doc2);
+    const doc7 = Documents.SchemaDocument(schemaDocs)
+    const docset = [doc1, doc2, doc3, doc7];
+    let doc4 = Documents.CollectionDocument(docset, {
+        x: 0, y: 400, title: "mini collection"
+    });
+    // let doc5 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
+    //     x: 650, y: 500, width: 600, height: 600, title: "cat 2"
+    // });
+    let docset2 = new Array<Document>(doc4);//, doc1, doc3);
+    let doc6 = Documents.CollectionDocument(docset2, {
+        x: 350, y: 100, width: 600, height: 600, title: "docking collection"
+    });
+    let mainNodes = null;// mainContainer.GetFieldT(KeyStore.Data, ListField);
+    if (!mainNodes) {
+        mainNodes = new ListField<Document>();
+    }
+    // mainNodes.Data.push(doc6);
+    // mainNodes.Data.push(doc2);
+    mainNodes.Data.push(doc4);
+    mainNodes.Data.push(doc3);
+    // mainNodes.Data.push(doc5);
+    // mainNodes.Data.push(doc1);
+    //mainNodes.Data.push(doc2);
+    //mainNodes.Data.push(doc6);
+    mainContainer.Set(KeyStore.Data, mainNodes);
+}
+//}
+//);
+
+ReactDOM.render((
+    <div style={{ position: "absolute", width: "100%", height: "100%" }}>
+        <DocumentView Document={mainContainer} ContainingCollectionView={undefined} DocumentView={undefined} />
+        <DocumentDecorations />
+        <ContextMenu />
+    </div>),
+    document.getElementById('root'));
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
new file mode 100644
index 000000000..7c0b512a7
--- /dev/null
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -0,0 +1,336 @@
+.collectiondockingview-container {
+    position: relative;
+    top: 0;
+    left: 0;
+    overflow: hidden;
+    .lm_controls>li {
+        opacity: 0.6;
+        transform: scale(1.2);
+    }
+    .lm_maximised .lm_controls .lm_maximise {
+        opacity: 1;
+        transform: scale(0.8);
+        background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==) !important;
+    }
+    .flexlayout__layout {
+        left: 0;
+        top: 0;
+        right: 0;
+        bottom: 0;
+        position: absolute;
+        overflow: hidden;
+    }
+    .flexlayout__splitter {
+        background-color: black;
+    }
+    .flexlayout__splitter:hover {
+        background-color: #333;
+    }
+    .flexlayout__splitter_drag {
+        border-radius: 5px;
+        background-color: #444;
+        z-index: 1000;
+    }
+    .flexlayout__outline_rect {
+        position: absolute;
+        cursor: move;
+        border: 2px solid #cfe8ff;
+        box-shadow: inset 0 0 60px rgba(0, 0, 0, .2);
+        border-radius: 5px;
+        z-index: 1000;
+        box-sizing: border-box;
+    }
+    .flexlayout__outline_rect_edge {
+        cursor: move;
+        border: 2px solid #b7d1b5;
+        box-shadow: inset 0 0 60px rgba(0, 0, 0, .2);
+        border-radius: 5px;
+        z-index: 1000;
+        box-sizing: border-box;
+    }
+    .flexlayout__edge_rect {
+        position: absolute;
+        z-index: 1000;
+        box-shadow: inset 0 0 5px rgba(0, 0, 0, .2);
+        background-color: lightgray;
+    }
+    .flexlayout__drag_rect {
+        position: absolute;
+        cursor: move;
+        border: 2px solid #aaaaaa;
+        box-shadow: inset 0 0 60px rgba(0, 0, 0, .3);
+        border-radius: 5px;
+        z-index: 1000;
+        box-sizing: border-box;
+        background-color: #eeeeee;
+        opacity: 0.9;
+        text-align: center;
+        display: flex;
+        justify-content: center;
+        flex-direction: column;
+        overflow: hidden;
+        padding: 10px;
+        word-wrap: break-word;
+    }
+    .flexlayout__tabset {
+        overflow: hidden;
+        background-color: #222;
+        box-sizing: border-box;
+    }
+    .flexlayout__tab {
+        overflow: auto;
+        position: absolute;
+        box-sizing: border-box;
+        background-color: #222;
+        color: black;
+    }
+    .flexlayout__tab_button {
+        cursor: pointer;
+        padding: 2px 8px 3px 8px;
+        margin: 2px;
+        /*box-shadow: inset 0px 0px 5px rgba(0, 0, 0, .15);*/
+        /*border-top-left-radius: 3px;*/
+        /*border-top-right-radius: 3px;*/
+        float: left;
+        vertical-align: top;
+        box-sizing: border-box;
+    }
+    .flexlayout__tab_button--selected {
+        color: #ddd;
+        background-color: #222;
+    }
+    .flexlayout__tab_button--unselected {
+        color: gray;
+    }
+    .flexlayout__tab_button_leading {
+        display: inline-block;
+    }
+    .flexlayout__tab_button_content {
+        display: inline-block;
+    }
+    .flexlayout__tab_button_textbox {
+        float: left;
+        border: none;
+        color: lightgreen;
+        background-color: #222;
+    }
+    .flexlayout__tab_button_textbox:focus {
+        outline: none;
+    }
+    .flexlayout__tab_button_trailing {
+        display: inline-block;
+        margin-left: 5px;
+        margin-top: 3px;
+        width: 8px;
+        height: 8px;
+    }
+    .flexlayout__tab_button:hover .flexlayout__tab_button_trailing,
+    .flexlayout__tab_button--selected .flexlayout__tab_button_trailing {
+        background: transparent url("../../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center;
+    }
+    .flexlayout__tab_button_overflow {
+        float: left;
+        width: 20px;
+        height: 15px;
+        margin-top: 2px;
+        padding-left: 12px;
+        border: none;
+        font-size: 10px;
+        color: lightgray;
+        font-family: Arial, sans-serif;
+        background: transparent url("../../../../node_modules/flexlayout-react/images/more.png") no-repeat left;
+    }
+    .flexlayout__tabset_header {
+        position: absolute;
+        left: 0;
+        right: 0;
+        color: #eee;
+        background-color: #212121;
+        padding: 3px 3px 3px 5px;
+        /*box-shadow: inset 0px 0px 3px 0px rgba(136, 136, 136, 0.54);*/
+        box-sizing: border-box;
+    }
+    .flexlayout__tab_header_inner {
+        position: absolute;
+        left: 0;
+        top: 0;
+        bottom: 0;
+        width: 10000px;
+    }
+    .flexlayout__tab_header_outer {
+        background-color: black;
+        position: absolute;
+        left: 0;
+        right: 0;
+        /*top: 0px;*/
+        /*height: 100px;*/
+        overflow: hidden;
+    }
+    .flexlayout__tabset-selected {
+        background-image: linear-gradient(#111, #444);
+    }
+    .flexlayout__tabset-maximized {
+        background-image: linear-gradient(#666, #333);
+    }
+    .flexlayout__tab_toolbar {
+        position: absolute;
+        display: flex;
+        flex-direction: row-reverse;
+        align-items: center;
+        top: 0;
+        bottom: 0;
+        right: 0;
+    }
+    .flexlayout__tab_toolbar_button-min {
+        width: 20px;
+        height: 20px;
+        border: none;
+        outline-width: 0;
+        background: transparent url("../../../../node_modules/flexlayout-react/images/maximize.png") no-repeat center;
+    }
+    .flexlayout__tab_toolbar_button-max {
+        width: 20px;
+        height: 20px;
+        border: none;
+        outline-width: 0;
+        background: transparent url("../../../../node_modules/flexlayout-react/images/restore.png") no-repeat center;
+    }
+    .flexlayout__popup_menu {}
+    .flexlayout__popup_menu_item {
+        padding: 2px 10px 2px 10px;
+        color: #ddd;
+    }
+    .flexlayout__popup_menu_item:hover {
+        background-color: #444444;
+    }
+    .flexlayout__popup_menu_container {
+        box-shadow: inset 0 0 5px rgba(0, 0, 0, .15);
+        border: 1px solid #555;
+        background: #222;
+        border-radius: 3px;
+        position: absolute;
+        z-index: 1000;
+    }
+    .flexlayout__border_top {
+        background-color: black;
+        border-bottom: 1px solid #ddd;
+        box-sizing: border-box;
+        overflow: hidden;
+    }
+    .flexlayout__border_bottom {
+        background-color: black;
+        border-top: 1px solid #333;
+        box-sizing: border-box;
+        overflow: hidden;
+    }
+    .flexlayout__border_left {
+        background-color: black;
+        border-right: 1px solid #333;
+        box-sizing: border-box;
+        overflow: hidden;
+    }
+    .flexlayout__border_right {
+        background-color: black;
+        border-left: 1px solid #333;
+        box-sizing: border-box;
+        overflow: hidden;
+    }
+    .flexlayout__border_inner_bottom {
+        display: flex;
+    }
+    .flexlayout__border_inner_left {
+        position: absolute;
+        white-space: nowrap;
+        right: 23px;
+        transform-origin: top right;
+        transform: rotate(-90deg);
+    }
+    .flexlayout__border_inner_right {
+        position: absolute;
+        white-space: nowrap;
+        left: 23px;
+        transform-origin: top left;
+        transform: rotate(90deg);
+    }
+    .flexlayout__border_button {
+        background-color: #222;
+        color: white;
+        display: inline-block;
+        white-space: nowrap;
+        cursor: pointer;
+        padding: 2px 8px 3px 8px;
+        margin: 2px;
+        vertical-align: top;
+        box-sizing: border-box;
+    }
+    .flexlayout__border_button--selected {
+        color: #ddd;
+        background-color: #222;
+    }
+    .flexlayout__border_button--unselected {
+        color: gray;
+    }
+    .flexlayout__border_button_leading {
+        float: left;
+        display: inline;
+    }
+    .flexlayout__border_button_content {
+        display: inline-block;
+    }
+    .flexlayout__border_button_textbox {
+        float: left;
+        border: none;
+        color: green;
+        background-color: #ddd;
+    }
+    .flexlayout__border_button_textbox:focus {
+        outline: none;
+    }
+    .flexlayout__border_button_trailing {
+        display: inline-block;
+        margin-left: 5px;
+        margin-top: 3px;
+        width: 8px;
+        height: 8px;
+    }
+    .flexlayout__border_button:hover .flexlayout__border_button_trailing,
+    .flexlayout__border_button--selected .flexlayout__border_button_trailing {
+        background: transparent url("../../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center;
+    }
+    .flexlayout__border_toolbar_left {
+        position: absolute;
+        display: flex;
+        flex-direction: column-reverse;
+        align-items: center;
+        bottom: 0;
+        left: 0;
+        right: 0;
+    }
+    .flexlayout__border_toolbar_right {
+        position: absolute;
+        display: flex;
+        flex-direction: column-reverse;
+        align-items: center;
+        bottom: 0;
+        left: 0;
+        right: 0;
+    }
+    .flexlayout__border_toolbar_top {
+        position: absolute;
+        display: flex;
+        flex-direction: row-reverse;
+        align-items: center;
+        top: 0;
+        bottom: 0;
+        right: 0;
+    }
+    .flexlayout__border_toolbar_bottom {
+        position: absolute;
+        display: flex;
+        flex-direction: row-reverse;
+        align-items: center;
+        top: 0;
+        bottom: 0;
+        right: 0;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
new file mode 100644
index 000000000..9aee9c10f
--- /dev/null
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -0,0 +1,281 @@
+import { observer } from "mobx-react";
+import { KeyStore } from "../../../fields/Key";
+import React = require("react");
+import FlexLayout from "flexlayout-react";
+import { action, observable, computed } from "mobx";
+import { Document } from "../../../fields/Document";
+import { DocumentView } from "../nodes/DocumentView";
+import { ListField } from "../../../fields/ListField";
+import { NumberField } from "../../../fields/NumberField";
+import "./CollectionDockingView.scss"
+import 'golden-layout/src/css/goldenlayout-base.css';
+import 'golden-layout/src/css/goldenlayout-dark-theme.css';
+import * as GoldenLayout from "golden-layout";
+import * as ReactDOM from 'react-dom';
+import { DragManager } from "../../util/DragManager";
+import { CollectionViewBase, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase";
+
+@observer
+export class CollectionDockingView extends CollectionViewBase {
+
+    private static UseGoldenLayout = true;
+    public static LayoutString() { return CollectionViewBase.LayoutString("CollectionDockingView"); }
+    private _containerRef = React.createRef<HTMLDivElement>();
+    @computed
+    private get modelForFlexLayout() {
+        const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
+        const value: Document[] = Document.GetData(fieldKey, ListField, []);
+        var docs = value.map(doc => {
+            return { type: 'tabset', weight: 50, selected: 0, children: [{ type: "tab", name: doc.Title, component: doc.Id }] };
+        });
+        return FlexLayout.Model.fromJson({
+            global: {}, borders: [],
+            layout: {
+                "type": "row",
+                "weight": 100,
+                "children": docs
+            }
+        });
+    }
+    @computed
+    private get modelForGoldenLayout(): any {
+        const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
+        const value: Document[] = Document.GetData(fieldKey, ListField, []);
+        var docs = value.map(doc => {
+            return { type: 'component', componentName: 'documentViewComponent', componentState: { doc: doc } };
+        });
+        return new GoldenLayout({
+            settings: {
+                selectionEnabled: true
+            }, content: [{ type: 'row', content: docs }]
+        });
+    }
+    constructor(props: CollectionViewProps) {
+        super(props);
+    }
+
+    componentDidMount: () => void = () => {
+        if (this._containerRef.current && CollectionDockingView.UseGoldenLayout) {
+            this.goldenLayoutFactory();
+            window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
+        }
+    }
+    componentWillUnmount: () => void = () => {
+        window.removeEventListener('resize', this.onResize);
+    }
+    private nextId = (function () { var _next_id = 0; return function () { return _next_id++; } })();
+
+
+    @action
+    onResize = (event: any) => {
+        var cur = this.props.ContainingDocumentView!.MainContent.current;
+
+        // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
+        CollectionDockingView.myLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
+    }
+
+    @action
+    onPointerDown = (e: React.PointerEvent): void => {
+        if (e.button === 2 && this.active) {
+            e.stopPropagation();
+            e.preventDefault();
+        } else {
+            if (e.buttons === 1 && this.active) {
+                e.stopPropagation();
+            }
+        }
+    }
+
+    flexLayoutFactory = (node: any): any => {
+        var component = node.getComponent();
+        if (component === "button") {
+            return <button>{node.getName()}</button>;
+        }
+        const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
+        const value: Document[] = Document.GetData(fieldKey, ListField, []);
+        for (var i: number = 0; i < value.length; i++) {
+            if (value[i].Id === component) {
+                return (<DocumentView key={value[i].Id} ContainingCollectionView={this} Document={value[i]} DocumentView={undefined} />);
+            }
+        }
+        if (component === "text") {
+            return (<div className="panel">Panel {node.getName()}</div>);
+        }
+    }
+
+    public static myLayout: any = null;
+
+    private static _dragDiv: any = null;
+    private static _dragParent: HTMLElement | null = null;
+    private static _dragElement: HTMLDivElement;
+    private static _dragFakeElement: HTMLDivElement;
+    public static StartOtherDrag(dragElement: HTMLDivElement, dragDoc: Document) {
+        var newItemConfig = {
+            type: 'component',
+            componentName: 'documentViewComponent',
+            componentState: { doc: dragDoc }
+        };
+        this._dragElement = dragElement;
+        this._dragParent = dragElement.parentElement;
+        // bcz: we want to copy this document into the header, not move it there.
+        //   However, GoldenLayout is setup to move things, so we have to do some kludgy stuff:
+
+        //   - create a temporary invisible div and register that as a DragSource with GoldenLayout
+        this._dragDiv = document.createElement("div");
+        this._dragDiv.style.opacity = 0;
+        DragManager.Root().appendChild(this._dragDiv);
+        CollectionDockingView.myLayout.createDragSource(this._dragDiv, newItemConfig);
+
+        //   - add our document to that div so that GoldenLayout will get the move events its listening for
+        this._dragDiv.appendChild(this._dragElement);
+
+        //   - add a duplicate of our document to the original document's container 
+        //     (GoldenLayout will be removing our original one)
+        this._dragFakeElement = dragElement.cloneNode(true) as HTMLDivElement;
+        this._dragParent!.appendChild(this._dragFakeElement);
+
+        // all of this must be undone when the document has been dropped (see tabCreated)
+    }
+
+    _makeFullScreen: boolean = false;
+    _maximizedStack: any = null;
+    public static OpenFullScreen(document: Document) {
+        var newItemConfig = {
+            type: 'component',
+            componentName: 'documentViewComponent',
+            componentState: { doc: document }
+        };
+        CollectionDockingView.myLayout._makeFullScreen = true;
+        CollectionDockingView.myLayout.root.contentItems[0].addChild(newItemConfig);
+    }
+    public static CloseFullScreen() {
+        if (CollectionDockingView.myLayout._maximizedStack != null) {
+            CollectionDockingView.myLayout._maximizedStack.header.controlsContainer.find('.lm_close').click();
+            CollectionDockingView.myLayout._maximizedStack = null;
+        }
+    }
+
+    //
+    //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
+    //
+    public static AddRightSplit(document: Document) {
+        var newItemConfig = {
+            type: 'component',
+            componentName: 'documentViewComponent',
+            componentState: { doc: document }
+        }
+        let newItemStackConfig = {
+            type: 'stack',
+            content: [newItemConfig]
+        };
+        var newContentItem = new CollectionDockingView.myLayout._typeToItem[newItemStackConfig.type](CollectionDockingView.myLayout, newItemStackConfig, parent);
+
+        if (CollectionDockingView.myLayout.root.contentItems[0].isRow) {
+            var rowlayout = CollectionDockingView.myLayout.root.contentItems[0];
+            var lastRowItem = rowlayout.contentItems[rowlayout.contentItems.length - 1];
+
+            lastRowItem.config["width"] *= 0.5;
+            newContentItem.config["width"] = lastRowItem.config["width"];
+            rowlayout.addChild(newContentItem, rowlayout.contentItems.length, true);
+            rowlayout.callDownwards('setSize');
+        }
+        else {
+            var collayout = CollectionDockingView.myLayout.root.contentItems[0];
+            var newRow = collayout.layoutManager.createContentItem({ type: "row" }, CollectionDockingView.myLayout);
+            collayout.parent.replaceChild(collayout, newRow);
+
+            newRow.addChild(newContentItem, undefined, true);
+            newRow.addChild(collayout, 0, true);
+
+            collayout.config["width"] = 50;
+            newContentItem.config["width"] = 50;
+            collayout.parent.callDownwards('setSize');
+        }
+    }
+    goldenLayoutFactory() {
+        CollectionDockingView.myLayout = this.modelForGoldenLayout;
+
+        var layout = CollectionDockingView.myLayout;
+        CollectionDockingView.myLayout.on('tabCreated', function (tab: any) {
+            if (CollectionDockingView._dragDiv) {
+                CollectionDockingView._dragDiv.removeChild(CollectionDockingView._dragElement);
+                CollectionDockingView._dragParent!.removeChild(CollectionDockingView._dragFakeElement);
+                CollectionDockingView._dragParent!.appendChild(CollectionDockingView._dragElement);
+                DragManager.Root().removeChild(CollectionDockingView._dragDiv);
+                CollectionDockingView._dragDiv = null;
+            }
+            tab.setTitle(tab.contentItem.config.componentState.doc.Title);
+            tab.closeElement.off('click') //unbind the current click handler
+                .click(function () {
+                    tab.contentItem.remove();
+                });
+        });
+
+        CollectionDockingView.myLayout.on('stackCreated', function (stack: any) {
+            if (CollectionDockingView.myLayout._makeFullScreen) {
+                CollectionDockingView.myLayout._maximizedStack = stack;
+                CollectionDockingView.myLayout._maxstack = stack.header.controlsContainer.find('.lm_maximise');
+            }
+            //stack.header.controlsContainer.find('.lm_popout').hide();
+            stack.header.controlsContainer.find('.lm_close') //get the close icon
+                .off('click') //unbind the current click handler
+                .click(function () {
+                    //if (confirm('really close this?')) {
+                    stack.remove();
+                    //}
+                });
+        });
+
+        var me = this;
+        CollectionDockingView.myLayout.registerComponent('documentViewComponent', function (container: any, state: any) {
+            // bcz: this is crufty
+            // calling html() causes a div tag to be added in the DOM with id 'containingDiv'. 
+            // Apparently, we need to wait to allow a live html div element to actually be instantiated.
+            // After a timeout, we lookup the live html div element and add our React DocumentView to it.
+            var containingDiv = "component_" + me.nextId();
+            container.getElement().html("<div id='" + containingDiv + "'></div>");
+            setTimeout(function () {
+                ReactDOM.render((
+                    <DocumentView key={state.doc.Id} Document={state.doc} ContainingCollectionView={me} DocumentView={undefined} />
+                ),
+                    document.getElementById(containingDiv)
+                );
+                if (CollectionDockingView.myLayout._maxstack != null) {
+                    CollectionDockingView.myLayout._maxstack.click();
+                }
+            }, 0);
+        });
+        CollectionDockingView.myLayout.container = this._containerRef.current;
+        CollectionDockingView.myLayout.init();
+    }
+
+
+    render() {
+        const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
+        const value: Document[] = Document.GetData(fieldKey, ListField, []);
+        // bcz: not sure why, but I need these to force the flexlayout to update when the collection size changes.
+        var s = this.props.ContainingDocumentView != undefined ? this.props.ContainingDocumentView!.ScalingToScreenSpace : 1;
+        var w = Document.GetData(KeyStore.Width, NumberField, Number(0)) / s;
+        var h = Document.GetData(KeyStore.Height, NumberField, Number(0)) / s;
+
+        var chooseLayout = () => {
+            if (!CollectionDockingView.UseGoldenLayout)
+                return <FlexLayout.Layout model={this.modelForFlexLayout} factory={this.flexLayoutFactory} />;
+        }
+
+        return (
+            <div className="border" style={{
+                borderStyle: "solid",
+                borderWidth: `${COLLECTION_BORDER_WIDTH}px`,
+            }}>
+                <div className="collectiondockingview-container" id="menuContainer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} ref={this._containerRef}
+                    style={{
+                        width: CollectionDockingView.UseGoldenLayout || s > 1 ? "100%" : w - 2 * COLLECTION_BORDER_WIDTH,
+                        height: CollectionDockingView.UseGoldenLayout || s > 1 ? "100%" : h - 2 * COLLECTION_BORDER_WIDTH
+                    }} >
+                    {chooseLayout()}
+                </div>
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss
new file mode 100644
index 000000000..e9d134e7b
--- /dev/null
+++ b/src/client/views/collections/CollectionFreeFormView.scss
@@ -0,0 +1,20 @@
+.collectionfreeformview-container {
+    position: relative;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+    .collectionfreeformview {
+        position: absolute;
+        top: 0;
+        left: 0;
+    }
+}
+
+.border {
+    border-style: solid;
+    box-sizing: border-box;
+    width: 100%;
+    height: 100%;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
new file mode 100644
index 000000000..9cf29d000
--- /dev/null
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -0,0 +1,205 @@
+import { observer } from "mobx-react";
+import React = require("react");
+import { action, observable, computed } from "mobx";
+import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
+import { DragManager } from "../../util/DragManager";
+import "./CollectionFreeFormView.scss";
+import { Utils } from "../../../Utils";
+import { CollectionViewBase, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase";
+import { SelectionManager } from "../../util/SelectionManager";
+import { Key, KeyStore } from "../../../fields/Key";
+import { Document } from "../../../fields/Document";
+import { ListField } from "../../../fields/ListField";
+import { NumberField } from "../../../fields/NumberField";
+import { Documents } from "../../documents/Documents";
+import { FieldWaiting } from "../../../fields/Field";
+
+@observer
+export class CollectionFreeFormView extends CollectionViewBase {
+    public static LayoutString() { return CollectionViewBase.LayoutString("CollectionFreeFormView"); }
+    private _containerRef = React.createRef<HTMLDivElement>();
+    private _canvasRef = React.createRef<HTMLDivElement>();
+    private _nodeContainerRef = React.createRef<HTMLDivElement>();
+    private _lastX: number = 0;
+    private _lastY: number = 0;
+
+    constructor(props: CollectionViewProps) {
+        super(props);
+    }
+
+    @action
+    drop = (e: Event, de: DragManager.DropEvent) => {
+        const doc = de.data["document"];
+        var me = this;
+        if (doc instanceof CollectionFreeFormDocumentView) {
+            if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this) {
+                doc.props.ContainingCollectionView.removeDocument(doc.props.Document);
+                this.addDocument(doc.props.Document);
+            }
+            const xOffset = de.data["xOffset"] as number || 0;
+            const yOffset = de.data["yOffset"] as number || 0;
+            const { scale, translateX, translateY } = Utils.GetScreenTransform(this._canvasRef.current!);
+            let sscale = this.props.ContainingDocumentView!.props.Document.GetData(KeyStore.Scale, NumberField, Number(1))
+            const screenX = de.x - xOffset;
+            const screenY = de.y - yOffset;
+            const docX = (screenX - translateX) / sscale / scale;
+            const docY = (screenY - translateY) / sscale / scale;
+            doc.x = docX;
+            doc.y = docY;
+            this.bringToFront(doc);
+        }
+        e.stopPropagation();
+    }
+
+    componentDidMount() {
+        if (this._containerRef.current) {
+            DragManager.MakeDropTarget(this._containerRef.current, {
+                handlers: {
+                    drop: this.drop
+                }
+            });
+        }
+    }
+
+    @action
+    onPointerDown = (e: React.PointerEvent): void => {
+        if ((e.button === 2 && this.active) ||
+            !e.defaultPrevented) {
+            document.removeEventListener("pointermove", this.onPointerMove);
+            document.addEventListener("pointermove", this.onPointerMove);
+            document.removeEventListener("pointerup", this.onPointerUp);
+            document.addEventListener("pointerup", this.onPointerUp);
+            this._lastX = e.pageX;
+            this._lastY = e.pageY;
+        }
+    }
+
+    @action
+    onPointerUp = (e: PointerEvent): void => {
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        e.stopPropagation();
+        SelectionManager.DeselectAll();
+    }
+
+    @action
+    onPointerMove = (e: PointerEvent): void => {
+        var me = this;
+        if (!e.cancelBubble && this.active) {
+            e.preventDefault();
+            e.stopPropagation();
+            let currScale: number = this.props.ContainingDocumentView!.ScalingToScreenSpace;
+            let x = this.props.DocumentForCollection.GetData(KeyStore.PanX, NumberField, Number(0));
+            let y = this.props.DocumentForCollection.GetData(KeyStore.PanY, NumberField, Number(0));
+            this.props.DocumentForCollection.SetData(KeyStore.PanX, x + (e.pageX - this._lastX) / currScale, NumberField);
+            this.props.DocumentForCollection.SetData(KeyStore.PanY, y + (e.pageY - this._lastY) / currScale, NumberField);
+        }
+        this._lastX = e.pageX;
+        this._lastY = e.pageY;
+    }
+
+    @action
+    onPointerWheel = (e: React.WheelEvent): void => {
+        e.stopPropagation();
+
+        let { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY } = this.props.ContainingDocumentView!.TransformToLocalPoint(e.pageX, e.pageY);
+
+        var deltaScale = (1 - (e.deltaY / 1000)) * Ss;
+
+        var newContainerX = LocalX * deltaScale + Panxx + Xx;
+        var newContainerY = LocalY * deltaScale + Panyy + Yy;
+
+        let dx = ContainerX - newContainerX;
+        let dy = ContainerY - newContainerY;
+
+        this.props.DocumentForCollection.Set(KeyStore.Scale, new NumberField(deltaScale));
+        this.props.DocumentForCollection.SetData(KeyStore.PanX, Panxx + dx, NumberField);
+        this.props.DocumentForCollection.SetData(KeyStore.PanY, Panyy + dy, NumberField);
+    }
+
+    @action
+    onDrop = (e: React.DragEvent): void => {
+        e.stopPropagation()
+        e.preventDefault()
+        let fReader = new FileReader()
+        let file = e.dataTransfer.items[0].getAsFile();
+        let that = this;
+        const panx: number = this.props.DocumentForCollection.GetData(KeyStore.PanX, NumberField, Number(0));
+        const pany: number = this.props.DocumentForCollection.GetData(KeyStore.PanY, NumberField, Number(0));
+        let x = e.pageX - panx
+        let y = e.pageY - pany
+
+        fReader.addEventListener("load", action("drop", (event) => {
+            if (fReader.result) {
+                let url = "" + fReader.result;
+                let doc = Documents.ImageDocument(url, {
+                    x: x, y: y
+                })
+                let docs = that.props.DocumentForCollection.GetT(KeyStore.Data, ListField);
+                if (docs != FieldWaiting) {
+                    if (!docs) {
+                        docs = new ListField<Document>();
+                        that.props.DocumentForCollection.Set(KeyStore.Data, docs)
+                    }
+                    docs.Data.push(doc);
+                }
+            }
+        }), false)
+
+        if (file) {
+            fReader.readAsDataURL(file)
+        }
+    }
+
+    onDragOver = (e: React.DragEvent): void => {
+    }
+
+    @action
+    bringToFront(doc: CollectionFreeFormDocumentView) {
+        const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
+
+        const value: Document[] = Document.GetList<Document>(fieldKey, []);
+        var topmost = value.reduce((topmost, d) => Math.max(d.GetNumber(KeyStore.ZIndex, 0), topmost), -1000);
+        value.map(d => {
+            var zind = d.GetNumber(KeyStore.ZIndex, 0);
+            if (zind != topmost - 1 - (topmost - zind) && d != doc.props.Document) {
+                d.SetData(KeyStore.ZIndex, topmost - 1 - (topmost - zind), NumberField);
+            }
+        })
+
+        if (doc.props.Document.GetNumber(KeyStore.ZIndex, 0) != 0) {
+            doc.props.Document.SetData(KeyStore.ZIndex, 0, NumberField);
+        }
+    }
+
+    render() {
+        const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
+        const value: Document[] = Document.GetList<Document>(fieldKey, []);
+        const panx: number = Document.GetNumber(KeyStore.PanX, 0);
+        const pany: number = Document.GetNumber(KeyStore.PanY, 0);
+        const currScale: number = Document.GetNumber(KeyStore.Scale, 1);
+
+        return (
+            <div className="border" style={{
+                borderWidth: `${COLLECTION_BORDER_WIDTH}px`,
+            }}>
+                <div className="collectionfreeformview-container"
+                    onPointerDown={this.onPointerDown}
+                    onWheel={this.onPointerWheel}
+                    onContextMenu={(e) => e.preventDefault()}
+                    onDrop={this.onDrop}
+                    onDragOver={this.onDragOver}
+                    ref={this._containerRef}>
+                    <div className="collectionfreeformview" style={{ transform: `translate(${panx}px, ${pany}px) scale(${currScale}, ${currScale})`, transformOrigin: `left, top` }} ref={this._canvasRef}>
+
+                        <div className="node-container" ref={this._nodeContainerRef}>
+                            {value.map(doc => {
+                                return (<CollectionFreeFormDocumentView key={doc.Id} ContainingCollectionView={this} Document={doc} DocumentView={undefined} />);
+                            })}
+                        </div>
+                    </div>
+                </div>
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
new file mode 100644
index 000000000..707b44db6
--- /dev/null
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -0,0 +1,108 @@
+.Resizer {
+    box-sizing: border-box;
+    background: #000;
+    opacity: 0.5;
+    z-index: 1;
+    background-clip: padding-box;
+    &.horizontal {
+        height: 11px;
+        margin: -5px 0;
+        border-top: 5px solid rgba(255, 255, 255, 0);
+        border-bottom: 5px solid rgba(255, 255, 255, 0);
+        cursor: row-resize;
+        width: 100%;
+        &:hover {
+            border-top: 5px solid rgba(0, 0, 0, 0.5);
+            border-bottom: 5px solid rgba(0, 0, 0, 0.5);
+        }
+    }
+    &.vertical {
+        width: 11px;
+        margin: 0 -5px;
+        border-left: 5px solid rgba(255, 255, 255, 0);
+        border-right: 5px solid rgba(255, 255, 255, 0);
+        cursor: col-resize;
+        &:hover {
+            border-left: 5px solid rgba(0, 0, 0, 0.5);
+            border-right: 5px solid rgba(0, 0, 0, 0.5);
+        }
+    }
+    &:hover {
+        -webkit-transition: all 2s ease;
+        transition: all 2s ease;
+    }
+}
+
+.vertical {
+    section {
+        width: 100vh;
+        height: 100vh;
+        display: -webkit-box;
+        display: -webkit-flex;
+        display: -ms-flexbox;
+        display: flex;
+        -webkit-box-orient: vertical;
+        -webkit-box-direction: normal;
+        -webkit-flex-direction: column;
+        -ms-flex-direction: column;
+        flex-direction: column;
+    }
+    header {
+        padding: 1rem;
+        background: #eee;
+    }
+    footer {
+        padding: 1rem;
+        background: #eee;
+    }
+}
+
+.horizontal {
+    section {
+        width: 100vh;
+        height: 100vh;
+        display: flex;
+        flex-direction: column;
+    }
+    header {
+        padding: 1rem;
+        background: #eee;
+    }
+    footer {
+        padding: 1rem;
+        background: #eee;
+    }
+}
+
+.parent {
+    width: 100%;
+    height: 100%;
+    -webkit-box-flex: 1;
+    -webkit-flex: 1;
+    -ms-flex: 1;
+    flex: 1;
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-box-direction: normal;
+    -webkit-flex-direction: column;
+    -ms-flex-direction: column;
+    flex-direction: column;
+}
+
+.header {
+    background: #aaa;
+    height: 3rem;
+    line-height: 3rem;
+}
+
+.wrapper {
+    background: #ffa;
+    margin: 5rem;
+    -webkit-box-flex: 1;
+    -webkit-flex: 1;
+    -ms-flex: 1;
+    flex: 1;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
new file mode 100644
index 000000000..2d5bd6c99
--- /dev/null
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -0,0 +1,144 @@
+import React = require("react")
+import ReactTable, { ReactTableDefaults, CellInfo, ComponentPropsGetterRC, ComponentPropsGetterR } from "react-table";
+import { observer } from "mobx-react";
+import { FieldView, FieldViewProps } from "../nodes/FieldView";
+import "react-table/react-table.css"
+import { observable, action, computed } from "mobx";
+import SplitPane from "react-split-pane"
+import "./CollectionSchemaView.scss"
+import { ScrollBox } from "../../util/ScrollBox";
+import { CollectionViewBase } from "./CollectionViewBase";
+import { DocumentView } from "../nodes/DocumentView";
+import { EditableView } from "../EditableView";
+import { CompileScript, ToField } from "../../util/Scripting";
+import { KeyStore as KS, Key } from "../../../fields/Key";
+import { Document } from "../../../fields/Document";
+import { Field } from "../../../fields/Field";
+
+@observer
+export class CollectionSchemaView extends CollectionViewBase {
+    public static LayoutString() { return CollectionViewBase.LayoutString("CollectionSchemaView"); }
+
+    @observable
+    selectedIndex = 0;
+
+    renderCell = (rowProps: CellInfo) => {
+        let props: FieldViewProps = {
+            doc: rowProps.value[0],
+            fieldKey: rowProps.value[1],
+            DocumentViewForField: undefined
+        }
+        let contents = (
+            <FieldView {...props} />
+        )
+        return (
+            <EditableView contents={contents} GetValue={() => {
+                let field = props.doc.Get(props.fieldKey);
+                if (field && field instanceof Field) {
+                    return field.ToScriptString();
+                }
+                return field || "";
+            }} SetValue={(value: string) => {
+                let script = CompileScript(value);
+                if (!script.compiled) {
+                    return false;
+                }
+                let field = script();
+                if (field instanceof Field) {
+                    props.doc.Set(props.fieldKey, field);
+                    return true;
+                } else {
+                    let dataField = ToField(field);
+                    if (dataField) {
+                        props.doc.Set(props.fieldKey, dataField);
+                        return true;
+                    }
+                }
+                return false;
+            }}></EditableView>
+        )
+    }
+
+    private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
+        const that = this;
+        if (!rowInfo) {
+            return {};
+        }
+        return {
+            onClick: action((e: React.MouseEvent, handleOriginal: Function) => {
+                that.selectedIndex = rowInfo.index;
+                const doc: Document = rowInfo.original;
+                console.log("Row clicked: ", doc.Title)
+
+                if (handleOriginal) {
+                    handleOriginal()
+                }
+            }),
+            style: {
+                background: rowInfo.index == this.selectedIndex ? "#00afec" : "white",
+                color: rowInfo.index == this.selectedIndex ? "white" : "black"
+            }
+        };
+    }
+
+    onPointerDown = (e: React.PointerEvent) => {
+        let target = e.target as HTMLElement;
+        if (target.tagName == "SPAN" && target.className.includes("Resizer")) {
+            e.stopPropagation();
+        }
+        if (e.button === 2 && this.active) {
+            e.stopPropagation();
+            e.preventDefault();
+        } else {
+            if (e.buttons === 1 && this.active) {
+                e.stopPropagation();
+            }
+        }
+    }
+
+    render() {
+        const { DocumentForCollection: Document, CollectionFieldKey: fieldKey } = this.props;
+        const children = Document.GetList<Document>(fieldKey, []);
+        const columns = Document.GetList(KS.ColumnsKey,
+            [KS.Title, KS.Data, KS.Author])
+        let content;
+        if (this.selectedIndex != -1) {
+            content = (
+                <DocumentView Document={children[this.selectedIndex]} DocumentView={undefined} ContainingCollectionView={this} />
+            )
+        } else {
+            content = <div />
+        }
+        return (
+            <div onPointerDown={this.onPointerDown} >
+                <SplitPane split={"vertical"} defaultSize="60%">
+                    <ScrollBox>
+                        <ReactTable
+                            data={children}
+                            pageSize={children.length}
+                            page={0}
+                            showPagination={false}
+                            style={{
+                                display: "inline-block"
+                            }}
+                            columns={columns.map(col => {
+                                return (
+                                    {
+                                        Header: col.Name,
+                                        accessor: (doc: Document) => [doc, col],
+                                        id: col.Id
+                                    })
+                            })}
+                            column={{
+                                ...ReactTableDefaults.column,
+                                Cell: this.renderCell
+                            }}
+                            getTrProps={this.getTrProps}
+                        />
+                    </ScrollBox>
+                    {content}
+                </SplitPane>
+            </div>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
new file mode 100644
index 000000000..09e8ec729
--- /dev/null
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -0,0 +1,57 @@
+import { action, computed } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../fields/Document";
+import { Opt } from "../../../fields/Field";
+import { Key, KeyStore } from "../../../fields/Key";
+import { ListField } from "../../../fields/ListField";
+import { SelectionManager } from "../../util/SelectionManager";
+import { ContextMenu } from "../ContextMenu";
+import React = require("react");
+import { DocumentView } from "../nodes/DocumentView";
+import { CollectionDockingView } from "./CollectionDockingView";
+import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
+
+
+export interface CollectionViewProps {
+    CollectionFieldKey: Key;
+    DocumentForCollection: Document;
+    ContainingDocumentView: Opt<DocumentView>;
+}
+
+export const COLLECTION_BORDER_WIDTH = 2;
+
+@observer
+export class CollectionViewBase extends React.Component<CollectionViewProps> {
+
+    public static LayoutString(collectionType: string) {
+        return `<${collectionType} DocumentForCollection={Document} CollectionFieldKey={DataKey} ContainingDocumentView={DocumentView}/>`;
+    }
+    @computed
+    public get active(): boolean {
+        var isSelected = (this.props.ContainingDocumentView instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.ContainingDocumentView));
+        var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == this);
+        var topMost = this.props.ContainingDocumentView != undefined && (
+            this.props.ContainingDocumentView.props.ContainingCollectionView == undefined ||
+            this.props.ContainingDocumentView.props.ContainingCollectionView instanceof CollectionDockingView);
+        return isSelected || childSelected || topMost;
+    }
+    @action
+    addDocument = (doc: Document): void => {
+        //TODO This won't create the field if it doesn't already exist
+        const value = this.props.DocumentForCollection.GetData(this.props.CollectionFieldKey, ListField, new Array<Document>())
+        value.push(doc);
+    }
+
+    @action
+    removeDocument = (doc: Document): void => {
+        //TODO This won't create the field if it doesn't already exist
+        const value = this.props.DocumentForCollection.GetData(this.props.CollectionFieldKey, ListField, new Array<Document>())
+        if (value.indexOf(doc) !== -1) {
+            value.splice(value.indexOf(doc), 1)
+
+            SelectionManager.DeselectAll()
+            ContextMenu.Instance.clearItems()
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
new file mode 100644
index 000000000..1d53cedc4
--- /dev/null
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -0,0 +1,223 @@
+import { action, computed } from "mobx";
+import { observer } from "mobx-react";
+import { Key, KeyStore } from "../../../fields/Key";
+import { NumberField } from "../../../fields/NumberField";
+import { DragManager } from "../../util/DragManager";
+import { SelectionManager } from "../../util/SelectionManager";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
+import { ContextMenu } from "../ContextMenu";
+import "./NodeView.scss";
+import React = require("react");
+import { DocumentView, DocumentViewProps } from "./DocumentView";
+
+
+@observer
+export class CollectionFreeFormDocumentView extends DocumentView {
+    private _contextMenuCanOpen = false;
+    private _downX: number = 0;
+    private _downY: number = 0;
+
+    constructor(props: DocumentViewProps) {
+        super(props);
+    }
+    get screenRect(): ClientRect | DOMRect {
+        if (this._mainCont.current) {
+            return this._mainCont.current.getBoundingClientRect();
+        }
+        return new DOMRect();
+    }
+
+    @computed
+    get x(): number {
+        return this.props.Document.GetData(KeyStore.X, NumberField, Number(0));
+    }
+
+    @computed
+    get y(): number {
+        return this.props.Document.GetData(KeyStore.Y, NumberField, Number(0));
+    }
+
+    set x(x: number) {
+        this.props.Document.SetData(KeyStore.X, x, NumberField)
+    }
+
+    set y(y: number) {
+        this.props.Document.SetData(KeyStore.Y, y, NumberField)
+    }
+
+    @computed
+    get transform(): string {
+        return `translate(${this.x}px, ${this.y}px)`;
+    }
+
+    @computed
+    get width(): number {
+        return this.props.Document.GetData(KeyStore.Width, NumberField, Number(0));
+    }
+
+    set width(w: number) {
+        this.props.Document.SetData(KeyStore.Width, w, NumberField)
+    }
+
+    @computed
+    get height(): number {
+        return this.props.Document.GetData(KeyStore.Height, NumberField, Number(0));
+    }
+
+    set height(h: number) {
+        this.props.Document.SetData(KeyStore.Height, h, NumberField)
+    }
+
+    @computed
+    get zIndex(): number {
+        return this.props.Document.GetData(KeyStore.ZIndex, NumberField, Number(0));
+    }
+
+    set zIndex(h: number) {
+        this.props.Document.SetData(KeyStore.ZIndex, h, NumberField)
+    }
+
+    @action
+    dragComplete = (e: DragManager.DragCompleteEvent) => {
+    }
+
+    @computed
+    get active(): boolean {
+        return SelectionManager.IsSelected(this) || this.props.ContainingCollectionView === undefined ||
+            this.props.ContainingCollectionView.active;
+    }
+
+    @computed
+    get topMost(): boolean {
+        return this.props.ContainingCollectionView == undefined || this.props.ContainingCollectionView instanceof CollectionDockingView;
+    }
+
+    onPointerDown = (e: React.PointerEvent): void => {
+        this._downX = e.clientX;
+        this._downY = e.clientY;
+        var me = this;
+        if (e.shiftKey && e.buttons === 1) {
+            CollectionDockingView.StartOtherDrag(this._mainCont.current!, this.props.Document);
+            e.stopPropagation();
+            return;
+        }
+        this._contextMenuCanOpen = e.button == 2;
+        if (this.active && !e.isDefaultPrevented()) {
+            e.stopPropagation();
+            if (e.buttons === 2) {
+                e.preventDefault();
+            }
+            document.removeEventListener("pointermove", this.onPointerMove)
+            document.addEventListener("pointermove", this.onPointerMove);
+            document.removeEventListener("pointerup", this.onPointerUp)
+            document.addEventListener("pointerup", this.onPointerUp);
+        }
+    }
+
+    onPointerMove = (e: PointerEvent): void => {
+        if (e.cancelBubble) {
+            this._contextMenuCanOpen = false;
+            return;
+        }
+        if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+            this._contextMenuCanOpen = false;
+            if (this._mainCont.current != null && !this.topMost) {
+                this._contextMenuCanOpen = false;
+                const rect = this.screenRect;
+                let dragData: { [id: string]: any } = {};
+                dragData["document"] = this;
+                dragData["xOffset"] = e.x - rect.left;
+                dragData["yOffset"] = e.y - rect.top;
+                DragManager.StartDrag(this._mainCont.current, dragData, {
+                    handlers: {
+                        dragComplete: this.dragComplete,
+                    },
+                    hideSource: true
+                })
+            }
+        }
+        e.stopPropagation();
+        e.preventDefault();
+    }
+
+    onPointerUp = (e: PointerEvent): void => {
+        document.removeEventListener("pointermove", this.onPointerMove)
+        document.removeEventListener("pointerup", this.onPointerUp)
+        e.stopPropagation();
+        if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
+            SelectionManager.SelectDoc(this, e.ctrlKey);
+        }
+    }
+
+    openRight = (e: React.MouseEvent): void => {
+        CollectionDockingView.AddRightSplit(this.props.Document);
+    }
+
+    deleteClicked = (e: React.MouseEvent): void => {
+        if (this.props.ContainingCollectionView instanceof CollectionFreeFormView) {
+            this.props.ContainingCollectionView.removeDocument(this.props.Document)
+        }
+    }
+    @action
+    fullScreenClicked = (e: React.MouseEvent): void => {
+        CollectionDockingView.OpenFullScreen(this.props.Document);
+        ContextMenu.Instance.clearItems();
+        ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
+        ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+    }
+    @action
+    closeFullScreenClicked = (e: React.MouseEvent): void => {
+        CollectionDockingView.CloseFullScreen();
+        ContextMenu.Instance.clearItems();
+        ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
+        ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+    }
+
+    @action
+    onContextMenu = (e: React.MouseEvent): void => {
+        if (!SelectionManager.IsSelected(this)) {
+            return;
+        }
+        e.preventDefault()
+
+        if (!this._contextMenuCanOpen) {
+            return;
+        }
+
+        if (this.topMost) {
+            ContextMenu.Instance.clearItems()
+            ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
+            ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+        }
+        else {
+            // DocumentViews should stop propogation of this event
+            e.stopPropagation();
+
+            ContextMenu.Instance.clearItems();
+            ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
+            ContextMenu.Instance.addItem({ description: "Open Right", event: this.openRight })
+            ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
+            ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+            SelectionManager.SelectDoc(this, e.ctrlKey);
+        }
+    }
+
+    render() {
+        var freestyling = this.props.ContainingCollectionView instanceof CollectionFreeFormView;
+        return (
+            <div className="node" ref={this._mainCont} style={{
+                transform: freestyling ? this.transform : "",
+                width: freestyling ? this.width : "100%",
+                height: freestyling ? this.height : "100%",
+                position: freestyling ? "absolute" : "relative",
+                zIndex: freestyling ? this.zIndex : 0,
+            }}
+                onContextMenu={this.onContextMenu}
+                onPointerDown={this.onPointerDown}>
+
+                <DocumentView {...this.props} DocumentView={this} />
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
new file mode 100644
index 000000000..730ce62f2
--- /dev/null
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -0,0 +1,153 @@
+import { action, computed } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../fields/Document";
+import { Opt, FieldWaiting } from "../../../fields/Field";
+import { Key, KeyStore } from "../../../fields/Key";
+import { ListField } from "../../../fields/ListField";
+import { NumberField } from "../../../fields/NumberField";
+import { TextField } from "../../../fields/TextField";
+import { Utils } from "../../../Utils";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
+import { CollectionSchemaView } from "../collections/CollectionSchemaView";
+import { CollectionViewBase, COLLECTION_BORDER_WIDTH } from "../collections/CollectionViewBase";
+import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { ImageBox } from "../nodes/ImageBox";
+import "./NodeView.scss";
+import React = require("react");
+const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
+
+export interface DocumentViewProps {
+    Document: Document;
+    DocumentView: Opt<DocumentView>  // needed only to set ContainingDocumentView on CollectionViewProps when invoked from JsxParser -- is there a better way?
+    ContainingCollectionView: Opt<CollectionViewBase>;
+}
+@observer
+export class DocumentView extends React.Component<DocumentViewProps> {
+
+    protected _mainCont = React.createRef<any>();
+    get MainContent() {
+        return this._mainCont;
+    }
+    @computed
+    get layout(): string {
+        return this.props.Document.GetData(KeyStore.Layout, TextField, String("<p>Error loading layout data</p>"));
+    }
+
+    @computed
+    get layoutKeys(): Key[] {
+        return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>());
+    }
+
+    @computed
+    get layoutFields(): Key[] {
+        return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>());
+    }
+
+    // 
+    // returns the cumulative scaling between the document and the screen
+    //
+    @computed
+    public get ScalingToScreenSpace(): number {
+        if (this.props.ContainingCollectionView != undefined &&
+            this.props.ContainingCollectionView.props.ContainingDocumentView != undefined) {
+            let ss = this.props.ContainingCollectionView.props.DocumentForCollection.GetData(KeyStore.Scale, NumberField, Number(1));
+            return this.props.ContainingCollectionView.props.ContainingDocumentView.ScalingToScreenSpace * ss;
+        }
+        return 1;
+    }
+
+    //
+    // Converts a coordinate in the screen space of the app into a local document coordinate.
+    //
+    public TransformToLocalPoint(screenX: number, screenY: number) {
+        // if this collection view is nested within another collection view, then 
+        // first transform the screen point into the parent collection's coordinate space.
+        let { LocalX: parentX, LocalY: parentY } = this.props.ContainingCollectionView != undefined &&
+            this.props.ContainingCollectionView.props.ContainingDocumentView != undefined ?
+            this.props.ContainingCollectionView.props.ContainingDocumentView.TransformToLocalPoint(screenX, screenY) :
+            { LocalX: screenX, LocalY: screenY };
+        let ContainerX: number = parentX - COLLECTION_BORDER_WIDTH;
+        let ContainerY: number = parentY - COLLECTION_BORDER_WIDTH;
+
+        var Xx = this.props.Document.GetData(KeyStore.X, NumberField, Number(0));
+        var Yy = this.props.Document.GetData(KeyStore.Y, NumberField, Number(0));
+        // CollectionDockingViews change the location of their children frames without using a Dash transformation.
+        // They also ignore any transformation that may have been applied to their content document.
+        // NOTE: this currently assumes CollectionDockingViews aren't nested.
+        if (this.props.ContainingCollectionView instanceof CollectionDockingView) {
+            var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this.MainContent.current!);
+            Xx = rx - COLLECTION_BORDER_WIDTH;
+            Yy = ry - COLLECTION_BORDER_WIDTH;
+        }
+
+        let Ss = this.props.Document.GetData(KeyStore.Scale, NumberField, Number(1));
+        let Panxx = this.props.Document.GetData(KeyStore.PanX, NumberField, Number(0));
+        let Panyy = this.props.Document.GetData(KeyStore.PanY, NumberField, Number(0));
+        let LocalX = (ContainerX - (Xx + Panxx)) / Ss;
+        let LocalY = (ContainerY - (Yy + Panyy)) / Ss;
+
+        return { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY };
+    }
+
+    //
+    // Converts a point in the coordinate space of a document to a screen space coordinate.
+    //
+    public TransformToScreenPoint(localX: number, localY: number, Ss: number = 1, Panxx: number = 0, Panyy: number = 0): { ScreenX: number, ScreenY: number } {
+
+        var Xx = this.props.Document.GetData(KeyStore.X, NumberField, Number(0));
+        var Yy = this.props.Document.GetData(KeyStore.Y, NumberField, Number(0));
+        // CollectionDockingViews change the location of their children frames without using a Dash transformation.
+        // They also ignore any transformation that may have been applied to their content document.
+        // NOTE: this currently assumes CollectionDockingViews aren't nested.
+        if (this.props.ContainingCollectionView instanceof CollectionDockingView) {
+            var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this.MainContent.current!);
+            Xx = rx - COLLECTION_BORDER_WIDTH;
+            Yy = ry - COLLECTION_BORDER_WIDTH;
+        }
+
+        let W = COLLECTION_BORDER_WIDTH;
+        let H = COLLECTION_BORDER_WIDTH;
+        let parentX = (localX - W) * Ss + (Xx + Panxx) + W;
+        let parentY = (localY - H) * Ss + (Yy + Panyy) + H;
+
+        // if this collection view is nested within another collection view, then 
+        // first transform the local point into the parent collection's coordinate space.
+        let containingDocView = this.props.ContainingCollectionView != undefined ? this.props.ContainingCollectionView.props.ContainingDocumentView : undefined;
+        if (containingDocView != undefined) {
+            let ss = containingDocView.props.Document.GetData(KeyStore.Scale, NumberField, Number(1));
+            let panxx = containingDocView.props.Document.GetData(KeyStore.PanX, NumberField, Number(0)) + COLLECTION_BORDER_WIDTH * ss;
+            let panyy = containingDocView.props.Document.GetData(KeyStore.PanY, NumberField, Number(0)) + COLLECTION_BORDER_WIDTH * ss;
+            let { ScreenX, ScreenY } = containingDocView.TransformToScreenPoint(parentX, parentY, ss, panxx, panyy);
+            parentX = ScreenX;
+            parentY = ScreenY;
+        }
+        return { ScreenX: parentX, ScreenY: parentY };
+    }
+
+
+    render() {
+        let bindings = { ...this.props } as any;
+        for (const key of this.layoutKeys) {
+            bindings[key.Name + "Key"] = key;  // this maps string values of the form <keyname>Key to an actual key Kestore.keyname  e.g,   "DataKey" => KeyStore.Data
+        }
+        for (const key of this.layoutFields) {
+            let field = this.props.Document.Get(key);
+            bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
+        }
+        if (bindings.DocumentView === undefined) {
+            bindings.DocumentView = this; // set the DocumentView to this if it hasn't already been set by a sub-class during its render method.
+        }
+        return (
+            <div className="node" ref={this._mainCont} style={{ width: "100%", height: "100%", }}>
+                <JsxParser
+                    components={{ FormattedTextBox: FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView }}
+                    bindings={bindings}
+                    jsx={this.layout}
+                    showWarnings={true}
+                    onError={(test: any) => { console.log(test) }}
+                />
+            </div>
+        )
+    }
+}
diff --git a/src/client/views/nodes/FieldTextBox.scss b/src/client/views/nodes/FieldTextBox.scss
new file mode 100644
index 000000000..b6ce2fabc
--- /dev/null
+++ b/src/client/views/nodes/FieldTextBox.scss
@@ -0,0 +1,14 @@
+.ProseMirror {
+    margin-top: -1em;
+    width: 100%;
+    height: 100%;
+}
+
+.ProseMirror:focus {
+    outline: none !important
+}
+
+.fieldTextBox-cont {
+    background: white;
+    padding: 1vw;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
new file mode 100644
index 000000000..12371eb2e
--- /dev/null
+++ b/src/client/views/nodes/FieldView.tsx
@@ -0,0 +1,56 @@
+import React = require("react")
+import { observer } from "mobx-react";
+import { computed } from "mobx";
+import { Field, Opt, FieldWaiting, FieldValue } from "../../../fields/Field";
+import { Document } from "../../../fields/Document";
+import { TextField } from "../../../fields/TextField";
+import { NumberField } from "../../../fields/NumberField";
+import { RichTextField } from "../../../fields/RichTextField";
+import { ImageField } from "../../../fields/ImageField";
+import { Key } from "../../../fields/Key";
+import { FormattedTextBox } from "./FormattedTextBox";
+import { ImageBox } from "./ImageBox";
+import { DocumentView } from "./DocumentView";
+
+//
+// these properties get assigned through the render() method of the DocumentView when it creates this node.
+// However, that only happens because the properties are "defined" in the markup for the field view.
+// See the LayoutString method on each field view :   ImageBox, FormattedTextBox, etc. 
+//
+export interface FieldViewProps {
+    fieldKey: Key;
+    doc: Document;
+    DocumentViewForField: Opt<DocumentView>
+}
+
+@observer
+export class FieldView extends React.Component<FieldViewProps> {
+    public static LayoutString(fieldType: string) { return `<${fieldType} doc={Document} DocumentViewForField={DocumentView} fieldKey={DataKey} />`; }
+    @computed
+    get field(): FieldValue<Field> {
+        const { doc, fieldKey } = this.props;
+        return doc.Get(fieldKey);
+    }
+    render() {
+        const field = this.field;
+        if (!field) {
+            return <p>{'<null>'}</p>
+        }
+        if (field instanceof TextField) {
+            return <p>{field.Data}</p>
+        }
+        else if (field instanceof RichTextField) {
+            return <FormattedTextBox {...this.props} />
+        }
+        else if (field instanceof ImageField) {
+            return <ImageBox {...this.props} />
+        }
+        else if (field instanceof NumberField) {
+            return <p>{field.Data}</p>
+        } else if (field != FieldWaiting) {
+            return <p>{field.GetValue}</p>
+        } else
+            return <p> {"Waiting for server..."} </p>
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
new file mode 100644
index 000000000..492367fce
--- /dev/null
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -0,0 +1,14 @@
+.ProseMirror {
+    margin-top: -1em;
+    width: 100%;
+    height: 100%;
+}
+
+.ProseMirror:focus {
+    outline: none !important
+}
+
+.formattedTextBox-cont {
+    background: white;
+    padding: 1vw;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
new file mode 100644
index 000000000..8bc4c902c
--- /dev/null
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -0,0 +1,127 @@
+import { action, IReactionDisposer, reaction } from "mobx";
+import { observer } from "mobx-react"
+import { baseKeymap } from "prosemirror-commands";
+import { history, redo, undo } from "prosemirror-history";
+import { keymap } from "prosemirror-keymap";
+import { schema } from "prosemirror-schema-basic";
+import { EditorState, Transaction } from "prosemirror-state";
+import { EditorView } from "prosemirror-view";
+import { Opt, FieldWaiting, FieldValue } from "../../../fields/Field";
+import { SelectionManager } from "../../util/SelectionManager";
+import "./FormattedTextBox.scss";
+import React = require("react")
+import { RichTextField } from "../../../fields/RichTextField";
+import { FieldViewProps, FieldView } from "./FieldView";
+import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
+
+
+// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
+//
+//  HTML Markup:  <FormattedTextBox Doc={Document's ID} FieldKey={Key's name + "Key"}
+// 
+//  In Code, the node's HTML is specified in the document's parameterized structure as:
+//        document.SetField(KeyStore.Layout,  "<FormattedTextBox doc={doc} fieldKey={<KEYNAME>Key} />");
+//  and the node's binding to the specified document KEYNAME as:
+//        document.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.<KEYNAME>]));
+//  The Jsx parser at run time will bind:
+//        'fieldKey' property to the Key stored in LayoutKeys
+//    and 'doc' property to the document that is being rendered
+//
+//  When rendered() by React, this extracts the TextController from the Document stored at the 
+//  specified Key and assigns it to an HTML input node.  When changes are made tot his node, 
+//  this will edit the document and assign the new value to that field.
+//]
+@observer
+export class FormattedTextBox extends React.Component<FieldViewProps> {
+
+    public static LayoutString() { return FieldView.LayoutString("FormattedTextBox"); }
+    private _ref: React.RefObject<HTMLDivElement>;
+    private _editorView: Opt<EditorView>;
+    private _reactionDisposer: Opt<IReactionDisposer>;
+
+    constructor(props: FieldViewProps) {
+        super(props);
+
+        this._ref = React.createRef();
+
+        this.onChange = this.onChange.bind(this);
+    }
+
+    dispatchTransaction = (tx: Transaction) => {
+        if (this._editorView) {
+            const state = this._editorView.state.apply(tx);
+            this._editorView.updateState(state);
+            const { doc, fieldKey } = this.props;
+            doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
+        }
+    }
+
+    componentDidMount() {
+        let state: EditorState;
+        const { doc, fieldKey } = this.props;
+        const config = {
+            schema,
+            plugins: [
+                history(),
+                keymap({ "Mod-z": undo, "Mod-y": redo }),
+                keymap(baseKeymap)
+            ]
+        };
+
+        let field = doc.GetT(fieldKey, RichTextField);
+        if (field && field != FieldWaiting) {  // bcz: don't think this works
+            state = EditorState.fromJSON(config, JSON.parse(field.Data));
+        } else {
+            state = EditorState.create(config);
+        }
+        if (this._ref.current) {
+            this._editorView = new EditorView(this._ref.current, {
+                state,
+                dispatchTransaction: this.dispatchTransaction
+            });
+        }
+
+        this._reactionDisposer = reaction(() => {
+            const field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
+            return field && field != FieldWaiting ? field.Data : undefined;
+        }, (field) => {
+            if (field && this._editorView) {
+                this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)));
+            }
+        })
+    }
+
+    componentWillUnmount() {
+        if (this._editorView) {
+            this._editorView.destroy();
+        }
+        if (this._reactionDisposer) {
+            this._reactionDisposer();
+        }
+    }
+
+    shouldComponentUpdate() {
+        return false;
+    }
+
+    @action
+    onChange(e: React.ChangeEvent<HTMLInputElement>) {
+        const { fieldKey, doc } = this.props;
+        doc.SetData(fieldKey, e.target.value, RichTextField);
+    }
+    onPointerDown = (e: React.PointerEvent): void => {
+        let me = this;
+        if (e.buttons === 1 && me.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(me.props.DocumentViewForField)) {
+            e.stopPropagation();
+        }
+    }
+    render() {
+        return (<div className="formattedTextBox-cont"
+            style={{
+                color: "initial",
+                whiteSpace: "initial"
+            }}
+            onPointerDown={this.onPointerDown}
+            ref={this._ref} />)
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
new file mode 100644
index 000000000..136fda1d0
--- /dev/null
+++ b/src/client/views/nodes/ImageBox.scss
@@ -0,0 +1,11 @@
+
+.imageBox-cont {
+    padding: 0vw;
+}
+
+.imageBox-button {
+    padding : 0vw;
+    border: none;
+    width : 100%;
+    height: 100%;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
new file mode 100644
index 000000000..ab20f140c
--- /dev/null
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -0,0 +1,92 @@
+
+import Lightbox from 'react-image-lightbox';
+import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
+import { SelectionManager } from "../../util/SelectionManager";
+import "./ImageBox.scss";
+import React = require("react")
+import { ImageField } from '../../../fields/ImageField';
+import { FieldViewProps, FieldView } from './FieldView';
+import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
+import { FieldWaiting } from '../../../fields/Field';
+import { observer } from "mobx-react"
+import { observable, action } from 'mobx';
+
+@observer
+export class ImageBox extends React.Component<FieldViewProps> {
+
+    public static LayoutString() { return FieldView.LayoutString("ImageBox"); }
+    private _ref: React.RefObject<HTMLDivElement>;
+    private _downX: number = 0;
+    private _downY: number = 0;
+    private _lastTap: number = 0;
+    @observable private _photoIndex: number = 0;
+    @observable private _isOpen: boolean = false;
+
+    constructor(props: FieldViewProps) {
+        super(props);
+
+        this._ref = React.createRef();
+        this.state = {
+            photoIndex: 0,
+            isOpen: false,
+        };
+    }
+
+    componentDidMount() {
+    }
+
+    componentWillUnmount() {
+    }
+
+    onPointerDown = (e: React.PointerEvent): void => {
+        if (Date.now() - this._lastTap < 300) {
+            if (e.buttons === 1 && this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField)) {
+                e.stopPropagation();
+                this._downX = e.clientX;
+                this._downY = e.clientY;
+                document.removeEventListener("pointerup", this.onPointerUp);
+                document.addEventListener("pointerup", this.onPointerUp);
+            }
+        } else {
+            this._lastTap = Date.now();
+        }
+    }
+    @action
+    onPointerUp = (e: PointerEvent): void => {
+        document.removeEventListener("pointerup", this.onPointerUp);
+        if (Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) {
+            this._isOpen = true;
+        }
+        e.stopPropagation();
+    }
+
+    lightbox = (path: string) => {
+        const images = [path, "http://www.cs.brown.edu/~bcz/face.gif"];
+        if (this._isOpen && this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField)) {
+            return (<Lightbox
+                mainSrc={images[this._photoIndex]}
+                nextSrc={images[(this._photoIndex + 1) % images.length]}
+                prevSrc={images[(this._photoIndex + images.length - 1) % images.length]}
+                onCloseRequest={() => this.setState({ isOpen: false })}
+                onMovePrevRequest={action(() =>
+                    this._photoIndex = (this._photoIndex + images.length - 1) % images.length
+                )}
+                onMoveNextRequest={action(() =>
+                    this._photoIndex = (this._photoIndex + 1) % images.length
+                )}
+            />)
+        }
+    }
+
+    render() {
+        let field = this.props.doc.Get(this.props.fieldKey);
+        let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+            field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif";
+
+        return (
+            <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} >
+                <img src={path} width="100%" alt="Image not found" />
+                {this.lightbox(path)}
+            </div>)
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/NodeView.scss b/src/client/views/nodes/NodeView.scss
new file mode 100644
index 000000000..dac1c0a8e
--- /dev/null
+++ b/src/client/views/nodes/NodeView.scss
@@ -0,0 +1,23 @@
+.node {
+    position: absolute;
+    background: #cdcdcd;
+    overflow: hidden;
+    &.minimized {
+        width: 30px;
+        height: 30px;
+    }
+    .top {
+        background: #232323;
+        height: 20px;
+        cursor: pointer;
+    }
+    .content {
+        padding: 20px 20px;
+        height: auto;
+        box-sizing: border-box;
+    }
+    .scroll-box {
+        overflow-y: scroll;
+        height: calc(100% - 20px);
+    }
+}
\ No newline at end of file
diff --git a/src/documents/Documents.ts b/src/documents/Documents.ts
deleted file mode 100644
index ce9a1529d..000000000
--- a/src/documents/Documents.ts
+++ /dev/null
@@ -1,161 +0,0 @@
-import { Document } from "../fields/Document";
-import { Server } from "../Server";
-import { KeyStore } from "../fields/Key";
-import { TextField } from "../fields/TextField";
-import { NumberField } from "../fields/NumberField";
-import { ListField } from "../fields/ListField";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import { CollectionSchemaView } from "../views/collections/CollectionSchemaView";
-import { ImageField } from "../fields/ImageField";
-import { ImageBox } from "../views/nodes/ImageBox";
-import { CollectionFreeFormView } from "../views/collections/CollectionFreeFormView";
-import { FIELD_ID } from "../fields/Field";
-
-interface DocumentOptions {
-    x?: number;
-    y?: number;
-    width?: number;
-    height?: number;
-    title?: string;
-}
-
-export namespace Documents {
-    function setupOptions(doc: Document, options: DocumentOptions): void {
-        if (options.x) {
-            doc.SetData(KeyStore.X, options.x, NumberField);
-        }
-        if (options.y) {
-            doc.SetData(KeyStore.Y, options.y, NumberField);
-        }
-        if (options.width) {
-            doc.SetData(KeyStore.Width, options.width, NumberField);
-        }
-        if (options.height) {
-            doc.SetData(KeyStore.Height, options.height, NumberField);
-        }
-        if (options.title) {
-            doc.SetData(KeyStore.Title, options.title, TextField);
-        }
-        doc.SetData(KeyStore.Scale, 1, NumberField);
-        doc.SetData(KeyStore.PanX, 0, NumberField);
-        doc.SetData(KeyStore.PanY, 0, NumberField);
-    }
-
-    let textProto: Document;
-    function GetTextPrototype(): Document {
-        if (!textProto) {
-            textProto = new Document();
-            textProto.Set(KeyStore.X, new NumberField(0));
-            textProto.Set(KeyStore.Y, new NumberField(0));
-            textProto.Set(KeyStore.Width, new NumberField(300));
-            textProto.Set(KeyStore.Height, new NumberField(150));
-            textProto.Set(KeyStore.Layout, new TextField(FormattedTextBox.LayoutString()));
-            textProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
-        }
-        return textProto;
-    }
-
-    export function TextDocument(options: DocumentOptions = {}): Document {
-        let doc = GetTextPrototype().MakeDelegate();
-        setupOptions(doc, options);
-        // doc.SetField(KeyStore.Data, new RichTextField());
-        return doc;
-    }
-
-    let schemaProto: Document;
-    function GetSchemaPrototype(): Document {
-        if (!schemaProto) {
-            schemaProto = new Document();
-            schemaProto.Set(KeyStore.X, new NumberField(0));
-            schemaProto.Set(KeyStore.Y, new NumberField(0));
-            schemaProto.Set(KeyStore.Width, new NumberField(300));
-            schemaProto.Set(KeyStore.Height, new NumberField(150));
-            schemaProto.Set(KeyStore.Layout, new TextField(CollectionSchemaView.LayoutString()));
-            schemaProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
-        }
-        return schemaProto;
-    }
-
-    export function SchemaDocument(documents: Array<Document>, options: DocumentOptions = {}): Document {
-        let doc = GetSchemaPrototype().MakeDelegate();
-        setupOptions(doc, options);
-        doc.Set(KeyStore.Data, new ListField(documents));
-        return doc;
-    }
-
-
-    let dockProto: Document;
-    function GetDockPrototype(): Document {
-        if (!dockProto) {
-            dockProto = new Document();
-            dockProto.Set(KeyStore.X, new NumberField(0));
-            dockProto.Set(KeyStore.Y, new NumberField(0));
-            dockProto.Set(KeyStore.Width, new NumberField(300));
-            dockProto.Set(KeyStore.Height, new NumberField(150));
-            dockProto.Set(KeyStore.Layout, new TextField(CollectionDockingView.LayoutString()));
-            dockProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
-        }
-        return dockProto;
-    }
-
-    export function DockDocument(documents: Array<Document>, options: DocumentOptions = {}): Document {
-        let doc = GetDockPrototype().MakeDelegate();
-        setupOptions(doc, options);
-        doc.Set(KeyStore.Data, new ListField(documents));
-        return doc;
-    }
-
-
-    let imageProtoId: FIELD_ID;
-    function GetImagePrototype(): Document {
-        if (imageProtoId === undefined) {
-            let imageProto = new Document();
-            imageProtoId = imageProto.Id;
-            imageProto.Set(KeyStore.Title, new TextField("IMAGE PROTO"));
-            imageProto.Set(KeyStore.X, new NumberField(0));
-            imageProto.Set(KeyStore.Y, new NumberField(0));
-            imageProto.Set(KeyStore.Width, new NumberField(300));
-            imageProto.Set(KeyStore.Height, new NumberField(300));
-            imageProto.Set(KeyStore.Layout, new TextField(ImageBox.LayoutString()));
-            // imageProto.SetField(KeyStore.Layout, new TextField('<div style={"background-image: " + {Data}} />'));
-            imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
-            Server.AddDocument(imageProto);
-            return imageProto;
-        }
-        return Server.GetField(imageProtoId) as Document;
-    }
-
-    export function ImageDocument(url: string, options: DocumentOptions = {}): Document {
-        let doc = GetImagePrototype().MakeDelegate();
-        setupOptions(doc, options);
-        doc.Set(KeyStore.Data, new ImageField(new URL(url)));
-        Server.AddDocument(doc);
-        var sdoc = Server.GetField(doc.Id) as Document;
-        return sdoc;
-    }
-
-    let collectionProto: Document;
-    function GetCollectionPrototype(): Document {
-        if (!collectionProto) {
-            collectionProto = new Document();
-            collectionProto.Set(KeyStore.X, new NumberField(0));
-            collectionProto.Set(KeyStore.Y, new NumberField(0));
-            collectionProto.Set(KeyStore.Scale, new NumberField(1));
-            collectionProto.Set(KeyStore.PanX, new NumberField(0));
-            collectionProto.Set(KeyStore.PanY, new NumberField(0));
-            collectionProto.Set(KeyStore.Width, new NumberField(300));
-            collectionProto.Set(KeyStore.Height, new NumberField(300));
-            collectionProto.Set(KeyStore.Layout, new TextField(CollectionFreeFormView.LayoutString()));
-            collectionProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
-        }
-        return collectionProto;
-    }
-
-    export function CollectionDocument(documents: Array<Document>, options: DocumentOptions = {}): Document {
-        let doc = GetCollectionPrototype().MakeDelegate();
-        setupOptions(doc, options);
-        doc.Set(KeyStore.Data, new ListField(documents));
-        return doc;
-    }
-}
\ No newline at end of file
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 546c89e04..6f9752a8e 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -5,7 +5,7 @@ import { ObservableMap, computed, action, observable } from "mobx";
 import { TextField } from "./TextField";
 import { ListField } from "./ListField";
 import { findDOMNode } from "react-dom";
-import { Server } from "../Server";
+import { Server } from "../client/Server";
 
 export class Document extends Field {
     public fields: ObservableMap<Key, Opt<Field>> = new ObservableMap();
diff --git a/src/server/index.js b/src/server/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/util/DragManager.ts b/src/util/DragManager.ts
deleted file mode 100644
index 63d6a88f8..000000000
--- a/src/util/DragManager.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import { Opt } from "../fields/Field";
-import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView";
-import { DocumentDecorations } from "../DocumentDecorations";
-import { SelectionManager } from "./SelectionManager";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import { Document } from "../fields/Document";
-
-export namespace DragManager {
-    export function Root() {
-        const root = document.getElementById("root");
-        if (!root) {
-            throw new Error("No root element found");
-        }
-        return root;
-    }
-
-    let dragDiv: HTMLDivElement;
-
-    export enum DragButtons {
-        Left = 1, Right = 2, Both = Left | Right
-    }
-
-    interface DragOptions {
-        handlers: DragHandlers;
-
-        hideSource: boolean | (() => boolean);
-    }
-
-    export interface DragDropDisposer {
-        (): void;
-    }
-
-    export class DragCompleteEvent {
-    }
-
-    export interface DragHandlers {
-        dragComplete: (e: DragCompleteEvent) => void;
-    }
-
-    export interface DropOptions {
-        handlers: DropHandlers;
-    }
-
-    export class DropEvent {
-        constructor(readonly x: number, readonly y: number, readonly data: { [id: string]: any }) { }
-    }
-
-    export interface DropHandlers {
-        drop: (e: Event, de: DropEvent) => void;
-    }
-
-    export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer {
-        if ("canDrop" in element.dataset) {
-            throw new Error("Element is already droppable, can't make it droppable again");
-        }
-        element.dataset["canDrop"] = "true";
-        const handler = (e: Event) => {
-            const ce = e as CustomEvent<DropEvent>;
-            options.handlers.drop(e, ce.detail);
-        };
-        element.addEventListener("dashOnDrop", handler);
-        return () => {
-            element.removeEventListener("dashOnDrop", handler);
-            delete element.dataset["canDrop"]
-        };
-    }
-
-
-    let _lastPointerX: number = 0;
-    let _lastPointerY: number = 0;
-    export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options: DragOptions) {
-        if (!dragDiv) {
-            dragDiv = document.createElement("div");
-            DragManager.Root().appendChild(dragDiv);
-        }
-        const w = ele.offsetWidth, h = ele.offsetHeight;
-        const rect = ele.getBoundingClientRect();
-        const scaleX = rect.width / w, scaleY = rect.height / h;
-        let x = rect.left, y = rect.top;
-        // const offsetX = e.x - rect.left, offsetY = e.y - rect.top;
-        let dragElement = ele.cloneNode(true) as HTMLElement;
-        dragElement.style.opacity = "0.7";
-        dragElement.style.position = "absolute";
-        dragElement.style.transformOrigin = "0 0";
-        dragElement.style.zIndex = "1000";
-        dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
-        dragDiv.appendChild(dragElement);
-        _lastPointerX = dragData["xOffset"] + rect.left;
-        _lastPointerY = dragData["yOffset"] + rect.top;
-
-        let hideSource = false;
-        if (typeof options.hideSource === "boolean") {
-            hideSource = options.hideSource;
-        } else {
-            hideSource = options.hideSource();
-        }
-        const wasHidden = ele.hidden;
-        if (hideSource) {
-            ele.hidden = true;
-        }
-
-        const moveHandler = (e: PointerEvent) => {
-            e.stopPropagation();
-            e.preventDefault();
-            x += e.clientX - _lastPointerX; _lastPointerX = e.clientX;
-            y += e.clientY - _lastPointerY; _lastPointerY = e.clientY;
-            dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
-        };
-        const upHandler = (e: PointerEvent) => {
-            document.removeEventListener("pointermove", moveHandler, true);
-            document.removeEventListener("pointerup", upHandler);
-            FinishDrag(dragElement, e, options, dragData);
-            if (hideSource && !wasHidden) {
-                ele.hidden = false;
-            }
-        };
-        document.addEventListener("pointermove", moveHandler, true);
-        document.addEventListener("pointerup", upHandler);
-    }
-
-    function FinishDrag(dragEle: HTMLElement, e: PointerEvent, options: DragOptions, dragData: { [index: string]: any }) {
-        dragDiv.removeChild(dragEle);
-        const target = document.elementFromPoint(e.x, e.y);
-        if (!target) {
-            return;
-        }
-        target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", {
-            bubbles: true,
-            detail: {
-                x: e.x,
-                y: e.y,
-                data: dragData
-            }
-        }));
-        options.handlers.dragComplete({});
-    }
-}
\ No newline at end of file
diff --git a/src/util/Scripting.ts b/src/util/Scripting.ts
deleted file mode 100644
index 0e83ab4f0..000000000
--- a/src/util/Scripting.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-// import * as ts from "typescript"
-let ts = (window as any).ts;
-import { Opt, Field } from "../fields/Field";
-import { Document as DocumentImport } from "../fields/Document";
-import { NumberField as NumberFieldImport, NumberField } from "../fields/NumberField";
-import { ImageField as ImageFieldImport } from "../fields/ImageField";
-import { TextField as TextFieldImport, TextField } from "../fields/TextField";
-import { RichTextField as RichTextFieldImport } from "../fields/RichTextField";
-import { KeyStore as KeyStoreImport } from "../fields/Key";
-
-export interface ExecutableScript {
-    (): any;
-
-    compiled: boolean;
-}
-
-function ExecScript(script: string, diagnostics: Opt<any[]>): ExecutableScript {
-    const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error));
-
-    let func: () => Opt<Field>;
-    if (compiled) {
-        func = function (): Opt<Field> {
-            let KeyStore = KeyStoreImport;
-            let Document = DocumentImport;
-            let NumberField = NumberFieldImport;
-            let TextField = TextFieldImport;
-            let ImageField = ImageFieldImport;
-            let RichTextField = RichTextFieldImport;
-            let window = undefined;
-            let document = undefined;
-            let retVal = eval(script);
-
-            return retVal;
-        };
-    } else {
-        func = () => undefined;
-    }
-
-    return Object.assign(func,
-        {
-            compiled
-        });
-}
-
-export function CompileScript(script: string): ExecutableScript {
-    let result = (window as any).ts.transpileModule(script, {})
-
-    return ExecScript(result.outputText, result.diagnostics);
-}
-
-export function ToField(data: any): Opt<Field> {
-    if (typeof data == "string") {
-        return new TextField(data);
-    } else if (typeof data == "number") {
-        return new NumberField(data);
-    }
-    return undefined;
-}
\ No newline at end of file
diff --git a/src/util/ScrollBox.tsx b/src/util/ScrollBox.tsx
deleted file mode 100644
index b6b088170..000000000
--- a/src/util/ScrollBox.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React = require("react")
-
-export class ScrollBox extends React.Component {
-    onWheel = (e: React.WheelEvent) => {
-        if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { // If the element has a scroll bar, then we don't want the containing collection to zoom
-            e.stopPropagation();
-        }
-    }
-
-    render() {
-        return (
-            <div style={{
-                overflow: "auto",
-                width: "100%",
-                height: "100%",
-            }} onWheel={this.onWheel}>
-                {this.props.children}
-            </div>
-        )
-    }
-}
\ No newline at end of file
diff --git a/src/util/SelectionManager.ts b/src/util/SelectionManager.ts
deleted file mode 100644
index 0759ae110..000000000
--- a/src/util/SelectionManager.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView";
-import { observable, action } from "mobx";
-
-export namespace SelectionManager {
-    class Manager {
-        @observable
-        SelectedDocuments: Array<CollectionFreeFormDocumentView> = [];
-
-        @action
-        SelectDoc(doc: CollectionFreeFormDocumentView, ctrlPressed: boolean): void {
-            // if doc is not in SelectedDocuments, add it
-            if (!ctrlPressed) {
-                manager.SelectedDocuments = [];
-            }
-
-            if (manager.SelectedDocuments.indexOf(doc) === -1) {
-                manager.SelectedDocuments.push(doc)
-            }
-        }
-    }
-
-    const manager = new Manager;
-
-    export function SelectDoc(doc: CollectionFreeFormDocumentView, ctrlPressed: boolean): void {
-        manager.SelectDoc(doc, ctrlPressed)
-    }
-
-    export function IsSelected(doc: CollectionFreeFormDocumentView): boolean {
-        return manager.SelectedDocuments.indexOf(doc) !== -1;
-    }
-
-    export function DeselectAll(): void {
-        manager.SelectedDocuments = []
-    }
-
-    export function SelectedDocuments(): Array<CollectionFreeFormDocumentView> {
-        return manager.SelectedDocuments;
-    }
-}
\ No newline at end of file
diff --git a/src/util/TypedEvent.ts b/src/util/TypedEvent.ts
deleted file mode 100644
index 0714a7f5c..000000000
--- a/src/util/TypedEvent.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-export interface Listener<T> {
-  (event: T): any;
-}
-
-export interface Disposable {
-  dispose(): void;
-}
-
-/** passes through events as they happen. You will not get events from before you start listening */
-export class TypedEvent<T> {
-  private listeners: Listener<T>[] = [];
-  private listenersOncer: Listener<T>[] = [];
-
-  on = (listener: Listener<T>): Disposable => {
-    this.listeners.push(listener);
-    return {
-      dispose: () => this.off(listener)
-    };
-  }
-
-  once = (listener: Listener<T>): void => {
-    this.listenersOncer.push(listener);
-  }
-
-  off = (listener: Listener<T>) => {
-    var callbackIndex = this.listeners.indexOf(listener);
-    if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
-  }
-
-  emit = (event: T) => {
-    /** Update any general listeners */
-    this.listeners.forEach((listener) => listener(event));
-
-    /** Clear the `once` queue */
-    this.listenersOncer.forEach((listener) => listener(event));
-    this.listenersOncer = [];
-  }
-
-  pipe = (te: TypedEvent<T>): Disposable => {
-    return this.on((e) => te.emit(e));
-  }
-}
\ No newline at end of file
diff --git a/src/views/ContextMenu.scss b/src/views/ContextMenu.scss
deleted file mode 100644
index 234f82eb9..000000000
--- a/src/views/ContextMenu.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-.contextMenu-cont {
-    position: absolute;
-    display: flex;
-    z-index: 1000;
-    box-shadow: #AAAAAA .2vw .2vw .4vw;
-}
-
-.contextMenu-item {
-    width: 10vw;
-    height: 4vh;
-    background: #DDDDDD;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    flex-direction: column;
-    -webkit-touch-callout: none;
-    -webkit-user-select: none;
-    -khtml-user-select: none;
-    -moz-user-select: none;
-    -ms-user-select: none;
-    user-select: none;
-    transition: all .1s;
-}
-
-.contextMenu-item:hover {
-    transition: all .1s;
-    background: #AAAAAA
-}
-
-.contextMenu-description {
-    font-size: 1.5vw;
-    text-align: left;
-    width: 8vw;
-}
\ No newline at end of file
diff --git a/src/views/ContextMenu.tsx b/src/views/ContextMenu.tsx
deleted file mode 100644
index 4f26a75d2..000000000
--- a/src/views/ContextMenu.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import React = require("react");
-import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
-import { observable } from "mobx";
-import { observer } from "mobx-react";
-import "./ContextMenu.scss"
-
-@observer
-export class ContextMenu extends React.Component {
-    static Instance: ContextMenu
-
-    @observable private _items: Array<ContextMenuProps> = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault() }];
-    @observable private _pageX: number = 0;
-    @observable private _pageY: number = 0;
-    @observable private _display: string = "none";
-
-    private ref: React.RefObject<HTMLDivElement>;
-
-    constructor(props: Readonly<{}>) {
-        super(props);
-
-        this.ref = React.createRef()
-
-        ContextMenu.Instance = this;
-    }
-
-    clearItems() {
-        this._items = []
-        this._display = "none"
-    }
-
-    addItem(item: ContextMenuProps) {
-        if (this._items.indexOf(item) === -1) {
-            this._items.push(item);
-        }
-    }
-
-    getItems() {
-        return this._items;
-    }
-
-    displayMenu(x: number, y: number) {
-        this._pageX = x
-        this._pageY = y
-
-        this._display = "flex"
-    }
-
-    intersects = (x: number, y: number): boolean => {
-        if (this.ref.current && this._display !== "none") {
-            if (x >= this._pageX && x <= this._pageX + this.ref.current.getBoundingClientRect().width) {
-                if (y >= this._pageY && y <= this._pageY + this.ref.current.getBoundingClientRect().height) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    render() {
-        return (
-            <div className="contextMenu-cont" style={{ left: this._pageX, top: this._pageY, display: this._display }} ref={this.ref}>
-                {this._items.map(prop => {
-                    return <ContextMenuItem {...prop} key={prop.description} />
-                })}
-            </div>
-        )
-    }
-}
\ No newline at end of file
diff --git a/src/views/ContextMenuItem.tsx b/src/views/ContextMenuItem.tsx
deleted file mode 100644
index 8f00f8b3d..000000000
--- a/src/views/ContextMenuItem.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React = require("react");
-import { ContextMenu } from "./ContextMenu";
-
-export interface ContextMenuProps {
-    description: string;
-    event: (e: React.MouseEvent<HTMLDivElement>) => void;
-}
-
-export class ContextMenuItem extends React.Component<ContextMenuProps> {
-    render() {
-        return (
-            <div className="contextMenu-item" onClick={this.props.event}>
-                <div className="contextMenu-description">{this.props.description}</div>
-            </div>
-        )
-    }
-}
\ No newline at end of file
diff --git a/src/views/EditableView.tsx b/src/views/EditableView.tsx
deleted file mode 100644
index 2e784d3f9..000000000
--- a/src/views/EditableView.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React = require('react')
-import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
-
-export interface EditableProps {
-    GetValue(): string;
-    SetValue(value: string): boolean;
-    contents: any;
-}
-
-@observer
-export class EditableView extends React.Component<EditableProps> {
-    @observable
-    editing: boolean = false;
-
-    @action
-    onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
-        if (e.key == "Enter" && !e.ctrlKey) {
-            this.props.SetValue(e.currentTarget.value);
-            this.editing = false;
-        } else if (e.key == "Escape") {
-            this.editing = false;
-        }
-    }
-
-    render() {
-        if (this.editing) {
-            return <input defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
-                style={{ width: "100%" }}></input>
-        } else {
-            return (
-                <div>
-                    {this.props.contents}
-                    <button onClick={action(() => this.editing = true)}>Edit</button>
-                </div>
-            )
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/views/collections/CollectionDockingView.scss b/src/views/collections/CollectionDockingView.scss
deleted file mode 100644
index db924b57f..000000000
--- a/src/views/collections/CollectionDockingView.scss
+++ /dev/null
@@ -1,336 +0,0 @@
-.collectiondockingview-container {
-    position: relative;
-    top: 0;
-    left: 0;
-    overflow: hidden;
-    .lm_controls>li {
-        opacity: 0.6;
-        transform: scale(1.2);
-    }
-    .lm_maximised .lm_controls .lm_maximise {
-        opacity: 1;
-        transform: scale(0.8);
-        background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==) !important;
-    }
-    .flexlayout__layout {
-        left: 0;
-        top: 0;
-        right: 0;
-        bottom: 0;
-        position: absolute;
-        overflow: hidden;
-    }
-    .flexlayout__splitter {
-        background-color: black;
-    }
-    .flexlayout__splitter:hover {
-        background-color: #333;
-    }
-    .flexlayout__splitter_drag {
-        border-radius: 5px;
-        background-color: #444;
-        z-index: 1000;
-    }
-    .flexlayout__outline_rect {
-        position: absolute;
-        cursor: move;
-        border: 2px solid #cfe8ff;
-        box-shadow: inset 0 0 60px rgba(0, 0, 0, .2);
-        border-radius: 5px;
-        z-index: 1000;
-        box-sizing: border-box;
-    }
-    .flexlayout__outline_rect_edge {
-        cursor: move;
-        border: 2px solid #b7d1b5;
-        box-shadow: inset 0 0 60px rgba(0, 0, 0, .2);
-        border-radius: 5px;
-        z-index: 1000;
-        box-sizing: border-box;
-    }
-    .flexlayout__edge_rect {
-        position: absolute;
-        z-index: 1000;
-        box-shadow: inset 0 0 5px rgba(0, 0, 0, .2);
-        background-color: lightgray;
-    }
-    .flexlayout__drag_rect {
-        position: absolute;
-        cursor: move;
-        border: 2px solid #aaaaaa;
-        box-shadow: inset 0 0 60px rgba(0, 0, 0, .3);
-        border-radius: 5px;
-        z-index: 1000;
-        box-sizing: border-box;
-        background-color: #eeeeee;
-        opacity: 0.9;
-        text-align: center;
-        display: flex;
-        justify-content: center;
-        flex-direction: column;
-        overflow: hidden;
-        padding: 10px;
-        word-wrap: break-word;
-    }
-    .flexlayout__tabset {
-        overflow: hidden;
-        background-color: #222;
-        box-sizing: border-box;
-    }
-    .flexlayout__tab {
-        overflow: auto;
-        position: absolute;
-        box-sizing: border-box;
-        background-color: #222;
-        color: black;
-    }
-    .flexlayout__tab_button {
-        cursor: pointer;
-        padding: 2px 8px 3px 8px;
-        margin: 2px;
-        /*box-shadow: inset 0px 0px 5px rgba(0, 0, 0, .15);*/
-        /*border-top-left-radius: 3px;*/
-        /*border-top-right-radius: 3px;*/
-        float: left;
-        vertical-align: top;
-        box-sizing: border-box;
-    }
-    .flexlayout__tab_button--selected {
-        color: #ddd;
-        background-color: #222;
-    }
-    .flexlayout__tab_button--unselected {
-        color: gray;
-    }
-    .flexlayout__tab_button_leading {
-        display: inline-block;
-    }
-    .flexlayout__tab_button_content {
-        display: inline-block;
-    }
-    .flexlayout__tab_button_textbox {
-        float: left;
-        border: none;
-        color: lightgreen;
-        background-color: #222;
-    }
-    .flexlayout__tab_button_textbox:focus {
-        outline: none;
-    }
-    .flexlayout__tab_button_trailing {
-        display: inline-block;
-        margin-left: 5px;
-        margin-top: 3px;
-        width: 8px;
-        height: 8px;
-    }
-    .flexlayout__tab_button:hover .flexlayout__tab_button_trailing,
-    .flexlayout__tab_button--selected .flexlayout__tab_button_trailing {
-        background: transparent url("../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center;
-    }
-    .flexlayout__tab_button_overflow {
-        float: left;
-        width: 20px;
-        height: 15px;
-        margin-top: 2px;
-        padding-left: 12px;
-        border: none;
-        font-size: 10px;
-        color: lightgray;
-        font-family: Arial, sans-serif;
-        background: transparent url("../../../node_modules/flexlayout-react/images/more.png") no-repeat left;
-    }
-    .flexlayout__tabset_header {
-        position: absolute;
-        left: 0;
-        right: 0;
-        color: #eee;
-        background-color: #212121;
-        padding: 3px 3px 3px 5px;
-        /*box-shadow: inset 0px 0px 3px 0px rgba(136, 136, 136, 0.54);*/
-        box-sizing: border-box;
-    }
-    .flexlayout__tab_header_inner {
-        position: absolute;
-        left: 0;
-        top: 0;
-        bottom: 0;
-        width: 10000px;
-    }
-    .flexlayout__tab_header_outer {
-        background-color: black;
-        position: absolute;
-        left: 0;
-        right: 0;
-        /*top: 0px;*/
-        /*height: 100px;*/
-        overflow: hidden;
-    }
-    .flexlayout__tabset-selected {
-        background-image: linear-gradient(#111, #444);
-    }
-    .flexlayout__tabset-maximized {
-        background-image: linear-gradient(#666, #333);
-    }
-    .flexlayout__tab_toolbar {
-        position: absolute;
-        display: flex;
-        flex-direction: row-reverse;
-        align-items: center;
-        top: 0;
-        bottom: 0;
-        right: 0;
-    }
-    .flexlayout__tab_toolbar_button-min {
-        width: 20px;
-        height: 20px;
-        border: none;
-        outline-width: 0;
-        background: transparent url("../../../node_modules/flexlayout-react/images/maximize.png") no-repeat center;
-    }
-    .flexlayout__tab_toolbar_button-max {
-        width: 20px;
-        height: 20px;
-        border: none;
-        outline-width: 0;
-        background: transparent url("../../../node_modules/flexlayout-react/images/restore.png") no-repeat center;
-    }
-    .flexlayout__popup_menu {}
-    .flexlayout__popup_menu_item {
-        padding: 2px 10px 2px 10px;
-        color: #ddd;
-    }
-    .flexlayout__popup_menu_item:hover {
-        background-color: #444444;
-    }
-    .flexlayout__popup_menu_container {
-        box-shadow: inset 0 0 5px rgba(0, 0, 0, .15);
-        border: 1px solid #555;
-        background: #222;
-        border-radius: 3px;
-        position: absolute;
-        z-index: 1000;
-    }
-    .flexlayout__border_top {
-        background-color: black;
-        border-bottom: 1px solid #ddd;
-        box-sizing: border-box;
-        overflow: hidden;
-    }
-    .flexlayout__border_bottom {
-        background-color: black;
-        border-top: 1px solid #333;
-        box-sizing: border-box;
-        overflow: hidden;
-    }
-    .flexlayout__border_left {
-        background-color: black;
-        border-right: 1px solid #333;
-        box-sizing: border-box;
-        overflow: hidden;
-    }
-    .flexlayout__border_right {
-        background-color: black;
-        border-left: 1px solid #333;
-        box-sizing: border-box;
-        overflow: hidden;
-    }
-    .flexlayout__border_inner_bottom {
-        display: flex;
-    }
-    .flexlayout__border_inner_left {
-        position: absolute;
-        white-space: nowrap;
-        right: 23px;
-        transform-origin: top right;
-        transform: rotate(-90deg);
-    }
-    .flexlayout__border_inner_right {
-        position: absolute;
-        white-space: nowrap;
-        left: 23px;
-        transform-origin: top left;
-        transform: rotate(90deg);
-    }
-    .flexlayout__border_button {
-        background-color: #222;
-        color: white;
-        display: inline-block;
-        white-space: nowrap;
-        cursor: pointer;
-        padding: 2px 8px 3px 8px;
-        margin: 2px;
-        vertical-align: top;
-        box-sizing: border-box;
-    }
-    .flexlayout__border_button--selected {
-        color: #ddd;
-        background-color: #222;
-    }
-    .flexlayout__border_button--unselected {
-        color: gray;
-    }
-    .flexlayout__border_button_leading {
-        float: left;
-        display: inline;
-    }
-    .flexlayout__border_button_content {
-        display: inline-block;
-    }
-    .flexlayout__border_button_textbox {
-        float: left;
-        border: none;
-        color: green;
-        background-color: #ddd;
-    }
-    .flexlayout__border_button_textbox:focus {
-        outline: none;
-    }
-    .flexlayout__border_button_trailing {
-        display: inline-block;
-        margin-left: 5px;
-        margin-top: 3px;
-        width: 8px;
-        height: 8px;
-    }
-    .flexlayout__border_button:hover .flexlayout__border_button_trailing,
-    .flexlayout__border_button--selected .flexlayout__border_button_trailing {
-        background: transparent url("../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center;
-    }
-    .flexlayout__border_toolbar_left {
-        position: absolute;
-        display: flex;
-        flex-direction: column-reverse;
-        align-items: center;
-        bottom: 0;
-        left: 0;
-        right: 0;
-    }
-    .flexlayout__border_toolbar_right {
-        position: absolute;
-        display: flex;
-        flex-direction: column-reverse;
-        align-items: center;
-        bottom: 0;
-        left: 0;
-        right: 0;
-    }
-    .flexlayout__border_toolbar_top {
-        position: absolute;
-        display: flex;
-        flex-direction: row-reverse;
-        align-items: center;
-        top: 0;
-        bottom: 0;
-        right: 0;
-    }
-    .flexlayout__border_toolbar_bottom {
-        position: absolute;
-        display: flex;
-        flex-direction: row-reverse;
-        align-items: center;
-        top: 0;
-        bottom: 0;
-        right: 0;
-    }
-}
\ No newline at end of file
diff --git a/src/views/collections/CollectionDockingView.tsx b/src/views/collections/CollectionDockingView.tsx
deleted file mode 100644
index adef03357..000000000
--- a/src/views/collections/CollectionDockingView.tsx
+++ /dev/null
@@ -1,282 +0,0 @@
-import { observer } from "mobx-react";
-import { KeyStore } from "../../fields/Key";
-import React = require("react");
-import FlexLayout from "flexlayout-react";
-import { action, observable, computed } from "mobx";
-import { Document } from "../../fields/Document";
-import { DocumentView } from "../nodes/DocumentView";
-import { ListField } from "../../fields/ListField";
-import { NumberField } from "../../fields/NumberField";
-import { SSL_OP_SINGLE_DH_USE } from "constants";
-import "./CollectionDockingView.scss"
-import 'golden-layout/src/css/goldenlayout-base.css';
-import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import * as GoldenLayout from "golden-layout";
-import * as ReactDOM from 'react-dom';
-import { DragManager } from "../../util/DragManager";
-import { CollectionViewBase, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase";
-
-@observer
-export class CollectionDockingView extends CollectionViewBase {
-
-    private static UseGoldenLayout = true;
-    public static LayoutString() { return CollectionViewBase.LayoutString("CollectionDockingView"); }
-    private _containerRef = React.createRef<HTMLDivElement>();
-    @computed
-    private get modelForFlexLayout() {
-        const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
-        const value: Document[] = Document.GetData(fieldKey, ListField, []);
-        var docs = value.map(doc => {
-            return { type: 'tabset', weight: 50, selected: 0, children: [{ type: "tab", name: doc.Title, component: doc.Id }] };
-        });
-        return FlexLayout.Model.fromJson({
-            global: {}, borders: [],
-            layout: {
-                "type": "row",
-                "weight": 100,
-                "children": docs
-            }
-        });
-    }
-    @computed
-    private get modelForGoldenLayout(): any {
-        const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
-        const value: Document[] = Document.GetData(fieldKey, ListField, []);
-        var docs = value.map(doc => {
-            return { type: 'component', componentName: 'documentViewComponent', componentState: { doc: doc } };
-        });
-        return new GoldenLayout({
-            settings: {
-                selectionEnabled: true
-            }, content: [{ type: 'row', content: docs }]
-        });
-    }
-    constructor(props: CollectionViewProps) {
-        super(props);
-    }
-
-    componentDidMount: () => void = () => {
-        if (this._containerRef.current && CollectionDockingView.UseGoldenLayout) {
-            this.goldenLayoutFactory();
-            window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
-        }
-    }
-    componentWillUnmount: () => void = () => {
-        window.removeEventListener('resize', this.onResize);
-    }
-    private nextId = (function () { var _next_id = 0; return function () { return _next_id++; } })();
-
-
-    @action
-    onResize = (event: any) => {
-        var cur = this.props.ContainingDocumentView!.MainContent.current;
-
-        // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
-        CollectionDockingView.myLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
-    }
-
-    @action
-    onPointerDown = (e: React.PointerEvent): void => {
-        if (e.button === 2 && this.active) {
-            e.stopPropagation();
-            e.preventDefault();
-        } else {
-            if (e.buttons === 1 && this.active) {
-                e.stopPropagation();
-            }
-        }
-    }
-
-    flexLayoutFactory = (node: any): any => {
-        var component = node.getComponent();
-        if (component === "button") {
-            return <button>{node.getName()}</button>;
-        }
-        const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
-        const value: Document[] = Document.GetData(fieldKey, ListField, []);
-        for (var i: number = 0; i < value.length; i++) {
-            if (value[i].Id === component) {
-                return (<DocumentView key={value[i].Id} ContainingCollectionView={this} Document={value[i]} DocumentView={undefined} />);
-            }
-        }
-        if (component === "text") {
-            return (<div className="panel">Panel {node.getName()}</div>);
-        }
-    }
-
-    public static myLayout: any = null;
-
-    private static _dragDiv: any = null;
-    private static _dragParent: HTMLElement | null = null;
-    private static _dragElement: HTMLDivElement;
-    private static _dragFakeElement: HTMLDivElement;
-    public static StartOtherDrag(dragElement: HTMLDivElement, dragDoc: Document) {
-        var newItemConfig = {
-            type: 'component',
-            componentName: 'documentViewComponent',
-            componentState: { doc: dragDoc }
-        };
-        this._dragElement = dragElement;
-        this._dragParent = dragElement.parentElement;
-        // bcz: we want to copy this document into the header, not move it there.
-        //   However, GoldenLayout is setup to move things, so we have to do some kludgy stuff:
-
-        //   - create a temporary invisible div and register that as a DragSource with GoldenLayout
-        this._dragDiv = document.createElement("div");
-        this._dragDiv.style.opacity = 0;
-        DragManager.Root().appendChild(this._dragDiv);
-        CollectionDockingView.myLayout.createDragSource(this._dragDiv, newItemConfig);
-
-        //   - add our document to that div so that GoldenLayout will get the move events its listening for
-        this._dragDiv.appendChild(this._dragElement);
-
-        //   - add a duplicate of our document to the original document's container 
-        //     (GoldenLayout will be removing our original one)
-        this._dragFakeElement = dragElement.cloneNode(true) as HTMLDivElement;
-        this._dragParent!.appendChild(this._dragFakeElement);
-
-        // all of this must be undone when the document has been dropped (see tabCreated)
-    }
-
-    _makeFullScreen: boolean = false;
-    _maximizedStack: any = null;
-    public static OpenFullScreen(document: Document) {
-        var newItemConfig = {
-            type: 'component',
-            componentName: 'documentViewComponent',
-            componentState: { doc: document }
-        };
-        CollectionDockingView.myLayout._makeFullScreen = true;
-        CollectionDockingView.myLayout.root.contentItems[0].addChild(newItemConfig);
-    }
-    public static CloseFullScreen() {
-        if (CollectionDockingView.myLayout._maximizedStack != null) {
-            CollectionDockingView.myLayout._maximizedStack.header.controlsContainer.find('.lm_close').click();
-            CollectionDockingView.myLayout._maximizedStack = null;
-        }
-    }
-
-    //
-    //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
-    //
-    public static AddRightSplit(document: Document) {
-        var newItemConfig = {
-            type: 'component',
-            componentName: 'documentViewComponent',
-            componentState: { doc: document }
-        }
-        let newItemStackConfig = {
-            type: 'stack',
-            content: [newItemConfig]
-        };
-        var newContentItem = new CollectionDockingView.myLayout._typeToItem[newItemStackConfig.type](CollectionDockingView.myLayout, newItemStackConfig, parent);
-
-        if (CollectionDockingView.myLayout.root.contentItems[0].isRow) {
-            var rowlayout = CollectionDockingView.myLayout.root.contentItems[0];
-            var lastRowItem = rowlayout.contentItems[rowlayout.contentItems.length - 1];
-
-            lastRowItem.config["width"] *= 0.5;
-            newContentItem.config["width"] = lastRowItem.config["width"];
-            rowlayout.addChild(newContentItem, rowlayout.contentItems.length, true);
-            rowlayout.callDownwards('setSize');
-        }
-        else {
-            var collayout = CollectionDockingView.myLayout.root.contentItems[0];
-            var newRow = collayout.layoutManager.createContentItem({ type: "row" }, CollectionDockingView.myLayout);
-            collayout.parent.replaceChild(collayout, newRow);
-
-            newRow.addChild(newContentItem, undefined, true);
-            newRow.addChild(collayout, 0, true);
-
-            collayout.config["width"] = 50;
-            newContentItem.config["width"] = 50;
-            collayout.parent.callDownwards('setSize');
-        }
-    }
-    goldenLayoutFactory() {
-        CollectionDockingView.myLayout = this.modelForGoldenLayout;
-
-        var layout = CollectionDockingView.myLayout;
-        CollectionDockingView.myLayout.on('tabCreated', function (tab: any) {
-            if (CollectionDockingView._dragDiv) {
-                CollectionDockingView._dragDiv.removeChild(CollectionDockingView._dragElement);
-                CollectionDockingView._dragParent!.removeChild(CollectionDockingView._dragFakeElement);
-                CollectionDockingView._dragParent!.appendChild(CollectionDockingView._dragElement);
-                DragManager.Root().removeChild(CollectionDockingView._dragDiv);
-                CollectionDockingView._dragDiv = null;
-            }
-            tab.setTitle(tab.contentItem.config.componentState.doc.Title);
-            tab.closeElement.off('click') //unbind the current click handler
-                .click(function () {
-                    tab.contentItem.remove();
-                });
-        });
-
-        CollectionDockingView.myLayout.on('stackCreated', function (stack: any) {
-            if (CollectionDockingView.myLayout._makeFullScreen) {
-                CollectionDockingView.myLayout._maximizedStack = stack;
-                CollectionDockingView.myLayout._maxstack = stack.header.controlsContainer.find('.lm_maximise');
-            }
-            //stack.header.controlsContainer.find('.lm_popout').hide();
-            stack.header.controlsContainer.find('.lm_close') //get the close icon
-                .off('click') //unbind the current click handler
-                .click(function () {
-                    //if (confirm('really close this?')) {
-                    stack.remove();
-                    //}
-                });
-        });
-
-        var me = this;
-        CollectionDockingView.myLayout.registerComponent('documentViewComponent', function (container: any, state: any) {
-            // bcz: this is crufty
-            // calling html() causes a div tag to be added in the DOM with id 'containingDiv'. 
-            // Apparently, we need to wait to allow a live html div element to actually be instantiated.
-            // After a timeout, we lookup the live html div element and add our React DocumentView to it.
-            var containingDiv = "component_" + me.nextId();
-            container.getElement().html("<div id='" + containingDiv + "'></div>");
-            setTimeout(function () {
-                ReactDOM.render((
-                    <DocumentView key={state.doc.Id} Document={state.doc} ContainingCollectionView={me} DocumentView={undefined} />
-                ),
-                    document.getElementById(containingDiv)
-                );
-                if (CollectionDockingView.myLayout._maxstack != null) {
-                    CollectionDockingView.myLayout._maxstack.click();
-                }
-            }, 0);
-        });
-        CollectionDockingView.myLayout.container = this._containerRef.current;
-        CollectionDockingView.myLayout.init();
-    }
-
-
-    render() {
-        const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
-        const value: Document[] = Document.GetData(fieldKey, ListField, []);
-        // bcz: not sure why, but I need these to force the flexlayout to update when the collection size changes.
-        var s = this.props.ContainingDocumentView != undefined ? this.props.ContainingDocumentView!.ScalingToScreenSpace : 1;
-        var w = Document.GetData(KeyStore.Width, NumberField, Number(0)) / s;
-        var h = Document.GetData(KeyStore.Height, NumberField, Number(0)) / s;
-
-        var chooseLayout = () => {
-            if (!CollectionDockingView.UseGoldenLayout)
-                return <FlexLayout.Layout model={this.modelForFlexLayout} factory={this.flexLayoutFactory} />;
-        }
-
-        return (
-            <div className="border" style={{
-                borderStyle: "solid",
-                borderWidth: `${COLLECTION_BORDER_WIDTH}px`,
-            }}>
-                <div className="collectiondockingview-container" id="menuContainer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} ref={this._containerRef}
-                    style={{
-                        width: CollectionDockingView.UseGoldenLayout || s > 1 ? "100%" : w - 2 * COLLECTION_BORDER_WIDTH,
-                        height: CollectionDockingView.UseGoldenLayout || s > 1 ? "100%" : h - 2 * COLLECTION_BORDER_WIDTH
-                    }} >
-                    {chooseLayout()}
-                </div>
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/views/collections/CollectionFreeFormView.scss b/src/views/collections/CollectionFreeFormView.scss
deleted file mode 100644
index e9d134e7b..000000000
--- a/src/views/collections/CollectionFreeFormView.scss
+++ /dev/null
@@ -1,20 +0,0 @@
-.collectionfreeformview-container {
-    position: relative;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    overflow: hidden;
-    .collectionfreeformview {
-        position: absolute;
-        top: 0;
-        left: 0;
-    }
-}
-
-.border {
-    border-style: solid;
-    box-sizing: border-box;
-    width: 100%;
-    height: 100%;
-}
\ No newline at end of file
diff --git a/src/views/collections/CollectionFreeFormView.tsx b/src/views/collections/CollectionFreeFormView.tsx
deleted file mode 100644
index 612f9acbe..000000000
--- a/src/views/collections/CollectionFreeFormView.tsx
+++ /dev/null
@@ -1,206 +0,0 @@
-import { observer } from "mobx-react";
-import { Key, KeyStore } from "../../fields/Key";
-import React = require("react");
-import { action, observable, computed } from "mobx";
-import { Document } from "../../fields/Document";
-import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { ListField } from "../../fields/ListField";
-import { NumberField } from "../../fields/NumberField";
-import { SSL_OP_SINGLE_DH_USE } from "constants";
-import { Documents } from "../../documents/Documents";
-import { DragManager } from "../../util/DragManager";
-import "./CollectionFreeFormView.scss";
-import { Utils } from "../../Utils";
-import { CollectionViewBase, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase";
-import { SelectionManager } from "../../util/SelectionManager";
-import { FieldWaiting } from "../../fields/Field";
-
-@observer
-export class CollectionFreeFormView extends CollectionViewBase {
-    public static LayoutString() { return CollectionViewBase.LayoutString("CollectionFreeFormView"); }
-    private _containerRef = React.createRef<HTMLDivElement>();
-    private _canvasRef = React.createRef<HTMLDivElement>();
-    private _nodeContainerRef = React.createRef<HTMLDivElement>();
-    private _lastX: number = 0;
-    private _lastY: number = 0;
-
-    constructor(props: CollectionViewProps) {
-        super(props);
-    }
-
-    @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        const doc = de.data["document"];
-        var me = this;
-        if (doc instanceof CollectionFreeFormDocumentView) {
-            if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this) {
-                doc.props.ContainingCollectionView.removeDocument(doc.props.Document);
-                this.addDocument(doc.props.Document);
-            }
-            const xOffset = de.data["xOffset"] as number || 0;
-            const yOffset = de.data["yOffset"] as number || 0;
-            const { scale, translateX, translateY } = Utils.GetScreenTransform(this._canvasRef.current!);
-            let sscale = this.props.ContainingDocumentView!.props.Document.GetData(KeyStore.Scale, NumberField, Number(1))
-            const screenX = de.x - xOffset;
-            const screenY = de.y - yOffset;
-            const docX = (screenX - translateX) / sscale / scale;
-            const docY = (screenY - translateY) / sscale / scale;
-            doc.x = docX;
-            doc.y = docY;
-            this.bringToFront(doc);
-        }
-        e.stopPropagation();
-    }
-
-    componentDidMount() {
-        if (this._containerRef.current) {
-            DragManager.MakeDropTarget(this._containerRef.current, {
-                handlers: {
-                    drop: this.drop
-                }
-            });
-        }
-    }
-
-    @action
-    onPointerDown = (e: React.PointerEvent): void => {
-        if ((e.button === 2 && this.active) ||
-            !e.defaultPrevented) {
-            document.removeEventListener("pointermove", this.onPointerMove);
-            document.addEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            document.addEventListener("pointerup", this.onPointerUp);
-            this._lastX = e.pageX;
-            this._lastY = e.pageY;
-        }
-    }
-
-    @action
-    onPointerUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        e.stopPropagation();
-        SelectionManager.DeselectAll();
-    }
-
-    @action
-    onPointerMove = (e: PointerEvent): void => {
-        var me = this;
-        if (!e.cancelBubble && this.active) {
-            e.preventDefault();
-            e.stopPropagation();
-            let currScale: number = this.props.ContainingDocumentView!.ScalingToScreenSpace;
-            let x = this.props.DocumentForCollection.GetData(KeyStore.PanX, NumberField, Number(0));
-            let y = this.props.DocumentForCollection.GetData(KeyStore.PanY, NumberField, Number(0));
-            this.props.DocumentForCollection.SetData(KeyStore.PanX, x + (e.pageX - this._lastX) / currScale, NumberField);
-            this.props.DocumentForCollection.SetData(KeyStore.PanY, y + (e.pageY - this._lastY) / currScale, NumberField);
-        }
-        this._lastX = e.pageX;
-        this._lastY = e.pageY;
-    }
-
-    @action
-    onPointerWheel = (e: React.WheelEvent): void => {
-        e.stopPropagation();
-
-        let { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY } = this.props.ContainingDocumentView!.TransformToLocalPoint(e.pageX, e.pageY);
-
-        var deltaScale = (1 - (e.deltaY / 1000)) * Ss;
-
-        var newContainerX = LocalX * deltaScale + Panxx + Xx;
-        var newContainerY = LocalY * deltaScale + Panyy + Yy;
-
-        let dx = ContainerX - newContainerX;
-        let dy = ContainerY - newContainerY;
-
-        this.props.DocumentForCollection.Set(KeyStore.Scale, new NumberField(deltaScale));
-        this.props.DocumentForCollection.SetData(KeyStore.PanX, Panxx + dx, NumberField);
-        this.props.DocumentForCollection.SetData(KeyStore.PanY, Panyy + dy, NumberField);
-    }
-
-    @action
-    onDrop = (e: React.DragEvent): void => {
-        e.stopPropagation()
-        e.preventDefault()
-        let fReader = new FileReader()
-        let file = e.dataTransfer.items[0].getAsFile();
-        let that = this;
-        const panx: number = this.props.DocumentForCollection.GetData(KeyStore.PanX, NumberField, Number(0));
-        const pany: number = this.props.DocumentForCollection.GetData(KeyStore.PanY, NumberField, Number(0));
-        let x = e.pageX - panx
-        let y = e.pageY - pany
-
-        fReader.addEventListener("load", action("drop", (event) => {
-            if (fReader.result) {
-                let url = "" + fReader.result;
-                let doc = Documents.ImageDocument(url, {
-                    x: x, y: y
-                })
-                let docs = that.props.DocumentForCollection.GetT(KeyStore.Data, ListField);
-                if (docs != FieldWaiting) {
-                    if (!docs) {
-                        docs = new ListField<Document>();
-                        that.props.DocumentForCollection.Set(KeyStore.Data, docs)
-                    }
-                    docs.Data.push(doc);
-                }
-            }
-        }), false)
-
-        if (file) {
-            fReader.readAsDataURL(file)
-        }
-    }
-
-    onDragOver = (e: React.DragEvent): void => {
-    }
-
-    @action
-    bringToFront(doc: CollectionFreeFormDocumentView) {
-        const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
-
-        const value: Document[] = Document.GetList<Document>(fieldKey, []);
-        var topmost = value.reduce((topmost, d) => Math.max(d.GetNumber(KeyStore.ZIndex, 0), topmost), -1000);
-        value.map(d => {
-            var zind = d.GetNumber(KeyStore.ZIndex, 0);
-            if (zind != topmost - 1 - (topmost - zind) && d != doc.props.Document) {
-                d.SetData(KeyStore.ZIndex, topmost - 1 - (topmost - zind), NumberField);
-            }
-        })
-
-        if (doc.props.Document.GetNumber(KeyStore.ZIndex, 0) != 0) {
-            doc.props.Document.SetData(KeyStore.ZIndex, 0, NumberField);
-        }
-    }
-
-    render() {
-        const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
-        const value: Document[] = Document.GetList<Document>(fieldKey, []);
-        const panx: number = Document.GetNumber(KeyStore.PanX, 0);
-        const pany: number = Document.GetNumber(KeyStore.PanY, 0);
-        const currScale: number = Document.GetNumber(KeyStore.Scale, 1);
-
-        return (
-            <div className="border" style={{
-                borderWidth: `${COLLECTION_BORDER_WIDTH}px`,
-            }}>
-                <div className="collectionfreeformview-container"
-                    onPointerDown={this.onPointerDown}
-                    onWheel={this.onPointerWheel}
-                    onContextMenu={(e) => e.preventDefault()}
-                    onDrop={this.onDrop}
-                    onDragOver={this.onDragOver}
-                    ref={this._containerRef}>
-                    <div className="collectionfreeformview" style={{ transform: `translate(${panx}px, ${pany}px) scale(${currScale}, ${currScale})`, transformOrigin: `left, top` }} ref={this._canvasRef}>
-
-                        <div className="node-container" ref={this._nodeContainerRef}>
-                            {value.map(doc => {
-                                return (<CollectionFreeFormDocumentView key={doc.Id} ContainingCollectionView={this} Document={doc} DocumentView={undefined} />);
-                            })}
-                        </div>
-                    </div>
-                </div>
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/views/collections/CollectionSchemaView.scss b/src/views/collections/CollectionSchemaView.scss
deleted file mode 100644
index 707b44db6..000000000
--- a/src/views/collections/CollectionSchemaView.scss
+++ /dev/null
@@ -1,108 +0,0 @@
-.Resizer {
-    box-sizing: border-box;
-    background: #000;
-    opacity: 0.5;
-    z-index: 1;
-    background-clip: padding-box;
-    &.horizontal {
-        height: 11px;
-        margin: -5px 0;
-        border-top: 5px solid rgba(255, 255, 255, 0);
-        border-bottom: 5px solid rgba(255, 255, 255, 0);
-        cursor: row-resize;
-        width: 100%;
-        &:hover {
-            border-top: 5px solid rgba(0, 0, 0, 0.5);
-            border-bottom: 5px solid rgba(0, 0, 0, 0.5);
-        }
-    }
-    &.vertical {
-        width: 11px;
-        margin: 0 -5px;
-        border-left: 5px solid rgba(255, 255, 255, 0);
-        border-right: 5px solid rgba(255, 255, 255, 0);
-        cursor: col-resize;
-        &:hover {
-            border-left: 5px solid rgba(0, 0, 0, 0.5);
-            border-right: 5px solid rgba(0, 0, 0, 0.5);
-        }
-    }
-    &:hover {
-        -webkit-transition: all 2s ease;
-        transition: all 2s ease;
-    }
-}
-
-.vertical {
-    section {
-        width: 100vh;
-        height: 100vh;
-        display: -webkit-box;
-        display: -webkit-flex;
-        display: -ms-flexbox;
-        display: flex;
-        -webkit-box-orient: vertical;
-        -webkit-box-direction: normal;
-        -webkit-flex-direction: column;
-        -ms-flex-direction: column;
-        flex-direction: column;
-    }
-    header {
-        padding: 1rem;
-        background: #eee;
-    }
-    footer {
-        padding: 1rem;
-        background: #eee;
-    }
-}
-
-.horizontal {
-    section {
-        width: 100vh;
-        height: 100vh;
-        display: flex;
-        flex-direction: column;
-    }
-    header {
-        padding: 1rem;
-        background: #eee;
-    }
-    footer {
-        padding: 1rem;
-        background: #eee;
-    }
-}
-
-.parent {
-    width: 100%;
-    height: 100%;
-    -webkit-box-flex: 1;
-    -webkit-flex: 1;
-    -ms-flex: 1;
-    flex: 1;
-    display: -webkit-box;
-    display: -webkit-flex;
-    display: -ms-flexbox;
-    display: flex;
-    -webkit-box-orient: vertical;
-    -webkit-box-direction: normal;
-    -webkit-flex-direction: column;
-    -ms-flex-direction: column;
-    flex-direction: column;
-}
-
-.header {
-    background: #aaa;
-    height: 3rem;
-    line-height: 3rem;
-}
-
-.wrapper {
-    background: #ffa;
-    margin: 5rem;
-    -webkit-box-flex: 1;
-    -webkit-flex: 1;
-    -ms-flex: 1;
-    flex: 1;
-}
\ No newline at end of file
diff --git a/src/views/collections/CollectionSchemaView.tsx b/src/views/collections/CollectionSchemaView.tsx
deleted file mode 100644
index 6f591a1bc..000000000
--- a/src/views/collections/CollectionSchemaView.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import React = require("react")
-import ReactTable, { ReactTableDefaults, CellInfo, ComponentPropsGetterRC, ComponentPropsGetterR } from "react-table";
-import { observer } from "mobx-react";
-import { KeyStore as KS, Key } from "../../fields/Key";
-import { Document } from "../../fields/Document";
-import { FieldView, FieldViewProps } from "../nodes/FieldView";
-import "react-table/react-table.css"
-import { observable, action, computed } from "mobx";
-import SplitPane from "react-split-pane"
-import "./CollectionSchemaView.scss"
-import { ScrollBox } from "../../util/ScrollBox";
-import { CollectionViewBase } from "./CollectionViewBase";
-import { DocumentView } from "../nodes/DocumentView";
-import { EditableView } from "../EditableView";
-import { CompileScript, ToField } from "../../util/Scripting";
-import { Field } from "../../fields/Field";
-
-@observer
-export class CollectionSchemaView extends CollectionViewBase {
-    public static LayoutString() { return CollectionViewBase.LayoutString("CollectionSchemaView"); }
-
-    @observable
-    selectedIndex = 0;
-
-    renderCell = (rowProps: CellInfo) => {
-        let props: FieldViewProps = {
-            doc: rowProps.value[0],
-            fieldKey: rowProps.value[1],
-            DocumentViewForField: undefined
-        }
-        let contents = (
-            <FieldView {...props} />
-        )
-        return (
-            <EditableView contents={contents} GetValue={() => {
-                let field = props.doc.Get(props.fieldKey);
-                if (field && field instanceof Field) {
-                    return field.ToScriptString();
-                }
-                return field || "";
-            }} SetValue={(value: string) => {
-                let script = CompileScript(value);
-                if (!script.compiled) {
-                    return false;
-                }
-                let field = script();
-                if (field instanceof Field) {
-                    props.doc.Set(props.fieldKey, field);
-                    return true;
-                } else {
-                    let dataField = ToField(field);
-                    if (dataField) {
-                        props.doc.Set(props.fieldKey, dataField);
-                        return true;
-                    }
-                }
-                return false;
-            }}></EditableView>
-        )
-    }
-
-    private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
-        const that = this;
-        if (!rowInfo) {
-            return {};
-        }
-        return {
-            onClick: action((e: React.MouseEvent, handleOriginal: Function) => {
-                that.selectedIndex = rowInfo.index;
-                const doc: Document = rowInfo.original;
-                console.log("Row clicked: ", doc.Title)
-
-                if (handleOriginal) {
-                    handleOriginal()
-                }
-            }),
-            style: {
-                background: rowInfo.index == this.selectedIndex ? "#00afec" : "white",
-                color: rowInfo.index == this.selectedIndex ? "white" : "black"
-            }
-        };
-    }
-
-    onPointerDown = (e: React.PointerEvent) => {
-        let target = e.target as HTMLElement;
-        if (target.tagName == "SPAN" && target.className.includes("Resizer")) {
-            e.stopPropagation();
-        }
-        if (e.button === 2 && this.active) {
-            e.stopPropagation();
-            e.preventDefault();
-        } else {
-            if (e.buttons === 1 && this.active) {
-                e.stopPropagation();
-            }
-        }
-    }
-
-    render() {
-        const { DocumentForCollection: Document, CollectionFieldKey: fieldKey } = this.props;
-        const children = Document.GetList<Document>(fieldKey, []);
-        const columns = Document.GetList(KS.ColumnsKey,
-            [KS.Title, KS.Data, KS.Author])
-        let content;
-        if (this.selectedIndex != -1) {
-            content = (
-                <DocumentView Document={children[this.selectedIndex]} DocumentView={undefined} ContainingCollectionView={this} />
-            )
-        } else {
-            content = <div />
-        }
-        return (
-            <div onPointerDown={this.onPointerDown} >
-                <SplitPane split={"vertical"} defaultSize="60%">
-                    <ScrollBox>
-                        <ReactTable
-                            data={children}
-                            pageSize={children.length}
-                            page={0}
-                            showPagination={false}
-                            style={{
-                                display: "inline-block"
-                            }}
-                            columns={columns.map(col => {
-                                return (
-                                    {
-                                        Header: col.Name,
-                                        accessor: (doc: Document) => [doc, col],
-                                        id: col.Id
-                                    })
-                            })}
-                            column={{
-                                ...ReactTableDefaults.column,
-                                Cell: this.renderCell
-                            }}
-                            getTrProps={this.getTrProps}
-                        />
-                    </ScrollBox>
-                    {content}
-                </SplitPane>
-            </div>
-        )
-    }
-}
\ No newline at end of file
diff --git a/src/views/collections/CollectionViewBase.tsx b/src/views/collections/CollectionViewBase.tsx
deleted file mode 100644
index 35d938d43..000000000
--- a/src/views/collections/CollectionViewBase.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { action, computed } from "mobx";
-import { observer } from "mobx-react";
-import { Document } from "../../fields/Document";
-import { Opt } from "../../fields/Field";
-import { Key, KeyStore } from "../../fields/Key";
-import { ListField } from "../../fields/ListField";
-import { SelectionManager } from "../../util/SelectionManager";
-import { ContextMenu } from "../ContextMenu";
-import React = require("react");
-import { DocumentView } from "../nodes/DocumentView";
-import { CollectionDockingView } from "./CollectionDockingView";
-import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-
-
-export interface CollectionViewProps {
-    CollectionFieldKey: Key;
-    DocumentForCollection: Document;
-    ContainingDocumentView: Opt<DocumentView>;
-}
-
-export const COLLECTION_BORDER_WIDTH = 2;
-
-@observer
-export class CollectionViewBase extends React.Component<CollectionViewProps> {
-
-    public static LayoutString(collectionType: string) {
-        return `<${collectionType} DocumentForCollection={Document} CollectionFieldKey={DataKey} ContainingDocumentView={DocumentView}/>`;
-    }
-    @computed
-    public get active(): boolean {
-        var isSelected = (this.props.ContainingDocumentView instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.ContainingDocumentView));
-        var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == this);
-        var topMost = this.props.ContainingDocumentView != undefined && (
-            this.props.ContainingDocumentView.props.ContainingCollectionView == undefined ||
-            this.props.ContainingDocumentView.props.ContainingCollectionView instanceof CollectionDockingView);
-        return isSelected || childSelected || topMost;
-    }
-    @action
-    addDocument = (doc: Document): void => {
-        //TODO This won't create the field if it doesn't already exist
-        const value = this.props.DocumentForCollection.GetData(this.props.CollectionFieldKey, ListField, new Array<Document>())
-        value.push(doc);
-    }
-
-    @action
-    removeDocument = (doc: Document): void => {
-        //TODO This won't create the field if it doesn't already exist
-        const value = this.props.DocumentForCollection.GetData(this.props.CollectionFieldKey, ListField, new Array<Document>())
-        if (value.indexOf(doc) !== -1) {
-            value.splice(value.indexOf(doc), 1)
-
-            SelectionManager.DeselectAll()
-            ContextMenu.Instance.clearItems()
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/src/views/nodes/CollectionFreeFormDocumentView.tsx b/src/views/nodes/CollectionFreeFormDocumentView.tsx
deleted file mode 100644
index e0bb459e9..000000000
--- a/src/views/nodes/CollectionFreeFormDocumentView.tsx
+++ /dev/null
@@ -1,223 +0,0 @@
-import { action, computed } from "mobx";
-import { observer } from "mobx-react";
-import { Key, KeyStore } from "../../fields/Key";
-import { NumberField } from "../../fields/NumberField";
-import { DragManager } from "../../util/DragManager";
-import { SelectionManager } from "../../util/SelectionManager";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
-import { ContextMenu } from "../ContextMenu";
-import "./NodeView.scss";
-import React = require("react");
-import { DocumentView, DocumentViewProps } from "./DocumentView";
-
-
-@observer
-export class CollectionFreeFormDocumentView extends DocumentView {
-    private _contextMenuCanOpen = false;
-    private _downX: number = 0;
-    private _downY: number = 0;
-
-    constructor(props: DocumentViewProps) {
-        super(props);
-    }
-    get screenRect(): ClientRect | DOMRect {
-        if (this._mainCont.current) {
-            return this._mainCont.current.getBoundingClientRect();
-        }
-        return new DOMRect();
-    }
-
-    @computed
-    get x(): number {
-        return this.props.Document.GetData(KeyStore.X, NumberField, Number(0));
-    }
-
-    @computed
-    get y(): number {
-        return this.props.Document.GetData(KeyStore.Y, NumberField, Number(0));
-    }
-
-    set x(x: number) {
-        this.props.Document.SetData(KeyStore.X, x, NumberField)
-    }
-
-    set y(y: number) {
-        this.props.Document.SetData(KeyStore.Y, y, NumberField)
-    }
-
-    @computed
-    get transform(): string {
-        return `translate(${this.x}px, ${this.y}px)`;
-    }
-
-    @computed
-    get width(): number {
-        return this.props.Document.GetData(KeyStore.Width, NumberField, Number(0));
-    }
-
-    set width(w: number) {
-        this.props.Document.SetData(KeyStore.Width, w, NumberField)
-    }
-
-    @computed
-    get height(): number {
-        return this.props.Document.GetData(KeyStore.Height, NumberField, Number(0));
-    }
-
-    set height(h: number) {
-        this.props.Document.SetData(KeyStore.Height, h, NumberField)
-    }
-
-    @computed
-    get zIndex(): number {
-        return this.props.Document.GetData(KeyStore.ZIndex, NumberField, Number(0));
-    }
-
-    set zIndex(h: number) {
-        this.props.Document.SetData(KeyStore.ZIndex, h, NumberField)
-    }
-
-    @action
-    dragComplete = (e: DragManager.DragCompleteEvent) => {
-    }
-
-    @computed
-    get active(): boolean {
-        return SelectionManager.IsSelected(this) || this.props.ContainingCollectionView === undefined ||
-            this.props.ContainingCollectionView.active;
-    }
-
-    @computed
-    get topMost(): boolean {
-        return this.props.ContainingCollectionView == undefined || this.props.ContainingCollectionView instanceof CollectionDockingView;
-    }
-
-    onPointerDown = (e: React.PointerEvent): void => {
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        var me = this;
-        if (e.shiftKey && e.buttons === 1) {
-            CollectionDockingView.StartOtherDrag(this._mainCont.current!, this.props.Document);
-            e.stopPropagation();
-            return;
-        }
-        this._contextMenuCanOpen = e.button == 2;
-        if (this.active && !e.isDefaultPrevented()) {
-            e.stopPropagation();
-            if (e.buttons === 2) {
-                e.preventDefault();
-            }
-            document.removeEventListener("pointermove", this.onPointerMove)
-            document.addEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp)
-            document.addEventListener("pointerup", this.onPointerUp);
-        }
-    }
-
-    onPointerMove = (e: PointerEvent): void => {
-        if (e.cancelBubble) {
-            this._contextMenuCanOpen = false;
-            return;
-        }
-        if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
-            this._contextMenuCanOpen = false;
-            if (this._mainCont.current != null && !this.topMost) {
-                this._contextMenuCanOpen = false;
-                const rect = this.screenRect;
-                let dragData: { [id: string]: any } = {};
-                dragData["document"] = this;
-                dragData["xOffset"] = e.x - rect.left;
-                dragData["yOffset"] = e.y - rect.top;
-                DragManager.StartDrag(this._mainCont.current, dragData, {
-                    handlers: {
-                        dragComplete: this.dragComplete,
-                    },
-                    hideSource: true
-                })
-            }
-        }
-        e.stopPropagation();
-        e.preventDefault();
-    }
-
-    onPointerUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onPointerMove)
-        document.removeEventListener("pointerup", this.onPointerUp)
-        e.stopPropagation();
-        if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
-            SelectionManager.SelectDoc(this, e.ctrlKey);
-        }
-    }
-
-    openRight = (e: React.MouseEvent): void => {
-        CollectionDockingView.AddRightSplit(this.props.Document);
-    }
-
-    deleteClicked = (e: React.MouseEvent): void => {
-        if (this.props.ContainingCollectionView instanceof CollectionFreeFormView) {
-            this.props.ContainingCollectionView.removeDocument(this.props.Document)
-        }
-    }
-    @action
-    fullScreenClicked = (e: React.MouseEvent): void => {
-        CollectionDockingView.OpenFullScreen(this.props.Document);
-        ContextMenu.Instance.clearItems();
-        ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
-        ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
-    }
-    @action
-    closeFullScreenClicked = (e: React.MouseEvent): void => {
-        CollectionDockingView.CloseFullScreen();
-        ContextMenu.Instance.clearItems();
-        ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
-        ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
-    }
-
-    @action
-    onContextMenu = (e: React.MouseEvent): void => {
-        if (!SelectionManager.IsSelected(this)) {
-            return;
-        }
-        e.preventDefault()
-
-        if (!this._contextMenuCanOpen) {
-            return;
-        }
-
-        if (this.topMost) {
-            ContextMenu.Instance.clearItems()
-            ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
-            ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
-        }
-        else {
-            // DocumentViews should stop propogation of this event
-            e.stopPropagation();
-
-            ContextMenu.Instance.clearItems();
-            ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
-            ContextMenu.Instance.addItem({ description: "Open Right", event: this.openRight })
-            ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
-            ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
-            SelectionManager.SelectDoc(this, e.ctrlKey);
-        }
-    }
-
-    render() {
-        var freestyling = this.props.ContainingCollectionView instanceof CollectionFreeFormView;
-        return (
-            <div className="node" ref={this._mainCont} style={{
-                transform: freestyling ? this.transform : "",
-                width: freestyling ? this.width : "100%",
-                height: freestyling ? this.height : "100%",
-                position: freestyling ? "absolute" : "relative",
-                zIndex: freestyling ? this.zIndex : 0,
-            }}
-                onContextMenu={this.onContextMenu}
-                onPointerDown={this.onPointerDown}>
-
-                <DocumentView {...this.props} DocumentView={this} />
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx
deleted file mode 100644
index 39be54a4d..000000000
--- a/src/views/nodes/DocumentView.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import { action, computed } from "mobx";
-import { observer } from "mobx-react";
-import { Document } from "../../fields/Document";
-import { Opt, FieldWaiting } from "../../fields/Field";
-import { Key, KeyStore } from "../../fields/Key";
-import { ListField } from "../../fields/ListField";
-import { NumberField } from "../../fields/NumberField";
-import { TextField } from "../../fields/TextField";
-import { Utils } from "../../Utils";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
-import { CollectionSchemaView } from "../collections/CollectionSchemaView";
-import { CollectionViewBase, COLLECTION_BORDER_WIDTH } from "../collections/CollectionViewBase";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { ImageBox } from "../nodes/ImageBox";
-import "./NodeView.scss";
-import React = require("react");
-const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
-
-export interface DocumentViewProps {
-    Document: Document;
-    DocumentView: Opt<DocumentView>  // needed only to set ContainingDocumentView on CollectionViewProps when invoked from JsxParser -- is there a better way?
-    ContainingCollectionView: Opt<CollectionViewBase>;
-}
-@observer
-export class DocumentView extends React.Component<DocumentViewProps> {
-
-    protected _mainCont = React.createRef<any>();
-    get MainContent() {
-        return this._mainCont;
-    }
-    @computed
-    get layout(): string {
-        return this.props.Document.GetData(KeyStore.Layout, TextField, String("<p>Error loading layout data</p>"));
-    }
-
-    @computed
-    get layoutKeys(): Key[] {
-        return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>());
-    }
-
-    @computed
-    get layoutFields(): Key[] {
-        return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>());
-    }
-
-    // 
-    // returns the cumulative scaling between the document and the screen
-    //
-    @computed
-    public get ScalingToScreenSpace(): number {
-        if (this.props.ContainingCollectionView != undefined &&
-            this.props.ContainingCollectionView.props.ContainingDocumentView != undefined) {
-            let ss = this.props.ContainingCollectionView.props.DocumentForCollection.GetData(KeyStore.Scale, NumberField, Number(1));
-            return this.props.ContainingCollectionView.props.ContainingDocumentView.ScalingToScreenSpace * ss;
-        }
-        return 1;
-    }
-
-    //
-    // Converts a coordinate in the screen space of the app into a local document coordinate.
-    //
-    public TransformToLocalPoint(screenX: number, screenY: number) {
-        // if this collection view is nested within another collection view, then 
-        // first transform the screen point into the parent collection's coordinate space.
-        let { LocalX: parentX, LocalY: parentY } = this.props.ContainingCollectionView != undefined &&
-            this.props.ContainingCollectionView.props.ContainingDocumentView != undefined ?
-            this.props.ContainingCollectionView.props.ContainingDocumentView.TransformToLocalPoint(screenX, screenY) :
-            { LocalX: screenX, LocalY: screenY };
-        let ContainerX: number = parentX - COLLECTION_BORDER_WIDTH;
-        let ContainerY: number = parentY - COLLECTION_BORDER_WIDTH;
-
-        var Xx = this.props.Document.GetData(KeyStore.X, NumberField, Number(0));
-        var Yy = this.props.Document.GetData(KeyStore.Y, NumberField, Number(0));
-        // CollectionDockingViews change the location of their children frames without using a Dash transformation.
-        // They also ignore any transformation that may have been applied to their content document.
-        // NOTE: this currently assumes CollectionDockingViews aren't nested.
-        if (this.props.ContainingCollectionView instanceof CollectionDockingView) {
-            var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this.MainContent.current!);
-            Xx = rx - COLLECTION_BORDER_WIDTH;
-            Yy = ry - COLLECTION_BORDER_WIDTH;
-        }
-
-        let Ss = this.props.Document.GetData(KeyStore.Scale, NumberField, Number(1));
-        let Panxx = this.props.Document.GetData(KeyStore.PanX, NumberField, Number(0));
-        let Panyy = this.props.Document.GetData(KeyStore.PanY, NumberField, Number(0));
-        let LocalX = (ContainerX - (Xx + Panxx)) / Ss;
-        let LocalY = (ContainerY - (Yy + Panyy)) / Ss;
-
-        return { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY };
-    }
-
-    //
-    // Converts a point in the coordinate space of a document to a screen space coordinate.
-    //
-    public TransformToScreenPoint(localX: number, localY: number, Ss: number = 1, Panxx: number = 0, Panyy: number = 0): { ScreenX: number, ScreenY: number } {
-
-        var Xx = this.props.Document.GetData(KeyStore.X, NumberField, Number(0));
-        var Yy = this.props.Document.GetData(KeyStore.Y, NumberField, Number(0));
-        // CollectionDockingViews change the location of their children frames without using a Dash transformation.
-        // They also ignore any transformation that may have been applied to their content document.
-        // NOTE: this currently assumes CollectionDockingViews aren't nested.
-        if (this.props.ContainingCollectionView instanceof CollectionDockingView) {
-            var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this.MainContent.current!);
-            Xx = rx - COLLECTION_BORDER_WIDTH;
-            Yy = ry - COLLECTION_BORDER_WIDTH;
-        }
-
-        let W = COLLECTION_BORDER_WIDTH;
-        let H = COLLECTION_BORDER_WIDTH;
-        let parentX = (localX - W) * Ss + (Xx + Panxx) + W;
-        let parentY = (localY - H) * Ss + (Yy + Panyy) + H;
-
-        // if this collection view is nested within another collection view, then 
-        // first transform the local point into the parent collection's coordinate space.
-        let containingDocView = this.props.ContainingCollectionView != undefined ? this.props.ContainingCollectionView.props.ContainingDocumentView : undefined;
-        if (containingDocView != undefined) {
-            let ss = containingDocView.props.Document.GetData(KeyStore.Scale, NumberField, Number(1));
-            let panxx = containingDocView.props.Document.GetData(KeyStore.PanX, NumberField, Number(0)) + COLLECTION_BORDER_WIDTH * ss;
-            let panyy = containingDocView.props.Document.GetData(KeyStore.PanY, NumberField, Number(0)) + COLLECTION_BORDER_WIDTH * ss;
-            let { ScreenX, ScreenY } = containingDocView.TransformToScreenPoint(parentX, parentY, ss, panxx, panyy);
-            parentX = ScreenX;
-            parentY = ScreenY;
-        }
-        return { ScreenX: parentX, ScreenY: parentY };
-    }
-
-
-    render() {
-        let bindings = { ...this.props } as any;
-        for (const key of this.layoutKeys) {
-            bindings[key.Name + "Key"] = key;  // this maps string values of the form <keyname>Key to an actual key Kestore.keyname  e.g,   "DataKey" => KeyStore.Data
-        }
-        for (const key of this.layoutFields) {
-            let field = this.props.Document.Get(key);
-            bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
-        }
-        if (bindings.DocumentView === undefined) {
-            bindings.DocumentView = this; // set the DocumentView to this if it hasn't already been set by a sub-class during its render method.
-        }
-        return (
-            <div className="node" ref={this._mainCont} style={{ width: "100%", height: "100%", }}>
-                <JsxParser
-                    components={{ FormattedTextBox: FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView }}
-                    bindings={bindings}
-                    jsx={this.layout}
-                    showWarnings={true}
-                    onError={(test: any) => { console.log(test) }}
-                />
-            </div>
-        )
-    }
-}
diff --git a/src/views/nodes/FieldTextBox.scss b/src/views/nodes/FieldTextBox.scss
deleted file mode 100644
index b6ce2fabc..000000000
--- a/src/views/nodes/FieldTextBox.scss
+++ /dev/null
@@ -1,14 +0,0 @@
-.ProseMirror {
-    margin-top: -1em;
-    width: 100%;
-    height: 100%;
-}
-
-.ProseMirror:focus {
-    outline: none !important
-}
-
-.fieldTextBox-cont {
-    background: white;
-    padding: 1vw;
-}
\ No newline at end of file
diff --git a/src/views/nodes/FieldView.tsx b/src/views/nodes/FieldView.tsx
deleted file mode 100644
index 3a7652284..000000000
--- a/src/views/nodes/FieldView.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import React = require("react")
-import { Document } from "../../fields/Document";
-import { observer } from "mobx-react";
-import { computed } from "mobx";
-import { Field, Opt, FieldWaiting, FieldValue } from "../../fields/Field";
-import { TextField } from "../../fields/TextField";
-import { NumberField } from "../../fields/NumberField";
-import { RichTextField } from "../../fields/RichTextField";
-import { FormattedTextBox } from "./FormattedTextBox";
-import { ImageField } from "../../fields/ImageField";
-import { ImageBox } from "./ImageBox";
-import { Key } from "../../fields/Key";
-import { DocumentView } from "./DocumentView";
-
-//
-// these properties get assigned through the render() method of the DocumentView when it creates this node.
-// However, that only happens because the properties are "defined" in the markup for the field view.
-// See the LayoutString method on each field view :   ImageBox, FormattedTextBox, etc. 
-//
-export interface FieldViewProps {
-    fieldKey: Key;
-    doc: Document;
-    DocumentViewForField: Opt<DocumentView>
-}
-
-@observer
-export class FieldView extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldType: string) { return `<${fieldType} doc={Document} DocumentViewForField={DocumentView} fieldKey={DataKey} />`; }
-    @computed
-    get field(): FieldValue<Field> {
-        const { doc, fieldKey } = this.props;
-        return doc.Get(fieldKey);
-    }
-    render() {
-        const field = this.field;
-        if (!field) {
-            return <p>{'<null>'}</p>
-        }
-        if (field instanceof TextField) {
-            return <p>{field.Data}</p>
-        }
-        else if (field instanceof RichTextField) {
-            return <FormattedTextBox {...this.props} />
-        }
-        else if (field instanceof ImageField) {
-            return <ImageBox {...this.props} />
-        }
-        else if (field instanceof NumberField) {
-            return <p>{field.Data}</p>
-        } else if (field != FieldWaiting) {
-            return <p>{field.GetValue}</p>
-        } else
-            return <p> {"Waiting for server..."} </p>
-    }
-
-}
\ No newline at end of file
diff --git a/src/views/nodes/FormattedTextBox.scss b/src/views/nodes/FormattedTextBox.scss
deleted file mode 100644
index 492367fce..000000000
--- a/src/views/nodes/FormattedTextBox.scss
+++ /dev/null
@@ -1,14 +0,0 @@
-.ProseMirror {
-    margin-top: -1em;
-    width: 100%;
-    height: 100%;
-}
-
-.ProseMirror:focus {
-    outline: none !important
-}
-
-.formattedTextBox-cont {
-    background: white;
-    padding: 1vw;
-}
\ No newline at end of file
diff --git a/src/views/nodes/FormattedTextBox.tsx b/src/views/nodes/FormattedTextBox.tsx
deleted file mode 100644
index 6d0e117cc..000000000
--- a/src/views/nodes/FormattedTextBox.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import { action, IReactionDisposer, reaction } from "mobx";
-import { observer } from "mobx-react"
-import { baseKeymap } from "prosemirror-commands";
-import { history, redo, undo } from "prosemirror-history";
-import { keymap } from "prosemirror-keymap";
-import { schema } from "prosemirror-schema-basic";
-import { EditorState, Transaction } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import { Opt, FieldWaiting, FieldValue } from "../../fields/Field";
-import { SelectionManager } from "../../util/SelectionManager";
-import "./FormattedTextBox.scss";
-import React = require("react")
-import { RichTextField } from "../../fields/RichTextField";
-import { FieldViewProps, FieldView } from "./FieldView";
-import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
-
-
-// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
-//
-//  HTML Markup:  <FormattedTextBox Doc={Document's ID} FieldKey={Key's name + "Key"}
-// 
-//  In Code, the node's HTML is specified in the document's parameterized structure as:
-//        document.SetField(KeyStore.Layout,  "<FormattedTextBox doc={doc} fieldKey={<KEYNAME>Key} />");
-//  and the node's binding to the specified document KEYNAME as:
-//        document.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.<KEYNAME>]));
-//  The Jsx parser at run time will bind:
-//        'fieldKey' property to the Key stored in LayoutKeys
-//    and 'doc' property to the document that is being rendered
-//
-//  When rendered() by React, this extracts the TextController from the Document stored at the 
-//  specified Key and assigns it to an HTML input node.  When changes are made tot his node, 
-//  this will edit the document and assign the new value to that field.
-//]
-@observer
-export class FormattedTextBox extends React.Component<FieldViewProps> {
-
-    public static LayoutString() { return FieldView.LayoutString("FormattedTextBox"); }
-    private _ref: React.RefObject<HTMLDivElement>;
-    private _editorView: Opt<EditorView>;
-    private _reactionDisposer: Opt<IReactionDisposer>;
-
-    constructor(props: FieldViewProps) {
-        super(props);
-
-        this._ref = React.createRef();
-
-        this.onChange = this.onChange.bind(this);
-    }
-
-    dispatchTransaction = (tx: Transaction) => {
-        if (this._editorView) {
-            const state = this._editorView.state.apply(tx);
-            this._editorView.updateState(state);
-            const { doc, fieldKey } = this.props;
-            doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
-        }
-    }
-
-    componentDidMount() {
-        let state: EditorState;
-        const { doc, fieldKey } = this.props;
-        const config = {
-            schema,
-            plugins: [
-                history(),
-                keymap({ "Mod-z": undo, "Mod-y": redo }),
-                keymap(baseKeymap)
-            ]
-        };
-
-        let field = doc.GetT(fieldKey, RichTextField);
-        if (field && field != FieldWaiting) {  // bcz: don't think this works
-            state = EditorState.fromJSON(config, JSON.parse(field.Data));
-        } else {
-            state = EditorState.create(config);
-        }
-        if (this._ref.current) {
-            this._editorView = new EditorView(this._ref.current, {
-                state,
-                dispatchTransaction: this.dispatchTransaction
-            });
-        }
-
-        this._reactionDisposer = reaction(() => {
-            const field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
-            return field && field != FieldWaiting ? field.Data : undefined;
-        }, (field) => {
-            if (field && this._editorView) {
-                this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)));
-            }
-        })
-    }
-
-    componentWillUnmount() {
-        if (this._editorView) {
-            this._editorView.destroy();
-        }
-        if (this._reactionDisposer) {
-            this._reactionDisposer();
-        }
-    }
-
-    shouldComponentUpdate() {
-        return false;
-    }
-
-    @action
-    onChange(e: React.ChangeEvent<HTMLInputElement>) {
-        const { fieldKey, doc } = this.props;
-        doc.SetData(fieldKey, e.target.value, RichTextField);
-    }
-    onPointerDown = (e: React.PointerEvent): void => {
-        let me = this;
-        if (e.buttons === 1 && me.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(me.props.DocumentViewForField)) {
-            e.stopPropagation();
-        }
-    }
-    render() {
-        return (<div className="formattedTextBox-cont"
-            style={{
-                color: "initial",
-                whiteSpace: "initial"
-            }}
-            onPointerDown={this.onPointerDown}
-            ref={this._ref} />)
-    }
-}
\ No newline at end of file
diff --git a/src/views/nodes/ImageBox.scss b/src/views/nodes/ImageBox.scss
deleted file mode 100644
index 136fda1d0..000000000
--- a/src/views/nodes/ImageBox.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-
-.imageBox-cont {
-    padding: 0vw;
-}
-
-.imageBox-button {
-    padding : 0vw;
-    border: none;
-    width : 100%;
-    height: 100%;
-}
\ No newline at end of file
diff --git a/src/views/nodes/ImageBox.tsx b/src/views/nodes/ImageBox.tsx
deleted file mode 100644
index 123c76d19..000000000
--- a/src/views/nodes/ImageBox.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-
-import Lightbox from 'react-image-lightbox';
-import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { SelectionManager } from "../../util/SelectionManager";
-import "./ImageBox.scss";
-import React = require("react")
-import { ImageField } from '../../fields/ImageField';
-import { FieldViewProps, FieldView } from './FieldView';
-import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
-import { FieldWaiting } from '../../fields/Field';
-import { observer } from "mobx-react"
-import { observable, action } from 'mobx';
-
-@observer
-export class ImageBox extends React.Component<FieldViewProps> {
-
-    public static LayoutString() { return FieldView.LayoutString("ImageBox"); }
-    private _ref: React.RefObject<HTMLDivElement>;
-    private _downX: number = 0;
-    private _downY: number = 0;
-    private _lastTap: number = 0;
-    @observable private _photoIndex: number = 0;
-    @observable private _isOpen: boolean = false;
-
-    constructor(props: FieldViewProps) {
-        super(props);
-
-        this._ref = React.createRef();
-        this.state = {
-            photoIndex: 0,
-            isOpen: false,
-        };
-    }
-
-    componentDidMount() {
-    }
-
-    componentWillUnmount() {
-    }
-
-    onPointerDown = (e: React.PointerEvent): void => {
-        if (Date.now() - this._lastTap < 300) {
-            if (e.buttons === 1 && this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField)) {
-                e.stopPropagation();
-                this._downX = e.clientX;
-                this._downY = e.clientY;
-                document.removeEventListener("pointerup", this.onPointerUp);
-                document.addEventListener("pointerup", this.onPointerUp);
-            }
-        } else {
-            this._lastTap = Date.now();
-        }
-    }
-    @action
-    onPointerUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointerup", this.onPointerUp);
-        if (Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) {
-            this._isOpen = true;
-        }
-        e.stopPropagation();
-    }
-
-    lightbox = (path: string) => {
-        const images = [path, "http://www.cs.brown.edu/~bcz/face.gif"];
-        if (this._isOpen && this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField)) {
-            return (<Lightbox
-                mainSrc={images[this._photoIndex]}
-                nextSrc={images[(this._photoIndex + 1) % images.length]}
-                prevSrc={images[(this._photoIndex + images.length - 1) % images.length]}
-                onCloseRequest={() => this.setState({ isOpen: false })}
-                onMovePrevRequest={action(() =>
-                    this._photoIndex = (this._photoIndex + images.length - 1) % images.length
-                )}
-                onMoveNextRequest={action(() =>
-                    this._photoIndex = (this._photoIndex + 1) % images.length
-                )}
-            />)
-        }
-    }
-
-    render() {
-        let field = this.props.doc.Get(this.props.fieldKey);
-        let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
-            field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif";
-
-        return (
-            <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} >
-                <img src={path} width="100%" alt="Image not found" />
-                {this.lightbox(path)}
-            </div>)
-    }
-}
\ No newline at end of file
diff --git a/src/views/nodes/NodeView.scss b/src/views/nodes/NodeView.scss
deleted file mode 100644
index dac1c0a8e..000000000
--- a/src/views/nodes/NodeView.scss
+++ /dev/null
@@ -1,23 +0,0 @@
-.node {
-    position: absolute;
-    background: #cdcdcd;
-    overflow: hidden;
-    &.minimized {
-        width: 30px;
-        height: 30px;
-    }
-    .top {
-        background: #232323;
-        height: 20px;
-        cursor: pointer;
-    }
-    .content {
-        padding: 20px 20px;
-        height: auto;
-        box-sizing: border-box;
-    }
-    .scroll-box {
-        overflow-y: scroll;
-        height: calc(100% - 20px);
-    }
-}
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
index 1a7c9286e..e91b0be5e 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -5,7 +5,7 @@ const CopyWebpackPlugin = require("copy-webpack-plugin");
 module.exports = {
   devtool: 'eval',
   mode: 'development',
-  entry: "./src/Main.tsx",
+  entry: "./src/client/views/Main.tsx",
   devtool: "source-map",
   node: {
     fs: 'empty',
-- 
cgit v1.2.3-70-g09d2

