From 81c0a8373fd5cb051531762243e200f11f8c7297 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <abdullah_ahmed@brown.edu>
Date: Sat, 9 Mar 2019 23:00:41 -0500
Subject: editable workspace titles now supported, added 'display' prop to
 EditableView

---
 src/client/views/collections/CollectionTreeView.tsx | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8b06d9ac4..9c31bdae2 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -69,7 +69,9 @@ class TreeView extends React.Component<TreeViewProps> {
             return <div key={this.props.document.Id}></div>;
         }
 
-        return <div className="docContainer"> <EditableView contents={title.Data}
+        return <div className="docContainer"> <EditableView
+            display={"inline"}
+            contents={title.Data}
             height={36} GetValue={() => {
                 let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField);
                 if (title && title !== "<Waiting>")
@@ -159,6 +161,7 @@ export class CollectionTreeView extends CollectionViewBase {
             <div id="body" className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
                 <h3>
                     <EditableView contents={titleStr}
+                        display={"inline"}
                         height={72} GetValue={() => {
                             return this.props.Document.Title;
                         }} SetValue={(value: string) => {
-- 
cgit v1.2.3-70-g09d2


From 772182d40f180121ed14384bc9c6f623dd77c415 Mon Sep 17 00:00:00 2001
From: Jude <julie_wang1@brown.edu>
Date: Sun, 10 Mar 2019 21:46:30 -0400
Subject: treeview improvements

---
 .../views/collections/CollectionTreeView.scss      | 26 ++++++++++++++++++++--
 .../views/collections/CollectionTreeView.tsx       | 25 ++++++++++++++-------
 src/client/views/nodes/AudioBox.tsx                | 12 +++++-----
 src/client/views/nodes/DocumentView.tsx            |  5 +++--
 4 files changed, 50 insertions(+), 18 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 4b28ded45..c2b376a34 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -3,16 +3,24 @@
 #body {
   padding: 20px;
   background: $light-color-secondary;
+  font-size: 13px;
+  overflow: scroll;
 }
 
 ul {
   list-style: none;
+  padding-left: 20px;
 }
 
 li {
   margin: 5px 0;
 }
 
+.collection-child {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+
 .no-indent {
   padding-left: 0;
 }
@@ -20,10 +28,16 @@ li {
 .bullet {
   width: 1.5em;
   display: inline-block;
+  color: $intermediate-color;
+}
+
+.coll-title {
+  font-size: 24px;
+  margin-bottom: 20px;
 }
 
 .collectionTreeView-dropTarget {
-  border: 0px solid $intermediate-color;
+  border: 0px solid transparent;
   border-radius: $border-radius;
   box-sizing: border-box;
   height: 100%;
@@ -33,8 +47,16 @@ li {
   display: inline-table;
 }
 
+.docContainer:hover {
+  .delete-button {
+    display: inline;
+  }
+}
+
 .delete-button {
   color: $intermediate-color;
   float: right;
-  margin-left: 1em;
+  margin-left: 15px;
+  margin-top: 3px;
+  display: none;
 }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8b06d9ac4..80fc89712 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -12,6 +12,10 @@ import { setupDrag } from "../../util/DragManager";
 import { FieldWaiting } from "../../../fields/Field";
 import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
 
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faTrashAlt, faCaretRight, faCaretDown } from '@fortawesome/free-solid-svg-icons';
+
 export interface TreeViewProps {
     document: Document;
     deleteDoc: (doc: Document) => void;
@@ -23,6 +27,10 @@ export enum BulletType {
     List
 }
 
+library.add(faTrashAlt);
+library.add(faCaretDown);
+library.add(faCaretRight);
+
 @observer
 /**
  * Component that takes in a document prop and a boolean whether it's collapsed or not.
@@ -50,11 +58,11 @@ class TreeView extends React.Component<TreeViewProps> {
 
         switch (type) {
             case BulletType.Collapsed:
-                return <div className="bullet" onClick={onClicked}>&#9654;</div>
+                return <div className="bullet" onClick={onClicked}><FontAwesomeIcon icon="caret-right" /></div>
             case BulletType.Collapsible:
-                return <div className="bullet" onClick={onClicked}>&#9660;</div>
+                return <div className="bullet" onClick={onClicked}><FontAwesomeIcon icon="caret-down" /></div>
             case BulletType.List:
-                return <div className="bullet">&mdash;</div>
+                return <div className="bullet"></div>
         }
     }
 
@@ -79,7 +87,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 this.props.document.SetData(KeyStore.Title, value, TextField);
                 return true;
             }} />
-            <div className="delete-button" onClick={this.delete}>x</div>
+            <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
         </div >
     }
 
@@ -101,7 +109,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     <TreeView document={value} deleteDoc={this.remove} />)
                 )
                 subView =
-                    <li key={this.props.document.Id} >
+                    <li className="collection-child" key={this.props.document.Id} >
                         {this.renderBullet(BulletType.Collapsible)}
                         {titleElement}
                         <ul key={this.props.document.Id}>
@@ -109,7 +117,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         </ul>
                     </li>
             } else {
-                subView = <li key={this.props.document.Id}>
+                subView = <li className="collection-child" key={this.props.document.Id}>
                     {this.renderBullet(BulletType.Collapsed)}
                     {titleElement}
                 </li>
@@ -157,7 +165,7 @@ export class CollectionTreeView extends CollectionViewBase {
 
         return (
             <div id="body" className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
-                <h3>
+                <div className="coll-title">
                     <EditableView contents={titleStr}
                         height={72} GetValue={() => {
                             return this.props.Document.Title;
@@ -165,7 +173,8 @@ export class CollectionTreeView extends CollectionViewBase {
                             this.props.Document.SetData(KeyStore.Title, value, TextField);
                             return true;
                         }} />
-                </h3>
+                </div>
+                <hr />
                 <ul className="no-indent">
                     {childrenElement}
                 </ul>
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index f7d89843d..6daf15f5f 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -18,7 +18,7 @@ export class AudioBox extends React.Component<FieldViewProps> {
         super(props);
     }
 
-   
+
 
     componentDidMount() {
     }
@@ -26,16 +26,16 @@ export class AudioBox extends React.Component<FieldViewProps> {
     componentWillUnmount() {
     }
 
-   
+
     render() {
         let field = this.props.doc.Get(this.props.fieldKey)
-        let path = field == FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3":
+        let path = field == FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3" :
             field instanceof AudioField ? field.Data.href : "http://techslides.com/demos/samples/sample.mp3";
-    
+
         return (
             <div>
-                <audio controls className = "audiobox-cont">
-                    <source src = {path} type="audio/mpeg"/>
+                <audio controls className="audiobox-cont">
+                    <source src={path} type="audio/mpeg" />
                     Not supported.
                 </audio>
             </div>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 263bb31d7..9a0c1e6a4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -16,8 +16,8 @@ import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { ContextMenu } from "../ContextMenu";
 import { FormattedTextBox } from "../nodes/FormattedTextBox";
 import { ImageBox } from "../nodes/ImageBox";
-import { VideoBox } from "../nodes/VideoBox"; 
-import { AudioBox } from "../nodes/AudioBox"; 
+import { VideoBox } from "../nodes/VideoBox";
+import { AudioBox } from "../nodes/AudioBox";
 import { Documents } from "../../documents/Documents"
 import { KeyValueBox } from "./KeyValueBox"
 import { WebBox } from "../nodes/WebBox";
@@ -97,6 +97,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
         this._downY = e.clientY;
+
         if (e.shiftKey && e.buttons === 1) {
             CollectionDockingView.Instance.StartOtherDrag(this.props.Document, e);
             e.stopPropagation();
-- 
cgit v1.2.3-70-g09d2


From b639d1fdfc8dfe9b1ea300665b4b4d74929f8af7 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 20 Mar 2019 20:21:12 -0400
Subject: added drag from tree view

---
 .../views/collections/CollectionTreeView.tsx       | 23 +++++++++++-----------
 1 file changed, 11 insertions(+), 12 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f9da759fd..19dd44c97 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -10,7 +10,7 @@ import "./CollectionTreeView.scss";
 import { EditableView } from "../EditableView";
 import { setupDrag } from "../../util/DragManager";
 import { FieldWaiting } from "../../../fields/Field";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { COLLECTION_BORDER_WIDTH, CollectionView } from "./CollectionView";
 
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -70,16 +70,12 @@ class TreeView extends React.Component<TreeViewProps> {
      * Renders the EditableView title element for placement into the tree.
      */
     renderTitle() {
+        let reference = React.createRef<HTMLDivElement>();
+        let onItemDown = setupDrag(reference, () => this.props.document, (containingCollection: CollectionView) => this.props.deleteDoc(this.props.document));
         let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField);
-
-        // if the title hasn't loaded, immediately return the div
-        if (!title || title === "<Waiting>") {
-            return <div key={this.props.document.Id}></div>;
-        }
-
-        return <div className="docContainer"> <EditableView
+        let editableView = (titleString: string) => (<EditableView
             display={"inline"}
-            contents={title.Data}
+            contents={titleString}
             height={36} GetValue={() => {
                 let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField);
                 if (title && title !== "<Waiting>")
@@ -88,9 +84,12 @@ class TreeView extends React.Component<TreeViewProps> {
             }} SetValue={(value: string) => {
                 this.props.document.SetData(KeyStore.Title, value, TextField);
                 return true;
-            }} />
-            <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
-        </div >
+            }} />);
+        return (
+            <div key={this.props.document.Id} className="docContainer" ref={reference} onPointerDown={onItemDown}>
+                {(!title || title === "<Waiting>") ? (null) : editableView(title.Data)}
+                <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
+            </div >)
     }
 
     render() {
-- 
cgit v1.2.3-70-g09d2


From d882d4013dc2df03a55c3f4afc954b7d9a38b4e4 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 20 Mar 2019 22:08:32 -0400
Subject: tree view cleanup

---
 src/client/Server.ts                               |   2 +-
 src/client/views/EditableView.tsx                  |   4 +-
 src/client/views/InkingCanvas.tsx                  |   2 +-
 .../views/collections/CollectionFreeFormView.tsx   |   4 +-
 .../views/collections/CollectionTreeView.scss      |   5 +-
 .../views/collections/CollectionTreeView.tsx       | 143 ++++++++-------------
 src/client/views/collections/CollectionView.tsx    |   4 +-
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 8 files changed, 66 insertions(+), 102 deletions(-)

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

diff --git a/src/client/Server.ts b/src/client/Server.ts
index bbdc27397..7d882c76d 100644
--- a/src/client/Server.ts
+++ b/src/client/Server.ts
@@ -39,7 +39,7 @@ export class Server {
             reaction(() => {
                 return this.ClientFieldsCached.get(fieldid);
             }, (field, reaction) => {
-                if (field !== "<Waiting>") {
+                if (field !== FieldWaiting) {
                     reaction.dispose()
                     callback(field)
                 }
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 98a6ed1ba..579d6e6ad 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,6 +1,6 @@
 import React = require('react')
 import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import { observable, action, trace } from 'mobx';
 import "./EditableView.scss"
 
 export interface EditableProps {
@@ -52,7 +52,7 @@ export class EditableView extends React.Component<EditableProps> {
         } else {
             return (
                 <div className="editableView-container-editing" style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
-                    onClick={action(() => this.editing = true)}>
+                    onClick={action(() => this.editing = true)} >
                     {this.props.contents}
                 </div>
             )
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 8d0121035..36a8834a0 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -137,7 +137,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
         // get data from server
         // let inkField = this.props.Document.GetT(KeyStore.Ink, InkField);
-        // if (!inkField || inkField == "<Waiting>") {
+        // if (!inkField || inkField == FieldWaiting) {
         //     return (<div className="inking-canvas" style={canvasStyle}
         //         onMouseDown={this.handleMouseDown} onMouseMove={this.handleMouseMove} >
         //         <svg>
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index 8bf4a7539..da9f7b392 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -219,13 +219,13 @@ export class CollectionFreeFormView extends CollectionViewBase {
 
     @computed get backgroundLayout(): string | undefined {
         let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField);
-        if (field && field !== "<Waiting>") {
+        if (field && field !== FieldWaiting) {
             return field.Data;
         }
     }
     @computed get overlayLayout(): string | undefined {
         let field = this.props.Document.GetT(KeyStore.OverlayLayout, TextField);
-        if (field && field !== "<Waiting>") {
+        if (field && field !== FieldWaiting) {
             return field.Data;
         }
     }
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index fa0f1c761..5a14aa54d 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,6 +1,8 @@
 @import "../global_variables";
 #body {
     padding: 20px;
+    padding-left: 10px;
+    padding-right: 0px;
     background: $light-color-secondary;
     font-size: 13px;
     overflow: scroll;
@@ -49,6 +51,7 @@ li {
 .docContainer:hover {
     .delete-button {
         display: inline;
+        width: auto;
     }
 }
 
@@ -57,5 +60,5 @@ li {
     float: right;
     margin-left: 15px;
     margin-top: 3px;
-    display: none;
+    display: inline;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 19dd44c97..6edd90215 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,20 +1,19 @@
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import { CollectionViewBase } from "./CollectionViewBase";
 import { Document } from "../../../fields/Document";
+import { FieldWaiting } from "../../../fields/Field";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
-import React = require("react")
-import { TextField } from "../../../fields/TextField";
-import { observable, action } from "mobx";
-import "./CollectionTreeView.scss";
-import { EditableView } from "../EditableView";
 import { setupDrag } from "../../util/DragManager";
-import { FieldWaiting } from "../../../fields/Field";
-import { COLLECTION_BORDER_WIDTH, CollectionView } from "./CollectionView";
+import { EditableView } from "../EditableView";
+import "./CollectionTreeView.scss";
+import { CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { CollectionViewBase } from "./CollectionViewBase";
+import React = require("react")
 
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faTrashAlt, faCaretRight, faCaretDown } from '@fortawesome/free-solid-svg-icons';
 
 export interface TreeViewProps {
     document: Document;
@@ -37,13 +36,9 @@ library.add(faCaretRight);
  */
 class TreeView extends React.Component<TreeViewProps> {
 
-    @observable
-    collapsed: boolean = false;
-
-    delete = () => {
-        this.props.deleteDoc(this.props.document);
-    }
+    @observable _collapsed: boolean = true;
 
+    delete = () => this.props.deleteDoc(this.props.document);
 
     @action
     remove = (document: Document) => {
@@ -54,16 +49,13 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     renderBullet(type: BulletType) {
-        let onClicked = action(() => this.collapsed = !this.collapsed);
-
+        let onClicked = action(() => this._collapsed = !this._collapsed);
+        let bullet: IconProp | undefined = undefined;
         switch (type) {
-            case BulletType.Collapsed:
-                return <div className="bullet" onClick={onClicked}><FontAwesomeIcon icon="caret-right" /></div>
-            case BulletType.Collapsible:
-                return <div className="bullet" onClick={onClicked}><FontAwesomeIcon icon="caret-down" /></div>
-            case BulletType.List:
-                return <div className="bullet"></div>
+            case BulletType.Collapsed: bullet = "caret-right"; break;
+            case BulletType.Collapsible: bullet = "caret-down"; break;
         }
+        return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>
     }
 
     /**
@@ -72,74 +64,48 @@ class TreeView extends React.Component<TreeViewProps> {
     renderTitle() {
         let reference = React.createRef<HTMLDivElement>();
         let onItemDown = setupDrag(reference, () => this.props.document, (containingCollection: CollectionView) => this.props.deleteDoc(this.props.document));
-        let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField);
-        let editableView = (titleString: string) => (<EditableView
-            display={"inline"}
-            contents={titleString}
-            height={36} GetValue={() => {
-                let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField);
-                if (title && title !== "<Waiting>")
-                    return title.Data;
-                return "";
-            }} SetValue={(value: string) => {
-                this.props.document.SetData(KeyStore.Title, value, TextField);
-                return true;
-            }} />);
+        let editableView = (titleString: string) =>
+            (<EditableView
+                display={"inline"}
+                contents={titleString}
+                height={36}
+                GetValue={() => this.props.document.Title}
+                SetValue={(value: string) => {
+                    this.props.document.SetText(KeyStore.Title, value);
+                    return true;
+                }}
+            />);
         return (
             <div key={this.props.document.Id} className="docContainer" ref={reference} onPointerDown={onItemDown}>
-                {(!title || title === "<Waiting>") ? (null) : editableView(title.Data)}
+                {editableView(this.props.document.Title)}
                 <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
             </div >)
     }
 
     render() {
-        var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
-
-        let reference = React.createRef<HTMLDivElement>();
-        let onItemDown = setupDrag(reference, () => this.props.document);
-        let titleElement = this.renderTitle();
+        let bulletType = BulletType.List;
+        let childElements: JSX.Element | undefined = undefined;
 
-        // check if this document is a collection
-        if (children && children !== FieldWaiting) {
-            let subView;
-
-            // if uncollapsed, then add the children elements
-            if (!this.collapsed) {
-                // render all children elements
-                let childrenElement = (children.Data.map(value =>
-                    <TreeView document={value} deleteDoc={this.remove} />)
-                )
-                subView =
-                    <li className="collection-child" key={this.props.document.Id} >
-                        {this.renderBullet(BulletType.Collapsible)}
-                        {titleElement}
-                        <ul key={this.props.document.Id}>
-                            {childrenElement}
-                        </ul>
-                    </li>
-            } else {
-                subView = <li className="collection-child" key={this.props.document.Id}>
-                    {this.renderBullet(BulletType.Collapsed)}
-                    {titleElement}
-                </li>
+        var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
+        if (children && children !== FieldWaiting) { // add children for a collection
+            if (!this._collapsed) {
+                bulletType = BulletType.Collapsible;
+                childElements = <ul key={this.props.document.Id}>
+                    {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} />)}
+                </ul>
             }
-
-            return <div className="treeViewItem-container" onPointerDown={onItemDown} ref={reference}>
-                {subView}
-            </div>
-        }
-
-        // otherwise this is a normal leaf node
-        else {
-            return <li key={this.props.document.Id}>
-                {this.renderBullet(BulletType.List)}
-                {titleElement}
-            </li>;
+            else bulletType = BulletType.Collapsed;
         }
+        return <div className="treeViewItem-container" >
+            <li className="collection-child" key={this.props.document.Id}>
+                {this.renderBullet(bulletType)}
+                {this.renderTitle()}
+                {childElements ? childElements : (null)}
+            </li>
+        </div>
     }
 }
 
-
 @observer
 export class CollectionTreeView extends CollectionViewBase {
 
@@ -152,12 +118,6 @@ export class CollectionTreeView extends CollectionViewBase {
     }
 
     render() {
-        let titleStr = "";
-        let title = this.props.Document.GetT<TextField>(KeyStore.Title, TextField);
-        if (title && title !== FieldWaiting) {
-            titleStr = title.Data;
-        }
-
         var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
         let childrenElement = !children || children === FieldWaiting ? (null) :
             (children.Data.map(value =>
@@ -167,12 +127,13 @@ export class CollectionTreeView extends CollectionViewBase {
         return (
             <div id="body" className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
                 <div className="coll-title">
-                    <EditableView contents={titleStr}
+                    <EditableView
+                        contents={this.props.Document.Title}
                         display={"inline"}
-                        height={72} GetValue={() => {
-                            return this.props.Document.Title;
-                        }} SetValue={(value: string) => {
-                            this.props.Document.SetData(KeyStore.Title, value, TextField);
+                        height={72}
+                        GetValue={() => this.props.Document.Title}
+                        SetValue={(value: string) => {
+                            this.props.Document.SetText(KeyStore.Title, value);
                             return true;
                         }} />
                 </div>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index d4afc69cf..7e1d31018 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -12,7 +12,7 @@ import { CollectionDockingView } from "./CollectionDockingView";
 import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionViewProps } from "./CollectionViewBase";
 import { CollectionTreeView } from "./CollectionTreeView";
-import { Field, FieldId } from "../../../fields/Field";
+import { Field, FieldId, FieldWaiting } from "../../../fields/Field";
 import { Main } from "../Main";
 
 export enum CollectionViewType {
@@ -86,7 +86,7 @@ export class CollectionView extends React.Component<CollectionViewProps> {
     get collectionViewType(): CollectionViewType {
         let Document = this.props.Document;
         let viewField = Document.GetT(KeyStore.ViewType, NumberField);
-        if (viewField === "<Waiting>") {
+        if (viewField === FieldWaiting) {
             return CollectionViewType.Invalid;
         } else if (viewField) {
             return viewField.Data;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9e34b2b60..fec451b09 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../fields/Document";
-import { Field, Opt } from "../../../fields/Field";
+import { Field, Opt, FieldWaiting } from "../../../fields/Field";
 import { Key } from "../../../fields/Key";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
@@ -315,7 +315,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             return (null);
         }
         let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
-        if (!lkeys || lkeys === "<Waiting>") {
+        if (!lkeys || lkeys === FieldWaiting) {
             return <p>Error loading layout keys</p>;
         }
         var scaling = this.props.ContentScaling();
-- 
cgit v1.2.3-70-g09d2


From 79846f5e85c04435082106724d49b03eea31657f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 21 Mar 2019 00:57:02 -0400
Subject: minor fix to schema dragger and treeview

---
 src/client/views/collections/CollectionSchemaView.tsx | 2 +-
 src/client/views/collections/CollectionTreeView.tsx   | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index c2645ea73..d3ade7ebb 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -189,7 +189,7 @@ export class CollectionSchemaView extends CollectionViewBase {
     @action
     onDividerMove = (e: PointerEvent): void => {
         let nativeWidth = this._mainCont.current!.getBoundingClientRect();
-        this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
+        this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100)));
     }
     @action
     onDividerUp = (e: PointerEvent): void => {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 6edd90215..ec1bf5d0e 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,7 +1,7 @@
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
 import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from "mobx";
+import { action, observable, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../fields/Document";
 import { FieldWaiting } from "../../../fields/Field";
@@ -90,14 +90,14 @@ class TreeView extends React.Component<TreeViewProps> {
         if (children && children !== FieldWaiting) { // add children for a collection
             if (!this._collapsed) {
                 bulletType = BulletType.Collapsible;
-                childElements = <ul key={this.props.document.Id}>
+                childElements = <ul>
                     {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} />)}
                 </ul>
             }
             else bulletType = BulletType.Collapsed;
         }
         return <div className="treeViewItem-container" >
-            <li className="collection-child" key={this.props.document.Id}>
+            <li className="collection-child">
                 {this.renderBullet(bulletType)}
                 {this.renderTitle()}
                 {childElements ? childElements : (null)}
-- 
cgit v1.2.3-70-g09d2


From 08c35582dc71623c2b7300458238b6ba25ff98d7 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 21 Mar 2019 15:30:15 -0400
Subject: fixed performance problem with loading schema with a lot of
 documents.

---
 .../views/collections/CollectionSchemaView.tsx     | 25 +++++++++++++++++-----
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 2 files changed, 21 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b986f394c..34b019244 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -2,7 +2,7 @@ import React = require("react")
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faCog } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace, untracked } from "mobx";
 import { observer } from "mobx-react";
 import Measure from "react-measure";
 import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
@@ -141,6 +141,7 @@ export class CollectionSchemaView extends CollectionViewBase {
         };
     }
 
+    @computed
     get columns() {
         return this.props.Document.GetList<Key>(KeyStore.ColumnsKey, []);
     }
@@ -167,10 +168,19 @@ export class CollectionSchemaView extends CollectionViewBase {
             this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0);
         }
     }
-    findAllDocumentKeys = (): { [id: string]: boolean } => {
+
+    @computed
+    get findAllDocumentKeys(): { [id: string]: boolean } {
         const docs = this.props.Document.GetList<Document>(this.props.fieldKey, []);
         let keys: { [id: string]: boolean } = {}
-        docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false)));
+        if (this._optionsActivated > -1) {
+            // bcz: ugh.  this is untracked since otherwise a large collection of documents will blast the server for all their fields.
+            //  then as each document's fields come back, we update the documents _proxies.  Each time we do this, the whole schema will be
+            //  invalidated and re-rendered.   This workaround will inquire all of the document fields before the options button is clicked.
+            //  then by the time the options button is clicked, all of the fields should be in place.  If a new field is added while this menu
+            //  is displayed (unlikely) it won't show up until something else changes.
+            untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false))));
+        }
         this.columns.forEach(key => keys[key.Id] = true)
         return keys;
     }
@@ -228,13 +238,18 @@ export class CollectionSchemaView extends CollectionViewBase {
         }
     }
 
+    @observable _optionsActivated: number = 0;
+    @action
+    OptionsMenuDown = (e: React.PointerEvent) => {
+        this._optionsActivated++;
+    }
     render() {
         library.add(faCog);
         const columns = this.columns;
         const children = this.props.Document.GetList<Document>(this.props.fieldKey, []);
         const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined;
         //all the keys/columns that will be displayed in the schema
-        const allKeys = this.findAllDocumentKeys();
+        const allKeys = this.findAllDocumentKeys;
         let content = this._selectedIndex == -1 || !selected ? (null) : (
             <Measure onResize={this.setScaling}>
                 {({ measureRef }) =>
@@ -274,7 +289,7 @@ export class CollectionSchemaView extends CollectionViewBase {
                 </div>
             </div>
             }>
-            <button id="schemaOptionsMenuBtn"><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button>
+            <button id="schemaOptionsMenuBtn" onPointerDown={this.OptionsMenuDown}><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button>
         </Flyout>);
 
         return (
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ec1bf5d0e..6cc14ebcb 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -76,7 +76,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 }}
             />);
         return (
-            <div key={this.props.document.Id} className="docContainer" ref={reference} onPointerDown={onItemDown}>
+            <div className="docContainer" ref={reference} onPointerDown={onItemDown}>
                 {editableView(this.props.document.Title)}
                 <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
             </div >)
-- 
cgit v1.2.3-70-g09d2


From 9f585c4f0a8287951f142f99dcc54861cb348728 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 26 Mar 2019 16:09:27 -0400
Subject: treeview scrolling.

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

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 6cc14ebcb..70790af18 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -125,7 +125,7 @@ export class CollectionTreeView extends CollectionViewBase {
             )
 
         return (
-            <div id="body" className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
+            <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
                 <div className="coll-title">
                     <EditableView
                         contents={this.props.Document.Title}
-- 
cgit v1.2.3-70-g09d2


From 5889bf159ee0a0f6567683b2bb8c2475feccf9ec Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 3 Apr 2019 01:38:29 -0400
Subject: Almost compiles

---
 src/Utils.ts                                       | 10 ++++++
 src/client/Server.ts                               |  2 +-
 src/client/documents/Documents.ts                  |  3 +-
 src/client/northstar/dash-nodes/HistogramBox.tsx   | 14 ++++----
 src/client/util/DragManager.ts                     |  2 +-
 src/client/util/Transform.ts                       |  2 +-
 src/client/util/type_decls.d                       |  2 +-
 src/client/views/Main.tsx                          | 10 +++---
 .../views/collections/CollectionBaseView.tsx       | 28 +++++++++++++---
 .../views/collections/CollectionDockingView.tsx    |  9 ++---
 src/client/views/collections/CollectionPDFView.tsx | 36 ++++++++++----------
 .../views/collections/CollectionSchemaView.tsx     | 12 +++++--
 .../views/collections/CollectionTreeView.tsx       |  3 +-
 .../views/collections/CollectionVideoView.tsx      | 39 +++++++++++-----------
 src/client/views/collections/CollectionView.tsx    |  4 +--
 .../views/collections/CollectionViewBase.tsx       |  7 ++--
 .../CollectionFreeFormRemoteCursors.tsx            | 17 ++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 30 +++++++++++------
 src/client/views/nodes/DocumentContentsView.tsx    | 18 +++++++++-
 src/client/views/nodes/DocumentView.tsx            | 37 +++++++++-----------
 src/client/views/nodes/FieldView.tsx               | 17 ++++++----
 src/client/views/nodes/KeyValuePair.tsx            |  4 +++
 src/fields/Field.ts                                |  4 +--
 23 files changed, 181 insertions(+), 129 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index a5d9bd0ca..3eb192eb8 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -86,4 +86,14 @@ export class Utils {
     }
 }
 
+export function returnTrue() {
+    return true;
+}
+
+export function returnFalse() {
+    return false;
+}
+
+export function emptyFunction() { }
+
 export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
\ No newline at end of file
diff --git a/src/client/Server.ts b/src/client/Server.ts
index 37e3c2c0d..c301b04d3 100644
--- a/src/client/Server.ts
+++ b/src/client/Server.ts
@@ -42,7 +42,7 @@ export class Server {
                 reaction(() => {
                     return this.ClientFieldsCached.get(fieldid);
                 }, (field, reaction) => {
-                    if (field !== "<Waiting>") {
+                    if (field !== FieldWaiting) {
                         reaction.dispose()
                         cb(field)
                     }
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0bf275df8..15f980895 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -17,7 +17,8 @@ import { HistogramOperation } from "../northstar/operations/HistogramOperation";
 import { Server } from "../Server";
 import { CollectionPDFView } from "../views/collections/CollectionPDFView";
 import { CollectionVideoView } from "../views/collections/CollectionVideoView";
-import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
+import { CollectionView } from "../views/collections/CollectionView";
+import { CollectionViewType } from "../views/collections/CollectionBaseView";
 import { AudioBox } from "../views/nodes/AudioBox";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { ImageBox } from "../views/nodes/ImageBox";
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index a968def96..4457c9502 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -117,14 +117,14 @@ export class HistogramBox extends React.Component<FieldViewProps> {
 
     activateHistogramOperation(catalog?: Catalog) {
         if (catalog) {
-            this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
+            this.props.Document.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
                 this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty;
                 if (this.HistoOp != HistogramOperation.Empty) {
-                    reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
-                    reaction(() => this.props.doc.GetList(KeyStore.BrushingDocs, []).length,
+                    reaction(() => this.props.Document.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+                    reaction(() => this.props.Document.GetList(KeyStore.BrushingDocs, []).length,
                         () => {
-                            let brushingDocs = this.props.doc.GetList(KeyStore.BrushingDocs, [] as Document[]);
-                            let proto = this.props.doc.GetPrototype() as Document;
+                            let brushingDocs = this.props.Document.GetList(KeyStore.BrushingDocs, [] as Document[]);
+                            let proto = this.props.Document.GetPrototype() as Document;
                             this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => {
                                 brush.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length]);
                                 let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
@@ -139,8 +139,8 @@ export class HistogramBox extends React.Component<FieldViewProps> {
     render() {
         let labelY = this.HistoOp && this.HistoOp.Y ? this.HistoOp.Y.PresentedName : "<...>";
         let labelX = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.PresentedName : "<...>";
-        var h = this.props.isTopMost ? this.PanelHeight : this.props.doc.GetNumber(KeyStore.Height, 0);
-        var w = this.props.isTopMost ? this.PanelWidth : this.props.doc.GetNumber(KeyStore.Width, 0);
+        var h = this.props.isTopMost ? this.PanelHeight : this.props.Document.GetNumber(KeyStore.Height, 0);
+        var w = this.props.isTopMost ? this.PanelWidth : this.props.Document.GetNumber(KeyStore.Width, 0);
         let loff = this.SizeConverter.LeftOffset;
         let toff = this.SizeConverter.TopOffset;
         let roff = this.SizeConverter.RightOffset;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 70b1c9829..53bbae18d 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -110,7 +110,7 @@ export namespace DragManager {
         xOffset?: number;
         yOffset?: number;
         aliasOnDrop?: boolean;
-        removeDocument?: (collectionDrop: CollectionView) => void;
+        moveDocument?: (targetCollection: Document, addDocument: (document: Document) => void) => void;
         [id: string]: any;
     }
     export function StartDocumentDrag(ele: HTMLElement, dragData: DocumentDragData, options?: DragOptions) {
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index 3e1039166..889134e3e 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -3,7 +3,7 @@ export class Transform {
     private _translateY: number = 0;
     private _scale: number = 1;
 
-    static get Identity(): Transform {
+    static Identity(): Transform {
         return new Transform(0, 0, 1);
     }
 
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 4f69053b1..47c3481b2 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -181,7 +181,7 @@ declare class Key extends Field {
     Copy(): Field;
     ToScriptString(): string;
 }
-declare type FIELD_WAITING = "<Waiting>";
+declare type FIELD_WAITING = null;
 declare type Opt<T> = T | undefined;
 declare type FieldValue<T> = Opt<T> | FIELD_WAITING;
 // @ts-ignore
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 82fd1e7c9..bcbe6b987 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -6,7 +6,7 @@ import { Document } from '../../fields/Document';
 import { KeyStore } from '../../fields/KeyStore';
 import "./Main.scss";
 import { MessageStore } from '../../server/Message';
-import { Utils } from '../../Utils';
+import { Utils, returnTrue } from '../../Utils';
 import * as request from 'request'
 import * as rp from 'request-promise'
 import { Documents } from '../documents/Documents';
@@ -212,7 +212,6 @@ export class Main extends React.Component {
         this.workspacesShown = !this.workspacesShown;
     }
 
-    screenToLocalTransform = () => Transform.Identity
     pwidthFunc = () => this.pwidth;
     pheightFunc = () => this.pheight;
     focusDocument = (doc: Document) => { }
@@ -221,15 +220,16 @@ export class Main extends React.Component {
     get content() {
         return !this.mainContainer ? (null) :
             <DocumentView Document={this.mainContainer}
-                AddDocument={undefined}
-                RemoveDocument={undefined}
-                ScreenToLocalTransform={this.screenToLocalTransform}
+                addDocument={undefined}
+                removeDocument={undefined}
+                ScreenToLocalTransform={Transform.Identity}
                 ContentScaling={this.noScaling}
                 PanelWidth={this.pwidthFunc}
                 PanelHeight={this.pheightFunc}
                 isTopMost={true}
                 selectOnLoad={false}
                 focus={this.focusDocument}
+                parentActive={returnTrue}
                 ContainingCollectionView={undefined} />
     }
 
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index c8c840085..c9f640c4a 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -19,16 +19,21 @@ export enum CollectionViewType {
 }
 
 export interface CollectionRenderProps {
-    addDocument: (document: Document, allowDuplicates: boolean) => void;
+    addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
     removeDocument: (document: Document) => boolean;
+    moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => void) => boolean;
     active: () => boolean;
 }
 
 export interface CollectionViewProps extends FieldViewProps {
     onContextMenu?: (e: React.MouseEvent) => void;
-    children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | null;
+    children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null;
+    className?: string;
+    contentRef?: React.Ref<HTMLDivElement>;
 }
 
+export const COLLECTION_BORDER_WIDTH = 1;
+
 export class CollectionBaseView extends React.Component<CollectionViewProps> {
     get collectionViewType(): CollectionViewType {
         let Document = this.props.Document;
@@ -44,7 +49,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     active = (): boolean => {
         var isSelected = this.props.isSelected();
-        var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == self);
+        var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == this);
         var topMost = this.props.isTopMost;
         return isSelected || childSelected || topMost;
     }
@@ -68,7 +73,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
     }
 
     @action.bound
-    addDocument(doc: Document, allowDuplicates: boolean): boolean {
+    addDocument(doc: Document, allowDuplicates: boolean = false): boolean {
         let props = this.props;
         var curPage = props.Document.GetNumber(KeyStore.CurPage, -1);
         doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage));
@@ -123,14 +128,27 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         return false
     }
 
+    @action.bound
+    moveDocument(doc: Document, targetCollection: Document, addDocument: (doc: Document) => void): boolean {
+        if (this.props.Document === targetCollection) {
+            return false;
+        }
+        if (this.removeDocument(doc)) {
+            addDocument(doc);
+            return true;
+        }
+        return false;
+    }
+
     render() {
         const props: CollectionRenderProps = {
             addDocument: this.addDocument,
             removeDocument: this.removeDocument,
+            moveDocument: this.moveDocument,
             active: this.active
         }
         return (
-            <div className="collectionView-cont" onContextMenu={this.props.onContextMenu}>
+            <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
                 {this.props.children(this.collectionViewType, props)}
             </div>
         )
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c67354139..bb34340fb 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -8,12 +8,12 @@ import { Document } from "../../../fields/Document";
 import { KeyStore } from "../../../fields/KeyStore";
 import Measure from "react-measure";
 import { FieldId, Opt, Field } from "../../../fields/Field";
-import { Utils } from "../../../Utils";
+import { Utils, returnTrue } from "../../../Utils";
 import { Server } from "../../Server";
 import { undoBatch } from "../../util/UndoManager";
 import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionDockingView.scss";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView";
 import React = require("react");
 import { SubCollectionViewProps } from "./CollectionViewBase";
 import { ServerUtils } from "../../../server/ServerUtil";
@@ -310,14 +310,15 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         var content =
             <div className="collectionDockingView-content" ref={this._mainCont}>
                 <DocumentView key={this._document.Id} Document={this._document}
-                    AddDocument={undefined}
-                    RemoveDocument={undefined}
+                    addDocument={undefined}
+                    removeDocument={undefined}
                     ContentScaling={this._contentScaling}
                     PanelWidth={this._nativeWidth}
                     PanelHeight={this._nativeHeight}
                     ScreenToLocalTransform={this.ScreenToLocalTransform}
                     isTopMost={true}
                     selectOnLoad={false}
+                    parentActive={returnTrue}
                     focus={(doc: Document) => { }}
                     ContainingCollectionView={undefined} />
             </div>
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 6bb3bacc7..14ed70b8c 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,23 +1,20 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
-import { CollectionView, CollectionViewType } from "./CollectionView";
 import { CollectionViewProps } from "./CollectionViewBase";
 import "./CollectionPDFView.scss"
 import React = require("react");
-import { FieldId } from "../../../fields/Field";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { FieldView } from "../nodes/FieldView";
+import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView";
 
 
 @observer
 export class CollectionPDFView extends React.Component<CollectionViewProps> {
 
     public static LayoutString(fieldKey: string = "DataKey") {
-        return `<${CollectionPDFView.name} Document={Document}
-                    ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
-                    isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
+        return FieldView.LayoutString(CollectionPDFView, fieldKey);
     }
 
     private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); }
@@ -35,26 +32,27 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
         );
     }
 
-    // "inherited" CollectionView API starts here...
-    @observable
-    public SelectedDocs: FieldId[] = []
-    public active: () => boolean = () => CollectionView.Active(this);
-
-    addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); }
-    removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
-
-    specificContextMenu = (e: React.MouseEvent): void => {
+    onContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.Document.Id != "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             ContextMenu.Instance.addItem({ description: "PDFOptions", event: () => { } });
         }
     }
 
-    render() {
+    private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
+        let props = { ...renderProps, ...this.props };
         return (
-            <div className="collectionPdfView-cont" onContextMenu={this.specificContextMenu}>
-                <CollectionFreeFormView {...CollectionView.SubViewProps(this)} />
+            <>
+                <CollectionFreeFormView {...props} />
                 {this.props.isSelected() ? this.uIButtons : (null)}
-            </div>
+            </>
+        )
+    }
+
+    render() {
+        return (
+            <CollectionBaseView {...this.props} className="collectionPdfView-cont" onContextMenu={this.onContextMenu}>
+                {this.subView}
+            </CollectionBaseView>
         )
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 0c7e28691..0eacd38b0 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -22,9 +22,11 @@ import { EditableView } from "../EditableView";
 import { DocumentView } from "../nodes/DocumentView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import "./CollectionSchemaView.scss";
-import { CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { CollectionView } from "./CollectionView";
 import { CollectionViewBase } from "./CollectionViewBase";
 import { TextField } from "../../../fields/TextField";
+import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView";
+import { emptyFunction, returnFalse } from "../../../Utils";
 
 
 // bcz: need to add drag and drop of rows and columns.  This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
@@ -79,6 +81,9 @@ export class CollectionSchemaView extends CollectionViewBase {
             select: () => { },
             isTopMost: false,
             selectOnLoad: false,
+            ScreenToLocalTransform: Transform.Identity,
+            focus: emptyFunction,
+            active: returnFalse
         }
         let contents = (
             <FieldView {...props} />
@@ -295,15 +300,16 @@ export class CollectionSchemaView extends CollectionViewBase {
                 {({ measureRef }) =>
                     <div className="collectionSchemaView-content" ref={measureRef}>
                         {doc instanceof Document ? <DocumentView Document={doc}
-                            AddDocument={this.props.addDocument} RemoveDocument={this.props.removeDocument}
+                            addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
                             isTopMost={false}
                             selectOnLoad={false}
                             ScreenToLocalTransform={this.getPreviewTransform}
                             ContentScaling={this.getContentScaling}
                             PanelWidth={this.getPanelWidth}
                             PanelHeight={this.getPanelHeight}
-                            ContainingCollectionView={this.props.CollectionView}
+                            ContainingCollectionView={undefined}
                             focus={this.focusDocument}
+                            parentActive={this.props.active}
                         /> : null}
                         <input value={this.previewScript} onChange={this.onPreviewScriptChange}
                             style={{ position: 'absolute', bottom: '0px' }} />
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 70790af18..6f9ba40d2 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -10,9 +10,10 @@ import { ListField } from "../../../fields/ListField";
 import { setupDrag } from "../../util/DragManager";
 import { EditableView } from "../EditableView";
 import "./CollectionTreeView.scss";
-import { CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { CollectionView } from "./CollectionView";
 import { CollectionViewBase } from "./CollectionViewBase";
 import React = require("react")
+import { COLLECTION_BORDER_WIDTH } from './CollectionBaseView';
 
 
 export interface TreeViewProps {
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 1f0143d87..7cb461b4d 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -3,12 +3,14 @@ import { observer } from "mobx-react";
 import { Document } from "../../../fields/Document";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
-import { CollectionView, CollectionViewType } from "./CollectionView";
+import { CollectionView } from "./CollectionView";
+import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
 import { CollectionViewProps } from "./CollectionViewBase";
 import React = require("react");
 import { FieldId } from "../../../fields/Field";
 import "./CollectionVideoView.scss"
-import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { FieldView } from "../nodes/FieldView";
 
 
 @observer
@@ -20,9 +22,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
     @observable _isPlaying: boolean = false;
 
     public static LayoutString(fieldKey: string = "DataKey") {
-        return `<${CollectionVideoView.name} Document={Document}
-                    ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
-                    isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
+        return FieldView.LayoutString(CollectionVideoView, fieldKey);
     }
     private get uIButtons() {
         let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]);
@@ -102,26 +102,27 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
 
     }
 
-    // "inherited" CollectionView API starts here...
-
-    @observable
-    public SelectedDocs: FieldId[] = []
-    public active: () => boolean = () => CollectionView.Active(this);
-
-    addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); }
-    removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
-
-    specificContextMenu = (e: React.MouseEvent): void => {
+    onContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.Document.Id != "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             ContextMenu.Instance.addItem({ description: "VideoOptions", event: () => { } });
         }
     }
 
+    private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
+        let props = { ...renderProps, ...this.props };
+        return (
+            <>
+                <CollectionFreeFormView {...props} />
+                {this.props.isSelected() ? this.uIButtons : (null)}
+            </>
+        )
+    }
+
     render() {
         trace();
-        return (<div className="collectionVideoView-cont" ref={this.mainCont} onContextMenu={this.specificContextMenu}>
-            <CollectionFreeFormView {...CollectionView.SubViewProps(this)} />
-            {this.props.isSelected() ? this.uIButtons : (null)}
-        </div>)
+        return (
+            <CollectionBaseView {...this.props} className="collectionVideoView-cont" contentRef={this.mainCont} onContextMenu={this.onContextMenu}>
+                {this.subView}
+            </CollectionBaseView>)
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 51cc99595..d440dcff9 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -12,7 +12,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
     public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(CollectionView, fieldStr) }
 
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
-        let props = { ...renderProps, ...this.props };
+        let props = { ...this.props, ...renderProps };
         switch (type) {
             case CollectionViewType.Freeform: return (<CollectionFreeFormView {...props} />)
             case CollectionViewType.Schema: return (<CollectionSchemaView {...props} />)
@@ -34,7 +34,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
         return (
             <CollectionBaseView {...this.props} onContextMenu={this.onContextMenu}>
                 {this.SubView}
-            </CollectionBaseView> >
+            </CollectionBaseView>
         )
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index ca4f3b12f..4a0b2312e 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -20,6 +20,9 @@ import { Server } from "../../Server";
 import { FieldViewProps } from "../nodes/FieldView";
 
 export interface CollectionViewProps extends FieldViewProps {
+    addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
+    removeDocument: (document: Document) => boolean;
+    moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => void) => boolean;
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
@@ -72,8 +75,8 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
                     de.data.draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null));
             }
             let added = this.props.addDocument(de.data.droppedDocument, false);
-            if (added && de.data.removeDocument && !de.data.aliasOnDrop) {
-                de.data.removeDocument(this.props.CollectionView);
+            if (added && de.data.moveDocument && !de.data.aliasOnDrop) {
+                de.data.moveDocument(this.props.Document, this.props.addDocument);
             }
             e.stopPropagation();
             return added;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 19382e66f..f664f4e65 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -1,21 +1,8 @@
-import { action, computed, observable } from "mobx";
+import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { Document } from "../../../../fields/Document";
-import { FieldWaiting } from "../../../../fields/Field";
 import { KeyStore } from "../../../../fields/KeyStore";
-import { TextField } from "../../../../fields/TextField";
-import { DragManager } from "../../../util/DragManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
-import { InkingCanvas } from "../../InkingCanvas";
-import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocumentContentsView } from "../../nodes/DocumentContentsView";
-import { DocumentViewProps } from "../../nodes/DocumentView";
-import { COLLECTION_BORDER_WIDTH } from "../CollectionView";
-import { CollectionViewBase, CollectionViewProps, CursorEntry } from "../CollectionViewBase";
-import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
+import { CollectionViewProps, CursorEntry } from "../CollectionViewBase";
 import "./CollectionFreeFormView.scss";
-import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 51ab85b71..144d121db 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -11,7 +11,7 @@ import { InkingCanvas } from "../../InkingCanvas";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentContentsView } from "../../nodes/DocumentContentsView";
 import { DocumentViewProps } from "../../nodes/DocumentView";
-import { COLLECTION_BORDER_WIDTH } from "../CollectionView";
+import { COLLECTION_BORDER_WIDTH } from "../CollectionBaseView";
 import { CollectionViewBase } from "../CollectionViewBase";
 import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
 import "./CollectionFreeFormView.scss";
@@ -20,6 +20,8 @@ import React = require("react");
 import v5 = require("uuid/v5");
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import { PreviewCursor } from "./PreviewCursor";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { SelectionManager } from "../../../util/SelectionManager";
 
 @observer
 export class CollectionFreeFormView extends CollectionViewBase {
@@ -39,8 +41,13 @@ export class CollectionFreeFormView extends CollectionViewBase {
     }
 
     public selectDocuments = (docs: Document[]) => {
-        this.props.CollectionView.SelectedDocs.length = 0;
-        docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id));
+        SelectionManager.DeselectAll;
+        docs.map(doc => {
+            const dv = DocumentManager.Instance.getDocumentView(doc);
+            if (dv) {
+                SelectionManager.SelectDoc(dv, true);
+            }
+        })
     }
 
     public getActiveDocuments = () => {
@@ -59,6 +66,8 @@ export class CollectionFreeFormView extends CollectionViewBase {
     @observable private _lastX: number = 0;
     @observable private _lastY: number = 0;
 
+    private outerElement?: HTMLDivElement;
+
     @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
     @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
     @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
@@ -66,8 +75,8 @@ export class CollectionFreeFormView extends CollectionViewBase {
     @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
     @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
     @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
-    @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; }  // shift so pan position is at center of window for non-overlay collections
-    @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
+    @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) && this.outerElement ? this.outerElement.clientWidth / 2 : 0; }  // shift so pan position is at center of window for non-overlay collections
+    @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) && this.outerElement ? this.outerElement.clientHeight / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
 
     @undoBatch
     @action
@@ -227,16 +236,17 @@ export class CollectionFreeFormView extends CollectionViewBase {
     getDocumentViewProps(document: Document): DocumentViewProps {
         return {
             Document: document,
-            AddDocument: this.props.addDocument,
-            RemoveDocument: this.props.removeDocument,
+            addDocument: this.props.addDocument,
+            removeDocument: this.props.removeDocument,
             ScreenToLocalTransform: this.getTransform,
             isTopMost: false,
             selectOnLoad: document.Id == this._selectOnLoaded,
             PanelWidth: document.Width,
             PanelHeight: document.Height,
             ContentScaling: this.noScaling,
-            ContainingCollectionView: this.props.CollectionView,
-            focus: this.focusDocument
+            ContainingCollectionView: undefined,
+            focus: this.focusDocument,
+            parentActive: this.props.active,
         }
     }
 
@@ -266,7 +276,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
 
     getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
     getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
-    getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY);
+    getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.scale).translate(this.panX, this.panY);
     noScaling = () => 1;
     childViews = () => this.views;
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index a5e4b1a17..2574b374a 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -44,7 +44,18 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
 
     CreateBindings(): JsxBindings {
-        let { Document, isSelected, select, isTopMost, selectOnLoad } = this.props;
+        let
+            {
+                Document,
+                isSelected,
+                select,
+                isTopMost,
+                selectOnLoad,
+                ScreenToLocalTransform,
+                addDocument,
+                removeDocument,
+                parentActive: active
+            } = this.props;
         let bindings: JsxBindings = {
             props: {
                 Document,
@@ -52,6 +63,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                 select,
                 isTopMost,
                 selectOnLoad,
+                ScreenToLocalTransform,
+                active,
+                addDocument,
+                removeDocument,
+                focus,
             }
         };
         for (const key of this.layoutKeys) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6530dafc6..545344024 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -13,19 +13,21 @@ import { DragManager } from "../../util/DragManager";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { CollectionView } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
 import { ServerUtils } from "../../../server/ServerUtil";
+import { CollectionViewType } from "../collections/CollectionBaseView";
 
 
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView>;
     Document: Document;
-    AddDocument?: (doc: Document, allowDuplicates: boolean) => boolean;
-    RemoveDocument?: (doc: Document) => boolean;
+    addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean;
+    removeDocument?: (doc: Document) => boolean;
+    moveDocument?: (doc: Document, targetCollection: Document, addDocument: (document: Document) => void) => boolean;
     ScreenToLocalTransform: () => Transform;
     isTopMost: boolean;
     ContentScaling: () => number;
@@ -33,6 +35,7 @@ export interface DocumentViewProps {
     PanelHeight: () => number;
     focus: (doc: Document) => void;
     selectOnLoad: boolean;
+    parentActive: () => boolean;
 }
 export interface JsxArgs extends DocumentViewProps {
     Keys: { [name: string]: Key }
@@ -83,9 +86,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     private _mainCont = React.createRef<HTMLDivElement>();
     private _downX: number = 0;
     private _downY: number = 0;
-    private _reactionDisposer: Opt<IReactionDisposer>;
-    @computed get active(): boolean { return SelectionManager.IsSelected(this) || !this.props.ContainingCollectionView || this.props.ContainingCollectionView.active(); }
-    @computed get topMost(): boolean { return !this.props.ContainingCollectionView || this.props.ContainingCollectionView.collectionViewType == CollectionViewType.Docking; }
+    @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
+    @computed get topMost(): boolean { return this.props.isTopMost; }
     @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<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>()); }
@@ -119,12 +121,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         runInAction(() => {
             DocumentManager.Instance.DocumentViews.push(this);
         })
-        this._reactionDisposer = reaction(
-            () => this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.slice(),
-            () => {
-                if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.indexOf(this.props.Document.Id) != -1)
-                    SelectionManager.SelectDoc(this, true);
-            });
     }
 
     componentDidUpdate() {
@@ -144,9 +140,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
 
         })
-        if (this._reactionDisposer) {
-            this._reactionDisposer();
-        }
     }
 
     startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) {
@@ -156,9 +149,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             dragData.aliasOnDrop = dropAliasOfDraggedDoc;
             dragData.xOffset = x - left;
             dragData.yOffset = y - top;
-            dragData.removeDocument = (dropCollectionView: CollectionView) => {
-                if (this.props.RemoveDocument && this.props.ContainingCollectionView !== dropCollectionView) {
-                    this.props.RemoveDocument(this.props.Document);
+            dragData.moveDocument = (targetCollection: Document, addDocument: (document: Document) => void) => {
+                if (this.props.moveDocument) {
+                    this.props.moveDocument(this.props.Document, targetCollection, addDocument)
                 }
             }
             DragManager.StartDocumentDrag(this._mainCont.current, dragData, {
@@ -197,14 +190,14 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 
     deleteClicked = (): void => {
-        if (this.props.RemoveDocument) {
-            this.props.RemoveDocument(this.props.Document);
+        if (this.props.removeDocument) {
+            this.props.removeDocument(this.props.Document);
         }
     }
 
     fieldsClicked = (e: React.MouseEvent): void => {
-        if (this.props.AddDocument) {
-            this.props.AddDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
+        if (this.props.addDocument) {
+            this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
         }
     }
     fullScreenClicked = (e: React.MouseEvent): void => {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 2a7d3175f..57af55f9f 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -19,6 +19,7 @@ import { ListField } from "../../../fields/ListField";
 import { DocumentContentsView } from "./DocumentContentsView";
 import { Transform } from "../../util/Transform";
 import { KeyStore } from "../../../fields/KeyStore";
+import { returnFalse } from "../../../Utils";
 
 
 //
@@ -33,8 +34,9 @@ export interface FieldViewProps {
     select: (isCtrlPressed: boolean) => void;
     isTopMost: boolean;
     selectOnLoad: boolean;
-    addDocument: (document: Document, allowDuplicates: boolean) => boolean;
-    removeDocument: (document: Document) => boolean;
+    addDocument?: (document: Document, allowDuplicates?: boolean) => boolean;
+    removeDocument?: (document: Document) => boolean;
+    moveDocument?: (document: Document, targetCollection: Document, addDocument: (document: Document) => void) => boolean;
     ScreenToLocalTransform: () => Transform;
     active: () => boolean;
     focus: (doc: Document) => void;
@@ -73,19 +75,20 @@ export class FieldView extends React.Component<FieldViewProps> {
         }
         else if (field instanceof Document) {
             return (<DocumentContentsView Document={field}
-                AddDocument={undefined}
-                RemoveDocument={undefined}
-                ScreenToLocalTransform={() => Transform.Identity}
+                addDocument={undefined}
+                removeDocument={undefined}
+                ScreenToLocalTransform={Transform.Identity}
                 ContentScaling={() => 1}
                 PanelWidth={() => 100}
                 PanelHeight={() => 100}
-                isTopMost={true}
+                isTopMost={true} //TODO Why is this top most?
                 selectOnLoad={false}
                 focus={() => { }}
                 isSelected={() => false}
                 select={() => false}
                 layoutKey={KeyStore.Layout}
-                ContainingCollectionView={undefined} />)
+                ContainingCollectionView={undefined}
+                parentActive={this.props.active} />)
         }
         else if (field instanceof ListField) {
             return (<div>
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 2fc11328f..e178cbc8d 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -11,6 +11,7 @@ import { Key } from '../../../fields/Key';
 import { Server } from "../../Server"
 import { EditableView } from "../EditableView";
 import { CompileScript, ToField } from "../../util/Scripting";
+import { Transform } from '../../util/Transform';
 
 // Represents one row in a key value plane
 
@@ -49,6 +50,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             select: () => { },
             isTopMost: false,
             selectOnLoad: false,
+            active: () => false,
+            ScreenToLocalTransform: Transform.Identity,
+            focus: () => { },
         }
         let contents = (
             <FieldView {...props} />
diff --git a/src/fields/Field.ts b/src/fields/Field.ts
index d48509a47..0d0e56f77 100644
--- a/src/fields/Field.ts
+++ b/src/fields/Field.ts
@@ -12,8 +12,8 @@ export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T
     return undefined;
 }
 
-export const FieldWaiting: FIELD_WAITING = "<Waiting>";
-export type FIELD_WAITING = "<Waiting>";
+export const FieldWaiting: FIELD_WAITING = null;
+export type FIELD_WAITING = null;
 export type FieldId = string;
 export type Opt<T> = T | undefined;
 export type FieldValue<T> = Opt<T> | FIELD_WAITING;
-- 
cgit v1.2.3-70-g09d2


From 318dacd846ab06358e26ca49e711e06434287d16 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Thu, 4 Apr 2019 19:37:10 -0400
Subject: Most stuff seems to be working

---
 src/client/util/DragManager.ts                        |  9 +++++----
 src/client/views/collections/CollectionBaseView.tsx   | 16 +++++++++-------
 src/client/views/collections/CollectionSchemaView.tsx | 18 +++++++++---------
 src/client/views/collections/CollectionTreeView.tsx   | 19 +++++++++++++++----
 src/client/views/collections/CollectionView.tsx       |  7 ++++++-
 src/client/views/collections/CollectionViewBase.tsx   |  2 ++
 .../collectionFreeForm/CollectionFreeFormView.tsx     |  1 +
 src/client/views/nodes/DocumentView.tsx               |  2 +-
 8 files changed, 48 insertions(+), 26 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index ff778302b..d8e70a8b4 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -6,9 +6,9 @@ import { ImageField } from "../../fields/ImageField";
 import { KeyStore } from "../../fields/KeyStore";
 import { CollectionView } from "../views/collections/CollectionView";
 import { DocumentView } from "../views/nodes/DocumentView";
-import { emptyFunction } from "../../Utils";
+import { returnFalse } from "../../Utils";
 
-export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, removeFunc: (containingCollection: CollectionView) => void = () => { }) {
+export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction) {
     let onRowMove = action((e: PointerEvent): void => {
         e.stopPropagation();
         e.preventDefault();
@@ -16,7 +16,7 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
         document.removeEventListener("pointermove", onRowMove);
         document.removeEventListener('pointerup', onRowUp);
         var dragData = new DragManager.DocumentDragData([docFunc()]);
-        dragData.removeDocument = removeFunc;
+        dragData.moveDocument = moveFunc;
         DragManager.StartDocumentDrag([_reference.current!], dragData);
     });
     let onRowUp = action((e: PointerEvent): void => {
@@ -101,6 +101,7 @@ export namespace DragManager {
         };
     }
 
+    export type MoveFunction = (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
     export class DocumentDragData {
         constructor(dragDoc: Document[]) {
             this.draggedDocuments = dragDoc;
@@ -111,7 +112,7 @@ export namespace DragManager {
         xOffset?: number;
         yOffset?: number;
         aliasOnDrop?: boolean;
-        moveDocument?: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+        moveDocument?: MoveFunction;
         [id: string]: any;
     }
 
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 9b68ee06c..ee752a2ca 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -9,6 +9,7 @@ import { Document } from '../../../fields/Document';
 import { ListField } from '../../../fields/ListField';
 import { action } from 'mobx';
 import { Transform } from '../../util/Transform';
+import { observer } from 'mobx-react';
 
 export enum CollectionViewType {
     Invalid,
@@ -35,6 +36,7 @@ export interface CollectionViewProps extends FieldViewProps {
 
 export const COLLECTION_BORDER_WIDTH = 1;
 
+@observer
 export class CollectionBaseView extends React.Component<CollectionViewProps> {
     get collectionViewType(): CollectionViewType {
         let Document = this.props.Document;
@@ -72,8 +74,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             if (this.createsCycle(annots[i], containerDocument))
                 return true;
         }
-        for (let containerProto: FieldValue<Document> = containerDocument; containerProto && containerProto != FieldWaiting; containerProto = containerProto.GetPrototype()) {
-            if (containerProto.Id == documentToAdd.Id)
+        for (let containerProto: FieldValue<Document> = containerDocument; containerProto && containerProto !== FieldWaiting; containerProto = containerProto.GetPrototype()) {
+            if (containerProto.Id === documentToAdd.Id)
                 return true;
         }
         return false;
@@ -91,14 +93,14 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             //TODO This won't create the field if it doesn't already exist
             const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
             if (!this.createsCycle(doc, props.Document)) {
-                if (!value.some(v => v.Id == doc.Id) || allowDuplicates)
+                if (!value.some(v => v.Id === doc.Id) || allowDuplicates)
                     value.push(doc);
             }
             else
                 return false;
         } else {
             let proto = props.Document.GetPrototype();
-            if (!proto || proto == FieldWaiting || !this.createsCycle(proto, doc)) {
+            if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) {
                 props.Document.SetOnPrototype(props.fieldKey, new ListField([doc]));
             }
             else
@@ -114,13 +116,13 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
         let index = -1;
         for (let i = 0; i < value.length; i++) {
-            if (value[i].Id == doc.Id) {
+            if (value[i].Id === doc.Id) {
                 index = i;
                 break;
             }
         }
         doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => {
-            if (annotationOn == props.Document) {
+            if (annotationOn === props.Document) {
                 doc.Set(KeyStore.AnnotationOn, undefined, true);
             }
         })
@@ -138,7 +140,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
     @action.bound
     moveDocument(doc: Document, targetCollection: Document, addDocument: (doc: Document) => boolean): boolean {
         if (this.props.Document === targetCollection) {
-            return false;
+            return true;
         }
         if (this.removeDocument(doc)) {
             return addDocument(doc);
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index ced46cc25..9dade94f1 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -90,7 +90,7 @@ export class CollectionSchemaView extends CollectionViewBase {
             <FieldView {...props} />
         )
         let reference = React.createRef<HTMLDivElement>();
-        let onItemDown = setupDrag(reference, () => props.Document, (containingCollection: CollectionView) => this.props.removeDocument(props.Document));
+        let onItemDown = setupDrag(reference, () => props.Document, this.props.moveDocument);
         let applyToDoc = (doc: Document, value: string) => {
             let script = CompileScript(value, { this: doc }, true);
             if (!script.compiled) {
@@ -150,7 +150,7 @@ export class CollectionSchemaView extends CollectionViewBase {
                 }
             }),
             style: {
-                background: rowInfo.index == this._selectedIndex ? "lightGray" : "white",
+                background: rowInfo.index === this._selectedIndex ? "lightGray" : "white",
                 //color: rowInfo.index == this._selectedIndex ? "white" : "black"
             }
         };
@@ -179,8 +179,8 @@ export class CollectionSchemaView extends CollectionViewBase {
     @action
     toggleExpander = (event: React.ChangeEvent<HTMLInputElement>) => {
         this._startSplitPercent = this.splitPercentage;
-        if (this._startSplitPercent == this.splitPercentage) {
-            this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0);
+        if (this._startSplitPercent === this.splitPercentage) {
+            this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0);
         }
     }
 
@@ -209,8 +209,8 @@ export class CollectionSchemaView extends CollectionViewBase {
     onDividerUp = (e: PointerEvent): void => {
         document.removeEventListener("pointermove", this.onDividerMove);
         document.removeEventListener('pointerup', this.onDividerUp);
-        if (this._startSplitPercent == this.splitPercentage) {
-            this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0);
+        if (this._startSplitPercent === this.splitPercentage) {
+            this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0);
         }
     }
     onDividerDown = (e: React.PointerEvent) => {
@@ -290,7 +290,7 @@ export class CollectionSchemaView extends CollectionViewBase {
         let doc: any = selected ? selected.Get(new Key(this.previewScript)) : undefined;
 
         // let doc = CompileScript(this.previewScript, { this: selected }, true)();
-        let content = this._selectedIndex == -1 || !selected ? (null) : (
+        let content = this._selectedIndex === -1 || !selected ? (null) : (
             <Measure onResize={this.setScaling}>
                 {({ measureRef }) =>
                     <div className="collectionSchemaView-content" ref={measureRef}>
@@ -313,7 +313,7 @@ export class CollectionSchemaView extends CollectionViewBase {
                 }
             </Measure>
         )
-        let dividerDragger = this.splitPercentage == 0 ? (null) :
+        let dividerDragger = this.splitPercentage === 0 ? (null) :
             <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />
 
         //options button and menu
@@ -323,7 +323,7 @@ export class CollectionSchemaView extends CollectionViewBase {
                 <div id="schema-options-header"><h5><b>Options</b></h5></div>
                 <div id="options-flyout-div">
                     <h6 className="schema-options-subHeader">Preview Window</h6>
-                    <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage != 0} onChange={this.toggleExpander} />  Show Preview </div>
+                    <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} />  Show Preview </div>
                     <h6 className="schema-options-subHeader" >Displayed Columns</h6>
                     <ul id="schema-col-checklist" >
                         {Array.from(Object.keys(allKeys)).map(item =>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 6f9ba40d2..32d74164d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -7,7 +7,7 @@ import { Document } from "../../../fields/Document";
 import { FieldWaiting } from "../../../fields/Field";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
-import { setupDrag } from "../../util/DragManager";
+import { setupDrag, DragManager } from "../../util/DragManager";
 import { EditableView } from "../EditableView";
 import "./CollectionTreeView.scss";
 import { CollectionView } from "./CollectionView";
@@ -19,6 +19,7 @@ import { COLLECTION_BORDER_WIDTH } from './CollectionBaseView';
 export interface TreeViewProps {
     document: Document;
     deleteDoc: (doc: Document) => void;
+    moveDocument: DragManager.MoveFunction;
 }
 
 export enum BulletType {
@@ -49,6 +50,16 @@ class TreeView extends React.Component<TreeViewProps> {
         }
     }
 
+    @action
+    move: DragManager.MoveFunction = (document, target, addDoc) => {
+        if (this.props.document === target) {
+            return true;
+        }
+        //TODO This should check if it was removed
+        this.remove(document)
+        return addDoc(document);
+    }
+
     renderBullet(type: BulletType) {
         let onClicked = action(() => this._collapsed = !this._collapsed);
         let bullet: IconProp | undefined = undefined;
@@ -64,7 +75,7 @@ class TreeView extends React.Component<TreeViewProps> {
      */
     renderTitle() {
         let reference = React.createRef<HTMLDivElement>();
-        let onItemDown = setupDrag(reference, () => this.props.document, (containingCollection: CollectionView) => this.props.deleteDoc(this.props.document));
+        let onItemDown = setupDrag(reference, () => this.props.document, this.props.moveDocument);
         let editableView = (titleString: string) =>
             (<EditableView
                 display={"inline"}
@@ -92,7 +103,7 @@ class TreeView extends React.Component<TreeViewProps> {
             if (!this._collapsed) {
                 bulletType = BulletType.Collapsible;
                 childElements = <ul>
-                    {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} />)}
+                    {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} moveDocument={this.move} />)}
                 </ul>
             }
             else bulletType = BulletType.Collapsed;
@@ -122,7 +133,7 @@ export class CollectionTreeView extends CollectionViewBase {
         var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
         let childrenElement = !children || children === FieldWaiting ? (null) :
             (children.Data.map(value =>
-                <TreeView document={value} key={value.Id} deleteDoc={this.remove} />)
+                <TreeView document={value} key={value.Id} deleteDoc={this.remove} moveDocument={this.props.moveDocument} />)
             )
 
         return (
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index d440dcff9..5b4caf58d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,16 +8,21 @@ import { CollectionTreeView } from './CollectionTreeView';
 import { ContextMenu } from '../ContextMenu';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { KeyStore } from '../../../fields/KeyStore';
+import { observer } from 'mobx-react';
+
+@observer
 export class CollectionView extends React.Component<FieldViewProps> {
     public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(CollectionView, fieldStr) }
 
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         let props = { ...this.props, ...renderProps };
         switch (type) {
-            case CollectionViewType.Freeform: return (<CollectionFreeFormView {...props} />)
             case CollectionViewType.Schema: return (<CollectionSchemaView {...props} />)
             case CollectionViewType.Docking: return (<CollectionDockingView {...props} />)
             case CollectionViewType.Tree: return (<CollectionTreeView {...props} />)
+            case CollectionViewType.Freeform:
+            default:
+                return (<CollectionFreeFormView {...props} />)
         }
         return (null);
     }
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index 2b858789f..147e5fd6b 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -80,6 +80,8 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
             } else if (de.data.moveDocument) {
                 const move = de.data.moveDocument;
                 added = de.data.droppedDocuments.reduce((added, d) => added || move(d, this.props.Document, this.props.addDocument), false)
+            } else {
+                added = de.data.droppedDocuments.reduce((added, d) => added || this.props.addDocument(d), false)
             }
             e.stopPropagation();
             return added;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f03205739..4aad06607 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -245,6 +245,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
             Document: document,
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
+            moveDocument: this.props.moveDocument,
             ScreenToLocalTransform: this.getTransform,
             isTopMost: false,
             selectOnLoad: document.Id === this._selectOnLoaded,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3b146654c..2a9b3b9ac 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -27,7 +27,7 @@ export interface DocumentViewProps {
     Document: Document;
     addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean;
     removeDocument?: (doc: Document) => boolean;
-    moveDocument?: (doc: Document, targetCollection: Document, addDocument: (document: Document) => void) => boolean;
+    moveDocument?: (doc: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
     isTopMost: boolean;
     ContentScaling: () => number;
-- 
cgit v1.2.3-70-g09d2


From 3a9f6df918ad45e55b0c6a540cb566aff4940288 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 8 Apr 2019 12:49:39 -0400
Subject: added copyOnDrag stuff.

---
 src/client/documents/Documents.ts                  |   2 +
 src/client/northstar/dash-fields/HistogramField.ts |   2 +-
 .../northstar/operations/HistogramOperation.ts     |   4 +
 src/client/util/DragManager.ts                     |  14 +--
 src/client/views/Main.tsx                          |  64 +++++--------
 .../views/collections/CollectionTreeView.tsx       |  11 ++-
 .../views/collections/CollectionViewBase.tsx       |   4 +-
 src/fields/Document.ts                             |  30 +++++-
 src/fields/KeyStore.ts                             | 105 ++++++++++++---------
 9 files changed, 133 insertions(+), 103 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1f0744782..7d2f9cde1 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -43,6 +43,7 @@ export interface DocumentOptions {
     layoutKeys?: Key[];
     viewType?: number;
     backgroundColor?: string;
+    copyDraggedItems?: boolean;
 }
 
 export namespace Documents {
@@ -86,6 +87,7 @@ export namespace Documents {
         if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); }
         if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
         if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
+        if (options.copyDraggedItems !== undefined) { doc.SetBoolean(KeyStore.CopyDraggedItems, options.copyDraggedItems); }
         return doc;
     }
 
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index 90be70b80..48731f52d 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -27,7 +27,7 @@ export class HistogramField extends BasicField<HistogramOperation> {
     }
 
     Copy(): Field {
-        return new HistogramField(this.Data);
+        return new HistogramField(this.Data.Copy());
     }
 
     ToScriptString(): string {
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index e63de1632..840520235 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -42,6 +42,10 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
         this.SchemaName = schemaName;
     }
 
+    Copy(): HistogramOperation {
+        return new HistogramOperation(this.SchemaName, this.X, this.Y, this.V, this.Normalization);
+    }
+
     Equals(other: Object): boolean {
         throw new Error("Method not implemented.");
     }
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 043932de5..c1919093e 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -10,7 +10,8 @@ import { DocumentView } from "../views/nodes/DocumentView";
 export function setupDrag(
     _reference: React.RefObject<HTMLDivElement>,
     docFunc: () => Document,
-    removeFunc: (containingCollection: CollectionView) => void = () => { }
+    removeFunc: (containingCollection: CollectionView) => void = () => { },
+    copyOnDrop: boolean = false
 ) {
     let onRowMove = action(
         (e: PointerEvent): void => {
@@ -20,6 +21,7 @@ export function setupDrag(
             document.removeEventListener("pointermove", onRowMove);
             document.removeEventListener("pointerup", onRowUp);
             var dragData = new DragManager.DocumentDragData([docFunc()]);
+            dragData.copyOnDrop = copyOnDrop;
             dragData.removeDocument = removeFunc;
             DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
         }
@@ -125,6 +127,7 @@ export namespace DragManager {
         xOffset?: number;
         yOffset?: number;
         aliasOnDrop?: boolean;
+        copyOnDrop?: boolean;
         removeDocument?: (collectionDrop: CollectionView) => void;
         [id: string]: any;
     }
@@ -136,15 +139,12 @@ export namespace DragManager {
         downY: number,
         options?: DragOptions
     ) {
-        StartDrag(
-            eles,
-            dragData,
-            downX, downY,
-            options,
+        StartDrag(eles, dragData, downX, downY, options,
             (dropData: { [id: string]: any }) =>
                 (dropData.droppedDocuments = dragData.aliasOnDrop
                     ? dragData.draggedDocuments.map(d => d.CreateAlias())
-                    : dragData.draggedDocuments)
+                    : dragData.copyOnDrop ? dragData.draggedDocuments.map(d => d.Copy(true) as Document) :
+                        dragData.draggedDocuments)
         );
     }
 
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 237eb3b6e..b4ab9e21d 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,57 +1,41 @@
-import { action, configure, observable, runInAction, trace, computed, reaction } from 'mobx';
+import { IconName, library } from '@fortawesome/fontawesome-svg-core';
+import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, configure, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
 import "normalize.css";
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
+import Measure from 'react-measure';
+import * as request from 'request';
 import { Document } from '../../fields/Document';
+import { Field, FieldWaiting, Opt } from '../../fields/Field';
 import { KeyStore } from '../../fields/KeyStore';
-import "./Main.scss";
+import { ListField } from '../../fields/ListField';
+import { WorkspacesMenu } from '../../server/authentication/controllers/WorkspacesMenu';
+import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { MessageStore } from '../../server/Message';
+import { RouteStore } from '../../server/RouteStore';
+import { ServerUtils } from '../../server/ServerUtil';
 import { Utils } from '../../Utils';
-import * as request from 'request'
-import * as rp from 'request-promise'
 import { Documents } from '../documents/Documents';
+import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
+import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
+import { Gateway, Settings } from '../northstar/manager/Gateway';
+import { AggregateFunction, Catalog } from '../northstar/model/idea/idea';
+import '../northstar/model/ModelExtensions';
+import { HistogramOperation } from '../northstar/operations/HistogramOperation';
+import '../northstar/utils/Extensions';
 import { Server } from '../Server';
 import { setupDrag } from '../util/DragManager';
 import { Transform } from '../util/Transform';
-import { UndoManager, undoBatch } from '../util/UndoManager';
-import { WorkspacesMenu } from '../../server/authentication/controllers/WorkspacesMenu';
+import { UndoManager } from '../util/UndoManager';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { ContextMenu } from './ContextMenu';
 import { DocumentDecorations } from './DocumentDecorations';
-import { DocumentView } from './nodes/DocumentView';
-import "./Main.scss";
-import { observer } from 'mobx-react';
 import { InkingControl } from './InkingControl';
-import { RouteStore } from '../../server/RouteStore';
-import { library, IconName } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faFont } from '@fortawesome/free-solid-svg-icons';
-import { faImage } from '@fortawesome/free-solid-svg-icons';
-import { faFilePdf } from '@fortawesome/free-solid-svg-icons';
-import { faObjectGroup } from '@fortawesome/free-solid-svg-icons';
-import { faTable } from '@fortawesome/free-solid-svg-icons';
-import { faGlobeAsia } from '@fortawesome/free-solid-svg-icons';
-import { faUndoAlt } from '@fortawesome/free-solid-svg-icons';
-import { faRedoAlt } from '@fortawesome/free-solid-svg-icons';
-import { faPenNib } from '@fortawesome/free-solid-svg-icons';
-import { faFilm } from '@fortawesome/free-solid-svg-icons';
-import { faMusic } from '@fortawesome/free-solid-svg-icons';
-import { faTree } from '@fortawesome/free-solid-svg-icons';
-import Measure from 'react-measure';
-import { DashUserModel } from '../../server/authentication/models/user_model';
-import { ServerUtils } from '../../server/ServerUtil';
-import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { Field, Opt, FieldWaiting } from '../../fields/Field';
-import { ListField } from '../../fields/ListField';
-import { Gateway, Settings } from '../northstar/manager/Gateway';
-import { Catalog, Schema, Attribute, AttributeGroup, AggregateFunction } from '../northstar/model/idea/idea';
-import { ArrayUtil } from '../northstar/utils/ArrayUtil';
-import '../northstar/model/ModelExtensions'
-import '../northstar/utils/Extensions'
-import { HistogramOperation } from '../northstar/operations/HistogramOperation';
-import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
-import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
-import { CollectionView } from './collections/CollectionView';
+import "./Main.scss";
+import { DocumentView } from './nodes/DocumentView';
 
 @observer
 export class Main extends React.Component {
@@ -247,7 +231,7 @@ export class Main extends React.Component {
         let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
         let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
         let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
-        let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas" }));
+        let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true }));
         let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, height: 200, title: "video node" }));
         let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" }));
         let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 70790af18..0b12f11fd 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -18,6 +18,7 @@ import React = require("react")
 export interface TreeViewProps {
     document: Document;
     deleteDoc: (doc: Document) => void;
+    copyOnDrag: boolean;
 }
 
 export enum BulletType {
@@ -63,7 +64,7 @@ class TreeView extends React.Component<TreeViewProps> {
      */
     renderTitle() {
         let reference = React.createRef<HTMLDivElement>();
-        let onItemDown = setupDrag(reference, () => this.props.document, (containingCollection: CollectionView) => this.props.deleteDoc(this.props.document));
+        let onItemDown = setupDrag(reference, () => this.props.document, (containingCollection: CollectionView) => this.props.deleteDoc(this.props.document), this.props.copyOnDrag);
         let editableView = (titleString: string) =>
             (<EditableView
                 display={"inline"}
@@ -85,13 +86,12 @@ class TreeView extends React.Component<TreeViewProps> {
     render() {
         let bulletType = BulletType.List;
         let childElements: JSX.Element | undefined = undefined;
-
         var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
         if (children && children !== FieldWaiting) { // add children for a collection
             if (!this._collapsed) {
                 bulletType = BulletType.Collapsible;
                 childElements = <ul>
-                    {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} />)}
+                    {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} copyOnDrag={this.props.copyOnDrag} />)}
                 </ul>
             }
             else bulletType = BulletType.Collapsed;
@@ -118,10 +118,11 @@ export class CollectionTreeView extends CollectionViewBase {
     }
 
     render() {
-        var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
+        let children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
+        let copyOnDrag = this.props.Document.GetBoolean(KeyStore.CopyDraggedItems, false);
         let childrenElement = !children || children === FieldWaiting ? (null) :
             (children.Data.map(value =>
-                <TreeView document={value} key={value.Id} deleteDoc={this.remove} />)
+                <TreeView document={value} key={value.Id} deleteDoc={this.remove} copyOnDrag={copyOnDrag} />)
             )
 
         return (
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index 458bae7ab..7cf49e215 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -82,13 +82,13 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
     @action
     protected drop(e: Event, de: DragManager.DropEvent): boolean {
         if (de.data instanceof DragManager.DocumentDragData) {
-            if (de.data.aliasOnDrop) {
+            if (de.data.aliasOnDrop || de.data.copyOnDrop) {
                 [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key =>
                     de.data.draggedDocuments.map((draggedDocument: Document, i: number) =>
                         draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null)));
             }
             let added = de.data.droppedDocuments.reduce((added, d) => this.props.addDocument(d, false), true);
-            if (added && de.data.removeDocument && !de.data.aliasOnDrop) {
+            if (added && de.data.removeDocument && !de.data.aliasOnDrop && !de.data.copyOnDrop) {
                 de.data.removeDocument(this.props.CollectionView);
             }
             e.stopPropagation();
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 538d4ada9..02004678d 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -10,6 +10,9 @@ import { Types } from "../server/Message";
 import { UndoManager } from "../client/util/UndoManager";
 import { HtmlField } from "./HtmlField";
 import { BooleanField } from "./BooleanField";
+import { allLimit } from "async";
+import { prototype } from "nodemailer/lib/smtp-pool";
+import { HistogramField } from "../client/northstar/dash-fields/HistogramField";
 
 export class Document extends Field {
     //TODO tfs: We should probably store FieldWaiting in fields when we request it from the server so that we don't set up multiple server gets for the same document and field
@@ -343,7 +346,10 @@ export class Document extends Field {
     SetText(key: Key, value: string, replaceWrongType = true) {
         this.SetData(key, value, TextField, replaceWrongType);
     }
-
+    @action
+    SetBoolean(key: Key, value: boolean, replaceWrongType = true) {
+        this.SetData(key, value, BooleanField, replaceWrongType);
+    }
     @action
     SetNumber(key: Key, value: number, replaceWrongType = true) {
         this.SetData(key, value, NumberField, replaceWrongType);
@@ -393,8 +399,26 @@ export class Document extends Field {
         return title;
         //throw new Error("Method not implemented.");
     }
-    Copy(): Field {
-        throw new Error("Method not implemented.");
+    Copy(copyProto?: boolean, id?: string): Field {
+        let copy = new Document();
+        this._proxies.forEach((fieldid, keyid) => { // copy each prototype field
+            let key = KeyStore.KeyLookup(keyid);
+            if (key) {
+                this.GetAsync(key, (field: Opt<Field>) => {
+                    if (key === KeyStore.Prototype && copyProto) { // handle prototype field specially
+                        if (field instanceof Document) {
+                            copy.Set(key, field.Copy(false)); // only copying one level of prototypes for now...
+                        }
+                    }
+                    else
+                        if (field instanceof Document)  // ... TODO bcz: should we copy documents or reference them
+                            copy.Set(key!, field)
+                        else if (field)
+                            copy.Set(key!, field.Copy())
+                })
+            }
+        });
+        return copy;
     }
 
     ToJson(): { type: Types; data: [string, string][]; _id: string } {
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 6ed3b1604..42dc34c51 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -2,49 +2,64 @@ import { Key } from "./Key";
 import { KeyTransfer } from "../server/Message";
 
 export namespace KeyStore {
-  export const Prototype = new Key("Prototype");
-  export const X = new Key("X");
-  export const Y = new Key("Y");
-  export const Page = new Key("Page");
-  export const Title = new Key("Title");
-  export const Author = new Key("Author");
-  export const PanX = new Key("PanX");
-  export const PanY = new Key("PanY");
-  export const Scale = new Key("Scale");
-  export const NativeWidth = new Key("NativeWidth");
-  export const NativeHeight = new Key("NativeHeight");
-  export const Width = new Key("Width");
-  export const Height = new Key("Height");
-  export const ZIndex = new Key("ZIndex");
-  export const Data = new Key("Data");
-  export const Annotations = new Key("Annotations");
-  export const ViewType = new Key("ViewType");
-  export const Layout = new Key("Layout");
-  export const BackgroundColor = new Key("BackgroundColor");
-  export const BackgroundLayout = new Key("BackgroundLayout");
-  export const OverlayLayout = new Key("OverlayLayout");
-  export const LayoutKeys = new Key("LayoutKeys");
-  export const LayoutFields = new Key("LayoutFields");
-  export const ColumnsKey = new Key("SchemaColumns");
-  export const SchemaSplitPercentage = new Key("SchemaSplitPercentage");
-  export const Caption = new Key("Caption");
-  export const ActiveWorkspace = new Key("ActiveWorkspace");
-  export const DocumentText = new Key("DocumentText");
-  export const BrushingDocs = new Key("BrushingDocs");
-  export const LinkedToDocs = new Key("LinkedToDocs");
-  export const LinkedFromDocs = new Key("LinkedFromDocs");
-  export const LinkDescription = new Key("LinkDescription");
-  export const LinkTags = new Key("LinkTag");
-  export const Thumbnail = new Key("Thumbnail");
-  export const ThumbnailPage = new Key("ThumbnailPage");
-  export const CurPage = new Key("CurPage");
-  export const AnnotationOn = new Key("AnnotationOn");
-  export const NumPages = new Key("NumPages");
-  export const Ink = new Key("Ink");
-  export const Cursors = new Key("Cursors");
-  export const OptionalRightCollection = new Key("OptionalRightCollection");
-  export const Archives = new Key("Archives");
-  export const Updated = new Key("Updated");
-  export const Workspaces = new Key("Workspaces");
-  export const Minimized = new Key("Minimized");
+    export const Prototype = new Key("Prototype");
+    export const X = new Key("X");
+    export const Y = new Key("Y");
+    export const Page = new Key("Page");
+    export const Title = new Key("Title");
+    export const Author = new Key("Author");
+    export const PanX = new Key("PanX");
+    export const PanY = new Key("PanY");
+    export const Scale = new Key("Scale");
+    export const NativeWidth = new Key("NativeWidth");
+    export const NativeHeight = new Key("NativeHeight");
+    export const Width = new Key("Width");
+    export const Height = new Key("Height");
+    export const ZIndex = new Key("ZIndex");
+    export const Data = new Key("Data");
+    export const Annotations = new Key("Annotations");
+    export const ViewType = new Key("ViewType");
+    export const Layout = new Key("Layout");
+    export const BackgroundColor = new Key("BackgroundColor");
+    export const BackgroundLayout = new Key("BackgroundLayout");
+    export const OverlayLayout = new Key("OverlayLayout");
+    export const LayoutKeys = new Key("LayoutKeys");
+    export const LayoutFields = new Key("LayoutFields");
+    export const ColumnsKey = new Key("SchemaColumns");
+    export const SchemaSplitPercentage = new Key("SchemaSplitPercentage");
+    export const Caption = new Key("Caption");
+    export const ActiveWorkspace = new Key("ActiveWorkspace");
+    export const DocumentText = new Key("DocumentText");
+    export const BrushingDocs = new Key("BrushingDocs");
+    export const LinkedToDocs = new Key("LinkedToDocs");
+    export const LinkedFromDocs = new Key("LinkedFromDocs");
+    export const LinkDescription = new Key("LinkDescription");
+    export const LinkTags = new Key("LinkTag");
+    export const Thumbnail = new Key("Thumbnail");
+    export const ThumbnailPage = new Key("ThumbnailPage");
+    export const CurPage = new Key("CurPage");
+    export const AnnotationOn = new Key("AnnotationOn");
+    export const NumPages = new Key("NumPages");
+    export const Ink = new Key("Ink");
+    export const Cursors = new Key("Cursors");
+    export const OptionalRightCollection = new Key("OptionalRightCollection");
+    export const Archives = new Key("Archives");
+    export const Workspaces = new Key("Workspaces");
+    export const Minimized = new Key("Minimized");
+    export const CopyDraggedItems = new Key("CopyDraggedItems");
+
+    export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight,
+        Width, Height, ZIndex, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys,
+        LayoutFields, ColumnsKey, SchemaSplitPercentage, Caption, ActiveWorkspace, DocumentText, BrushingDocs, LinkedToDocs, LinkedFromDocs,
+        LinkDescription, LinkTags, Thumbnail, ThumbnailPage, CurPage, AnnotationOn, NumPages, Ink, Cursors, OptionalRightCollection,
+        Archives, Workspaces, Minimized, CopyDraggedItems
+    ];
+    export function KeyLookup(keyid: string) {
+        for (let i = 0; i < KeyList.length; i++) {
+            let keylistid = KeyList[i].Id;
+            if (keylistid === keyid)
+                return KeyList[i];
+        }
+        return null;
+    }
 }
-- 
cgit v1.2.3-70-g09d2


From cecbafdd8e42b1fa021a54fe624c3cf68e8645bd Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 8 Apr 2019 22:22:50 -0400
Subject: Renamed CollectionViewBase to CollectionSubView Fixed bug with adding
 documents to PDF and videos

---
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 src/client/views/collections/CollectionPDFView.tsx |   9 +-
 .../views/collections/CollectionSchemaView.tsx     |   4 +-
 src/client/views/collections/CollectionSubView.tsx | 231 +++++++++++++++++++++
 .../views/collections/CollectionTreeView.tsx       |   4 +-
 .../views/collections/CollectionVideoView.tsx      |  14 +-
 .../views/collections/CollectionViewBase.tsx       | 231 ---------------------
 .../CollectionFreeFormLinksView.tsx                |   2 +-
 .../CollectionFreeFormRemoteCursors.tsx            |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 10 files changed, 249 insertions(+), 254 deletions(-)
 create mode 100644 src/client/views/collections/CollectionSubView.tsx
 delete mode 100644 src/client/views/collections/CollectionViewBase.tsx

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c5e1e7f4b..c3757a377 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -15,7 +15,7 @@ import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionDockingView.scss";
 import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView";
 import React = require("react");
-import { SubCollectionViewProps } from "./CollectionViewBase";
+import { SubCollectionViewProps } from "./CollectionSubView";
 import { ServerUtils } from "../../../server/ServerUtil";
 import { DragManager } from "../../util/DragManager";
 import { TextField } from "../../../fields/TextField";
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 1f2e71f6e..32f50afc0 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -2,16 +2,15 @@ import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
-import { CollectionViewProps } from "./CollectionViewBase";
 import "./CollectionPDFView.scss"
 import React = require("react");
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
-import { FieldView } from "../nodes/FieldView";
+import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView";
 
 
 @observer
-export class CollectionPDFView extends React.Component<CollectionViewProps> {
+export class CollectionPDFView extends React.Component<FieldViewProps> {
 
     public static LayoutString(fieldKey: string = "DataKey") {
         return FieldView.LayoutString(CollectionPDFView, fieldKey);
@@ -23,7 +22,7 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
     @action onPageForward = () => this.curPage < this.numPages ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage + 1) : -1;
 
     private get uIButtons() {
-        let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]);
+        let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
         return (
             <div className="collectionPdfView-buttonTray" key="tray" style={{ transform: `scale(${scaling}, ${scaling})` }}>
                 <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button>
@@ -39,7 +38,7 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
     }
 
     private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
-        let props = { ...renderProps, ...this.props };
+        let props = { ...this.props, ...renderProps };
         return (
             <>
                 <CollectionFreeFormView {...props} />
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 4604945d1..9b780f29b 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -23,7 +23,7 @@ import { DocumentView } from "../nodes/DocumentView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import "./CollectionSchemaView.scss";
 import { CollectionView } from "./CollectionView";
-import { CollectionViewBase } from "./CollectionViewBase";
+import { CollectionSubView } from "./CollectionSubView";
 import { TextField } from "../../../fields/TextField";
 import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView";
 import { emptyFunction, returnFalse } from "../../../Utils";
@@ -56,7 +56,7 @@ class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggl
 }
 
 @observer
-export class CollectionSchemaView extends CollectionViewBase {
+export class CollectionSchemaView extends CollectionSubView {
     private _mainCont = React.createRef<HTMLDivElement>();
     private _startSplitPercent = 0;
     private DIVIDER_WIDTH = 4;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
new file mode 100644
index 000000000..59b89e119
--- /dev/null
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -0,0 +1,231 @@
+import { action, runInAction } from "mobx";
+import { Document } from "../../../fields/Document";
+import { ListField } from "../../../fields/ListField";
+import React = require("react");
+import { KeyStore } from "../../../fields/KeyStore";
+import { FieldWaiting, Opt } from "../../../fields/Field";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { DragManager } from "../../util/DragManager";
+import { Documents, DocumentOptions } from "../../documents/Documents";
+import { RouteStore } from "../../../server/RouteStore";
+import { TupleField } from "../../../fields/TupleField";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { NumberField } from "../../../fields/NumberField";
+import { ServerUtils } from "../../../server/ServerUtil";
+import { Server } from "../../Server";
+import { FieldViewProps } from "../nodes/FieldView";
+import * as rp from 'request-promise'
+
+export interface CollectionViewProps extends FieldViewProps {
+    addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
+    removeDocument: (document: Document) => boolean;
+    moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+}
+
+export interface SubCollectionViewProps extends CollectionViewProps {
+}
+
+export type CursorEntry = TupleField<[string, string], [number, number]>;
+
+export class CollectionSubView extends React.Component<SubCollectionViewProps> {
+    private dropDisposer?: DragManager.DragDropDisposer;
+    protected createDropTarget = (ele: HTMLDivElement) => {
+        if (this.dropDisposer) {
+            this.dropDisposer();
+        }
+        if (ele) {
+            this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+        }
+    }
+
+    @action
+    protected setCursorPosition(position: [number, number]) {
+        let ind;
+        let doc = this.props.Document;
+        let id = CurrentUserUtils.id;
+        let email = CurrentUserUtils.email;
+        if (id && email) {
+            let textInfo: [string, string] = [id, email];
+            doc.GetTAsync(KeyStore.Prototype, Document).then(proto => {
+                if (!proto) {
+                    return;
+                }
+                proto.GetOrCreateAsync<ListField<CursorEntry>>(KeyStore.Cursors, ListField, action((field: ListField<CursorEntry>) => {
+                    let cursors = field.Data;
+                    if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) {
+                        cursors[ind].Data[1] = position;
+                    } else {
+                        let entry = new TupleField<[string, string], [number, number]>([textInfo, position]);
+                        cursors.push(entry);
+                    }
+                }))
+            })
+        }
+    }
+
+    @undoBatch
+    @action
+    protected drop(e: Event, de: DragManager.DropEvent): boolean {
+        if (de.data instanceof DragManager.DocumentDragData) {
+            if (de.data.aliasOnDrop || de.data.copyOnDrop) {
+                [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key =>
+                    de.data.draggedDocuments.map((draggedDocument: Document, i: number) =>
+                        draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null)));
+            }
+            let added = false;
+            if (de.data.aliasOnDrop) {
+                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false);
+            } else if (de.data.moveDocument) {
+                const move = de.data.moveDocument;
+                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || move(d, this.props.Document, this.props.addDocument), false)
+            } else {
+                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false)
+            }
+            e.stopPropagation();
+            return added;
+        }
+        if (de.data instanceof DragManager.LinkDragData) {
+            let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document;
+            if (sourceDoc) runInAction(() => {
+                let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document)
+                if (srcTarg && srcTarg !== FieldWaiting) {
+                    let linkDocs = srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]);
+                    linkDocs.map(linkDoc => {
+                        let targDoc = linkDoc.GetT(KeyStore.LinkedToDocs, Document);
+                        if (targDoc && targDoc !== FieldWaiting) {
+                            let dropdoc = targDoc.MakeDelegate();
+                            de.data.droppedDocuments.push(dropdoc);
+                            this.props.addDocument(dropdoc, false);
+                        }
+                    })
+                }
+            })
+            return true;
+        }
+        return false;
+    }
+
+    protected getDocumentFromType(type: string, path: string, options: DocumentOptions): Opt<Document> {
+        let ctor: ((path: string, options: DocumentOptions) => Document) | undefined;
+        if (type.indexOf("image") !== -1) {
+            ctor = Documents.ImageDocument;
+        }
+        if (type.indexOf("video") !== -1) {
+            ctor = Documents.VideoDocument;
+        }
+        if (type.indexOf("audio") !== -1) {
+            ctor = Documents.AudioDocument;
+        }
+        if (type.indexOf("pdf") !== -1) {
+            ctor = Documents.PdfDocument;
+            options.nativeWidth = 1200;
+        }
+        if (type.indexOf("html") !== -1) {
+            if (path.includes('localhost')) {
+                let s = path.split('/');
+                let id = s[s.length - 1];
+                Server.GetField(id).then(field => {
+                    if (field instanceof Document) {
+                        let alias = field.CreateAlias();
+                        alias.SetNumber(KeyStore.X, options.x || 0);
+                        alias.SetNumber(KeyStore.Y, options.y || 0);
+                        alias.SetNumber(KeyStore.Width, options.width || 300);
+                        alias.SetNumber(KeyStore.Height, options.height || options.width || 300);
+                        this.props.addDocument(alias, false);
+                    }
+                })
+                return undefined;
+            }
+            ctor = Documents.WebDocument;
+            options = { height: options.width, ...options, title: path };
+        }
+        return ctor ? ctor(path, options) : undefined;
+    }
+
+    @undoBatch
+    @action
+    protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
+        let html = e.dataTransfer.getData("text/html");
+        let text = e.dataTransfer.getData("text/plain");
+
+        if (text && text.startsWith("<div")) {
+            return;
+        }
+        e.stopPropagation()
+        e.preventDefault()
+
+        if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
+            console.log("not good");
+            let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
+            htmlDoc.SetText(KeyStore.DocumentText, text);
+            this.props.addDocument(htmlDoc, false);
+            return;
+        }
+
+        let batch = UndoManager.StartBatch("collection view drop");
+        let promises: Promise<void>[] = [];
+        // tslint:disable-next-line:prefer-for-of
+        for (let i = 0; i < e.dataTransfer.items.length; i++) {
+            const upload = window.location.origin + RouteStore.upload;
+            let item = e.dataTransfer.items[i];
+            if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
+                let str: string;
+                let prom = new Promise<string>(res =>
+                    e.dataTransfer.items[i].getAsString(res)).then(action((s: string) => {
+                        str = s;
+                        return rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s))
+                    })).then(res => {
+                        let type = res.headers["content-type"];
+                        if (type) {
+                            let doc = this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
+                            if (doc) {
+                                this.props.addDocument(doc, false);
+                            }
+                        }
+                    });
+                promises.push(prom);
+                // this.props.addDocument(Documents.WebDocument(s, { ...options, width: 300, height: 300 }), false)
+            }
+            let type = item.type
+            if (item.kind === "file") {
+                let file = item.getAsFile();
+                let formData = new FormData()
+
+                if (file) {
+                    formData.append('file', file)
+                }
+
+                let prom = fetch(upload, {
+                    method: 'POST',
+                    body: formData
+                }).then(async (res: Response) => {
+                    const json = await res.json();
+                    json.map((file: any) => {
+                        let path = window.location.origin + file
+                        runInAction(() => {
+                            let doc = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300 })
+
+                            let docs = this.props.Document.GetT(KeyStore.Data, ListField);
+                            if (docs !== FieldWaiting) {
+                                if (!docs) {
+                                    docs = new ListField<Document>();
+                                    this.props.Document.Set(KeyStore.Data, docs)
+                                }
+                                if (doc) {
+                                    docs.Data.push(doc);
+                                }
+                            }
+                        });
+                    });
+                });
+                promises.push(prom);
+            }
+        }
+
+        if (promises.length) {
+            Promise.all(promises).catch(() => { }).then(() => batch.end());
+        } else {
+            batch.end();
+        }
+    }
+}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index dce35868e..2bdf0baa6 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -11,7 +11,7 @@ import { setupDrag, DragManager } from "../../util/DragManager";
 import { EditableView } from "../EditableView";
 import "./CollectionTreeView.scss";
 import { CollectionView } from "./CollectionView";
-import { CollectionViewBase } from "./CollectionViewBase";
+import { CollectionSubView } from "./CollectionSubView";
 import React = require("react")
 import { COLLECTION_BORDER_WIDTH } from './CollectionBaseView';
 import { props } from 'bluebird';
@@ -120,7 +120,7 @@ class TreeView extends React.Component<TreeViewProps> {
 }
 
 @observer
-export class CollectionTreeView extends CollectionViewBase {
+export class CollectionTreeView extends CollectionSubView {
 
     @action
     remove = (document: Document) => {
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index afadfeefb..1acc76c85 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -1,20 +1,16 @@
-import { action, computed, observable, trace } from "mobx";
+import { action, observable, trace } from "mobx";
 import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
-import { CollectionView } from "./CollectionView";
 import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
-import { CollectionViewProps } from "./CollectionViewBase";
 import React = require("react");
-import { FieldId } from "../../../fields/Field";
 import "./CollectionVideoView.scss"
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
-import { FieldView } from "../nodes/FieldView";
+import { FieldView, FieldViewProps } from "../nodes/FieldView";
 
 
 @observer
-export class CollectionVideoView extends React.Component<CollectionViewProps> {
+export class CollectionVideoView extends React.Component<FieldViewProps> {
     private _intervalTimer: any = undefined;
     private _player: HTMLVideoElement | undefined = undefined;
 
@@ -25,7 +21,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
         return FieldView.LayoutString(CollectionVideoView, fieldKey);
     }
     private get uIButtons() {
-        let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]);
+        let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
         return ([
             <div className="collectionVideoView-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
                 <span>{"" + Math.round(this._currentTimecode)}</span>
@@ -109,7 +105,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
     }
 
     private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
-        let props = { ...renderProps, ...this.props };
+        let props = { ...this.props, ...renderProps };
         return (
             <>
                 <CollectionFreeFormView {...props} />
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
deleted file mode 100644
index ad1294f76..000000000
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ /dev/null
@@ -1,231 +0,0 @@
-import { action, runInAction } from "mobx";
-import { Document } from "../../../fields/Document";
-import { ListField } from "../../../fields/ListField";
-import React = require("react");
-import { KeyStore } from "../../../fields/KeyStore";
-import { FieldWaiting, Opt } from "../../../fields/Field";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DragManager } from "../../util/DragManager";
-import { Documents, DocumentOptions } from "../../documents/Documents";
-import { RouteStore } from "../../../server/RouteStore";
-import { TupleField } from "../../../fields/TupleField";
-import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { NumberField } from "../../../fields/NumberField";
-import { ServerUtils } from "../../../server/ServerUtil";
-import { Server } from "../../Server";
-import { FieldViewProps } from "../nodes/FieldView";
-import * as rp from 'request-promise'
-
-export interface CollectionViewProps extends FieldViewProps {
-    addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
-    removeDocument: (document: Document) => boolean;
-    moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
-}
-
-export interface SubCollectionViewProps extends CollectionViewProps {
-}
-
-export type CursorEntry = TupleField<[string, string], [number, number]>;
-
-export class CollectionViewBase extends React.Component<SubCollectionViewProps> {
-    private dropDisposer?: DragManager.DragDropDisposer;
-    protected createDropTarget = (ele: HTMLDivElement) => {
-        if (this.dropDisposer) {
-            this.dropDisposer();
-        }
-        if (ele) {
-            this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
-        }
-    }
-
-    @action
-    protected setCursorPosition(position: [number, number]) {
-        let ind;
-        let doc = this.props.Document;
-        let id = CurrentUserUtils.id;
-        let email = CurrentUserUtils.email;
-        if (id && email) {
-            let textInfo: [string, string] = [id, email];
-            doc.GetTAsync(KeyStore.Prototype, Document).then(proto => {
-                if (!proto) {
-                    return;
-                }
-                proto.GetOrCreateAsync<ListField<CursorEntry>>(KeyStore.Cursors, ListField, action((field: ListField<CursorEntry>) => {
-                    let cursors = field.Data;
-                    if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) {
-                        cursors[ind].Data[1] = position;
-                    } else {
-                        let entry = new TupleField<[string, string], [number, number]>([textInfo, position]);
-                        cursors.push(entry);
-                    }
-                }))
-            })
-        }
-    }
-
-    @undoBatch
-    @action
-    protected drop(e: Event, de: DragManager.DropEvent): boolean {
-        if (de.data instanceof DragManager.DocumentDragData) {
-            if (de.data.aliasOnDrop || de.data.copyOnDrop) {
-                [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key =>
-                    de.data.draggedDocuments.map((draggedDocument: Document, i: number) =>
-                        draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null)));
-            }
-            let added = false;
-            if (de.data.aliasOnDrop) {
-                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false);
-            } else if (de.data.moveDocument) {
-                const move = de.data.moveDocument;
-                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || move(d, this.props.Document, this.props.addDocument), false)
-            } else {
-                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false)
-            }
-            e.stopPropagation();
-            return added;
-        }
-        if (de.data instanceof DragManager.LinkDragData) {
-            let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document;
-            if (sourceDoc) runInAction(() => {
-                let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document)
-                if (srcTarg && srcTarg !== FieldWaiting) {
-                    let linkDocs = srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]);
-                    linkDocs.map(linkDoc => {
-                        let targDoc = linkDoc.GetT(KeyStore.LinkedToDocs, Document);
-                        if (targDoc && targDoc !== FieldWaiting) {
-                            let dropdoc = targDoc.MakeDelegate();
-                            de.data.droppedDocuments.push(dropdoc);
-                            this.props.addDocument(dropdoc, false);
-                        }
-                    })
-                }
-            })
-            return true;
-        }
-        return false;
-    }
-
-    protected getDocumentFromType(type: string, path: string, options: DocumentOptions): Opt<Document> {
-        let ctor: ((path: string, options: DocumentOptions) => Document) | undefined;
-        if (type.indexOf("image") !== -1) {
-            ctor = Documents.ImageDocument;
-        }
-        if (type.indexOf("video") !== -1) {
-            ctor = Documents.VideoDocument;
-        }
-        if (type.indexOf("audio") !== -1) {
-            ctor = Documents.AudioDocument;
-        }
-        if (type.indexOf("pdf") !== -1) {
-            ctor = Documents.PdfDocument;
-            options.nativeWidth = 1200;
-        }
-        if (type.indexOf("html") !== -1) {
-            if (path.includes('localhost')) {
-                let s = path.split('/');
-                let id = s[s.length - 1];
-                Server.GetField(id).then(field => {
-                    if (field instanceof Document) {
-                        let alias = field.CreateAlias();
-                        alias.SetNumber(KeyStore.X, options.x || 0);
-                        alias.SetNumber(KeyStore.Y, options.y || 0);
-                        alias.SetNumber(KeyStore.Width, options.width || 300);
-                        alias.SetNumber(KeyStore.Height, options.height || options.width || 300);
-                        this.props.addDocument(alias, false);
-                    }
-                })
-                return undefined;
-            }
-            ctor = Documents.WebDocument;
-            options = { height: options.width, ...options, title: path };
-        }
-        return ctor ? ctor(path, options) : undefined;
-    }
-
-    @undoBatch
-    @action
-    protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
-        let html = e.dataTransfer.getData("text/html");
-        let text = e.dataTransfer.getData("text/plain");
-
-        if (text && text.startsWith("<div")) {
-            return;
-        }
-        e.stopPropagation()
-        e.preventDefault()
-
-        if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
-            console.log("not good");
-            let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
-            htmlDoc.SetText(KeyStore.DocumentText, text);
-            this.props.addDocument(htmlDoc, false);
-            return;
-        }
-
-        let batch = UndoManager.StartBatch("collection view drop");
-        let promises: Promise<void>[] = [];
-        // tslint:disable-next-line:prefer-for-of
-        for (let i = 0; i < e.dataTransfer.items.length; i++) {
-            const upload = window.location.origin + RouteStore.upload;
-            let item = e.dataTransfer.items[i];
-            if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
-                let str: string;
-                let prom = new Promise<string>(res =>
-                    e.dataTransfer.items[i].getAsString(res)).then(action((s: string) => {
-                        str = s;
-                        return rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s))
-                    })).then(res => {
-                        let type = res.headers["content-type"];
-                        if (type) {
-                            let doc = this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
-                            if (doc) {
-                                this.props.addDocument(doc, false);
-                            }
-                        }
-                    });
-                promises.push(prom);
-                // this.props.addDocument(Documents.WebDocument(s, { ...options, width: 300, height: 300 }), false)
-            }
-            let type = item.type
-            if (item.kind === "file") {
-                let file = item.getAsFile();
-                let formData = new FormData()
-
-                if (file) {
-                    formData.append('file', file)
-                }
-
-                let prom = fetch(upload, {
-                    method: 'POST',
-                    body: formData
-                }).then(async (res: Response) => {
-                    const json = await res.json();
-                    json.map((file: any) => {
-                        let path = window.location.origin + file
-                        runInAction(() => {
-                            let doc = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300 })
-
-                            let docs = this.props.Document.GetT(KeyStore.Data, ListField);
-                            if (docs !== FieldWaiting) {
-                                if (!docs) {
-                                    docs = new ListField<Document>();
-                                    this.props.Document.Set(KeyStore.Data, docs)
-                                }
-                                if (doc) {
-                                    docs.Data.push(doc);
-                                }
-                            }
-                        });
-                    });
-                });
-                promises.push(prom);
-            }
-        }
-
-        if (promises.length) {
-            Promise.all(promises).catch(() => { }).then(() => batch.end());
-        } else {
-            batch.end();
-        }
-    }
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index abc6cecff..c288e7abf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -7,7 +7,7 @@ import { ListField } from "../../../../fields/ListField";
 import { Utils } from "../../../../Utils";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DocumentView } from "../../nodes/DocumentView";
-import { CollectionViewProps } from "../CollectionViewBase";
+import { CollectionViewProps } from "../CollectionSubView";
 import "./CollectionFreeFormLinksView.scss";
 import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
 import React = require("react");
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index f664f4e65..17656e4ae 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -1,7 +1,7 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
 import { KeyStore } from "../../../../fields/KeyStore";
-import { CollectionViewProps, CursorEntry } from "../CollectionViewBase";
+import { CollectionViewProps, CursorEntry } from "../CollectionSubView";
 import "./CollectionFreeFormView.scss";
 import React = require("react");
 import v5 = require("uuid/v5");
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 731ba3332..1c3899b24 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -12,7 +12,7 @@ import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDo
 import { DocumentContentsView } from "../../nodes/DocumentContentsView";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { COLLECTION_BORDER_WIDTH } from "../CollectionBaseView";
-import { CollectionViewBase } from "../CollectionViewBase";
+import { CollectionSubView } from "../CollectionSubView";
 import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
 import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
@@ -26,7 +26,7 @@ import { NumberField } from "../../../../fields/NumberField";
 import { Main } from "../../Main";
 
 @observer
-export class CollectionFreeFormView extends CollectionViewBase {
+export class CollectionFreeFormView extends CollectionSubView {
     public _canvasRef = React.createRef<HTMLDivElement>();
     private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
 
-- 
cgit v1.2.3-70-g09d2


From 5e086920bf97297a02bcd38faea56454c2220279 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 8 Apr 2019 22:45:22 -0400
Subject: Enabled semi-colon and braces linter rule

---
 src/Utils.ts                                       | 12 +--
 src/client/Server.ts                               | 35 ++++----
 src/client/SocketStub.ts                           | 24 +++---
 src/client/documents/Documents.ts                  | 38 +++++----
 src/client/northstar/core/BaseObject.ts            |  4 +-
 .../northstar/core/attribute/AttributeModel.ts     |  6 +-
 .../core/attribute/AttributeTransformationModel.ts | 17 ++--
 .../core/attribute/CalculatedAttributeModel.ts     |  2 +-
 .../northstar/core/brusher/IBaseBrushable.ts       |  4 +-
 src/client/northstar/core/brusher/IBaseBrusher.ts  |  2 +-
 src/client/northstar/core/filter/FilterModel.ts    |  2 +-
 .../northstar/core/filter/IBaseFilterConsumer.ts   |  4 +-
 .../northstar/core/filter/IBaseFilterProvider.ts   |  2 +-
 .../northstar/core/filter/ValueComparision.ts      | 16 ++--
 src/client/northstar/dash-fields/HistogramField.ts |  2 +-
 .../dash-nodes/HistogramBinPrimitiveCollection.ts  | 12 +--
 src/client/northstar/dash-nodes/HistogramBox.tsx   | 24 +++---
 .../dash-nodes/HistogramBoxPrimitives.tsx          | 40 +++++----
 .../dash-nodes/HistogramLabelPrimitives.tsx        | 15 ++--
 src/client/northstar/manager/Gateway.ts            |  2 +-
 src/client/northstar/model/ModelExtensions.ts      | 18 ++--
 src/client/northstar/model/ModelHelpers.ts         |  8 +-
 .../model/binRanges/AlphabeticVisualBinRange.ts    |  4 +-
 .../model/binRanges/DateTimeVisualBinRange.ts      |  4 +-
 .../model/binRanges/NominalVisualBinRange.ts       |  4 +-
 .../model/binRanges/QuantitativeVisualBinRange.ts  |  2 +-
 .../northstar/model/binRanges/VisualBinRange.ts    |  2 +-
 .../model/binRanges/VisualBinRangeHelper.ts        | 14 +--
 src/client/northstar/operations/BaseOperation.ts   |  9 +-
 .../northstar/operations/HistogramOperation.ts     |  2 +-
 src/client/northstar/utils/ArrayUtil.ts            |  2 +-
 src/client/northstar/utils/Extensions.ts           |  6 +-
 src/client/northstar/utils/GeometryUtil.ts         | 34 ++++----
 src/client/northstar/utils/MathUtil.ts             | 38 +++++----
 src/client/northstar/utils/SizeConverter.ts        | 16 ++--
 src/client/northstar/utils/StyleContants.ts        |  2 +-
 src/client/northstar/utils/Utils.ts                |  8 +-
 src/client/util/DocumentManager.ts                 | 12 +--
 src/client/util/DragManager.ts                     |  5 +-
 src/client/util/RichTextSchema.tsx                 | 42 ++++-----
 src/client/util/Scripting.ts                       | 10 +--
 src/client/util/ScrollBox.tsx                      |  4 +-
 src/client/util/SelectionManager.ts                |  4 +-
 src/client/util/TooltipTextMenu.tsx                | 38 ++++-----
 src/client/util/Transform.ts                       | 20 ++---
 src/client/util/TypedEvent.ts                      |  2 +-
 src/client/util/UndoManager.ts                     | 24 +++---
 src/client/views/ContextMenu.tsx                   | 14 +--
 src/client/views/ContextMenuItem.tsx               |  4 +-
 src/client/views/DocumentDecorations.tsx           | 99 +++++++++++-----------
 src/client/views/EditableView.tsx                  | 10 +--
 src/client/views/InkingCanvas.tsx                  | 15 ++--
 src/client/views/InkingControl.tsx                 | 12 +--
 src/client/views/InkingStroke.tsx                  |  6 +-
 src/client/views/Main.tsx                          | 55 ++++++------
 .../views/collections/CollectionBaseView.tsx       | 34 +++++---
 .../views/collections/CollectionDockingView.tsx    | 37 ++++----
 src/client/views/collections/CollectionPDFView.tsx |  6 +-
 .../views/collections/CollectionSchemaView.tsx     | 39 ++++-----
 src/client/views/collections/CollectionSubView.tsx | 41 ++++-----
 .../views/collections/CollectionTreeView.tsx       | 14 +--
 .../views/collections/CollectionVideoView.tsx      |  6 +-
 src/client/views/collections/CollectionView.tsx    | 20 ++---
 .../CollectionFreeFormLinkView.tsx                 |  2 +-
 .../CollectionFreeFormLinksView.tsx                | 18 ++--
 .../CollectionFreeFormRemoteCursors.tsx            |  6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 33 ++++----
 .../collections/collectionFreeForm/MarqueeView.tsx | 15 ++--
 .../collectionFreeForm/PreviewCursor.tsx           |  5 +-
 src/client/views/nodes/Annotation.tsx              | 22 ++---
 src/client/views/nodes/AudioBox.tsx                | 12 +--
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 12 +--
 src/client/views/nodes/DocumentContentsView.tsx    |  6 +-
 src/client/views/nodes/DocumentView.tsx            | 35 ++++----
 src/client/views/nodes/FieldView.tsx               | 29 ++++---
 src/client/views/nodes/FormattedTextBox.tsx        | 17 ++--
 src/client/views/nodes/ImageBox.tsx                | 12 +--
 src/client/views/nodes/KeyValueBox.tsx             | 18 ++--
 src/client/views/nodes/KeyValuePair.tsx            | 14 +--
 src/client/views/nodes/LinkBox.tsx                 | 17 ++--
 src/client/views/nodes/LinkEditor.tsx              |  6 +-
 src/client/views/nodes/LinkMenu.tsx                | 10 +--
 src/client/views/nodes/PDFBox.tsx                  | 56 ++++++------
 src/client/views/nodes/Sticky.tsx                  |  6 +-
 src/client/views/nodes/VideoBox.tsx                | 10 +--
 src/client/views/nodes/WebBox.tsx                  |  6 +-
 src/debug/Test.tsx                                 |  8 +-
 src/debug/Viewer.tsx                               | 48 +++++------
 src/fields/AudioField.ts                           |  2 +-
 src/fields/BasicField.ts                           |  6 +-
 src/fields/Document.ts                             | 25 +++---
 src/fields/DocumentReference.ts                    |  2 +-
 src/fields/Field.ts                                |  2 +-
 src/fields/HtmlField.ts                            |  2 +-
 src/fields/ImageField.ts                           |  2 +-
 src/fields/InkField.ts                             |  2 +-
 src/fields/Key.ts                                  |  6 +-
 src/fields/KeyStore.ts                             |  3 +-
 src/fields/ListField.ts                            | 31 ++++---
 src/fields/NumberField.ts                          |  4 +-
 src/fields/PDFField.ts                             |  4 +-
 src/fields/RichTextField.ts                        |  2 +-
 src/fields/TextField.ts                            |  4 +-
 src/fields/TupleField.ts                           |  6 +-
 src/fields/VideoField.ts                           |  2 +-
 src/fields/WebField.ts                             |  2 +-
 src/mobile/ImageUpload.tsx                         | 14 +--
 src/server/Client.ts                               |  4 +-
 src/server/authentication/config/passport.ts       |  6 +-
 .../authentication/controllers/WorkspacesMenu.tsx  |  2 +-
 .../authentication/controllers/user_controller.ts  | 20 ++---
 .../authentication/models/current_user_utils.ts    |  8 +-
 src/server/authentication/models/user_model.ts     |  2 +-
 src/server/database.ts                             | 34 ++++----
 src/server/index.ts                                | 76 ++++++++---------
 test/test.ts                                       |  6 +-
 tslint.json                                        | 16 ++--
 117 files changed, 901 insertions(+), 826 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 8bd7f2f5c..b0e66787e 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -6,16 +6,16 @@ import { Message, Types } from './server/Message';
 export class Utils {
 
     public static GenerateGuid(): string {
-        return v4()
+        return v4();
     }
 
     public static GenerateDeterministicGuid(seed: string): string {
-        return v5(seed, v5.URL)
+        return v5(seed, v5.URL);
     }
 
     public static GetScreenTransform(ele: HTMLElement): { scale: number, translateX: number, translateY: number } {
         if (!ele) {
-            return { scale: 1, translateX: 1, translateY: 1 }
+            return { scale: 1, translateX: 1, translateY: 1 };
         }
         const rect = ele.getBoundingClientRect();
         const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / ele.offsetWidth;
@@ -55,8 +55,8 @@ export class Utils {
         return (args: any) => {
             this.log(prefix, messageName, args, true);
             func(args);
-        }
-    };
+        };
+    }
 
     public static Emit<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T) {
         this.log("Emit", message.Name, args, false);
@@ -81,7 +81,7 @@ export class Utils {
     public static AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) {
         socket.on(message.Message, (arg: T, fn: (res: any) => any) => {
             this.log('S receiving', message.Name, arg, true);
-            handler([arg, this.loggingCallback('S sending', fn, message.Name)])
+            handler([arg, this.loggingCallback('S sending', fn, message.Name)]);
         });
     }
 }
diff --git a/src/client/Server.ts b/src/client/Server.ts
index 45c7144ca..857101a33 100644
--- a/src/client/Server.ts
+++ b/src/client/Server.ts
@@ -1,7 +1,7 @@
-import { Key } from "../fields/Key"
+import { Key } from "../fields/Key";
 import { ObservableMap, action, reaction } from "mobx";
-import { Field, FieldWaiting, FIELD_WAITING, Opt, FieldId } from "../fields/Field"
-import { Document } from "../fields/Document"
+import { Field, FieldWaiting, FIELD_WAITING, Opt, FieldId } from "../fields/Field";
+import { Document } from "../fields/Document";
 import { SocketStub, FieldMap } from "./SocketStub";
 import * as OpenSocket from 'socket.io-client';
 import { Utils } from "./../Utils";
@@ -10,7 +10,7 @@ import { MessageStore, Types } from "./../server/Message";
 export class Server {
     public static ClientFieldsCached: ObservableMap<FieldId, Field | FIELD_WAITING> = new ObservableMap();
     static Socket: SocketIOClient.Socket = OpenSocket(`${window.location.protocol}//${window.location.hostname}:4321`);
-    static GUID: string = Utils.GenerateGuid()
+    static GUID: string = Utils.GenerateGuid();
 
 
     // Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached).
@@ -25,15 +25,16 @@ export class Server {
                 this.ClientFieldsCached.set(fieldid, FieldWaiting);
                 SocketStub.SEND_FIELD_REQUEST(fieldid, action((field: Field | undefined) => {
                     let cached = this.ClientFieldsCached.get(fieldid);
-                    if (cached !== FieldWaiting)
+                    if (cached !== FieldWaiting) {
                         cb(cached);
+                    }
                     else {
                         if (field) {
                             this.ClientFieldsCached.set(fieldid, field);
                         } else {
-                            this.ClientFieldsCached.delete(fieldid)
+                            this.ClientFieldsCached.delete(fieldid);
                         }
-                        cb(field)
+                        cb(field);
                     }
                 }));
             } else if (cached !== FieldWaiting) {
@@ -42,12 +43,12 @@ export class Server {
                 reaction(() => this.ClientFieldsCached.get(fieldid),
                     (field, reaction) => {
                         if (field !== FieldWaiting) {
-                            reaction.dispose()
-                            cb(field)
+                            reaction.dispose();
+                            cb(field);
                         }
-                    })
+                    });
             }
-        }
+        };
         if (callback) {
             fn(callback);
         } else {
@@ -79,15 +80,15 @@ export class Server {
                     let field = fields[id];
                     if (field) {
                         if (!(this.ClientFieldsCached.get(field.Id) instanceof Field)) {
-                            this.ClientFieldsCached.set(field.Id, field)
+                            this.ClientFieldsCached.set(field.Id, field);
                         } else {
-                            throw new Error("we shouldn't be trying to replace things that are already in the cache")
+                            throw new Error("we shouldn't be trying to replace things that are already in the cache");
                         }
                     } else {
                         if (this.ClientFieldsCached.get(id) === FieldWaiting) {
                             this.ClientFieldsCached.delete(id);
                         } else {
-                            throw new Error("we shouldn't be trying to replace things that are already in the cache")
+                            throw new Error("we shouldn't be trying to replace things that are already in the cache");
                         }
                     }
                 }
@@ -99,9 +100,9 @@ export class Server {
                                 let realField = field as Field;
                                 existingFields[realField.Id] = realField;
                             }
-                            cb({ ...fields, ...existingFields })
+                            cb({ ...fields, ...existingFields });
                         }
-                    }, { fireImmediately: true })
+                    }, { fireImmediately: true });
             }));
         };
         if (callback) {
@@ -139,7 +140,7 @@ export class Server {
 
     public static UpdateField(field: Field) {
         if (!this.ClientFieldsCached.has(field.Id)) {
-            this.ClientFieldsCached.set(field.Id, field)
+            this.ClientFieldsCached.set(field.Id, field);
         }
         SocketStub.SEND_SET_FIELD(field);
     }
diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts
index c3cd8bee6..257973e3d 100644
--- a/src/client/SocketStub.ts
+++ b/src/client/SocketStub.ts
@@ -1,7 +1,7 @@
-import { Key } from "../fields/Key"
-import { Field, FieldId, Opt } from "../fields/Field"
+import { Key } from "../fields/Key";
+import { Field, FieldId, Opt } from "../fields/Field";
 import { ObservableMap } from "mobx";
-import { Document } from "../fields/Document"
+import { Document } from "../fields/Document";
 import { MessageStore, DocumentTransfer } from "../server/Message";
 import { Utils } from "../Utils";
 import { Server } from "./Server";
@@ -37,7 +37,7 @@ export class SocketStub {
         // document.fields.forEach((f, key) => (this.FieldStore.get(document.Id) as Document)._proxies.set(key.Id, (f as Field).Id));
 
         console.log("sending " + document.Title);
-        Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(document.ToJson()))
+        Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(document.ToJson()));
     }
 
     public static SEND_FIELD_REQUEST(fieldid: FieldId): Promise<Opt<Field>>;
@@ -50,12 +50,12 @@ export class SocketStub {
                 } else {
                     cb(undefined);
                 }
-            })
-        }
+            });
+        };
         if (callback) {
             fn(callback);
         } else {
-            return new Promise(fn)
+            return new Promise(fn);
         }
     }
 
@@ -65,7 +65,7 @@ export class SocketStub {
             for (let field of fields) {
                 fieldMap[field._id] = ServerUtils.FromJson(field);
             }
-            callback(fieldMap)
+            callback(fieldMap);
         });
     }
 
@@ -78,8 +78,9 @@ export class SocketStub {
 
         // server updates its document to hold a proxy mapping from key => fieldId
         var document = this.FieldStore.get(doc.Id) as Document;
-        if (document)
+        if (document) {
             document._proxies.set(key.Id, value.Id);
+        }
 
         // server adds the field to its repository of fields
         this.FieldStore.set(value.Id, value);
@@ -93,8 +94,9 @@ export class SocketStub {
 
         // Server removes the field id from the document's list of field proxies
         var document = this.FieldStore.get(doc.Id) as Document;
-        if (document)
+        if (document) {
             document._proxies.delete(key.Id);
+        }
     }
 
     public static SEND_SET_FIELD(field: Field) {
@@ -103,6 +105,6 @@ export class SocketStub {
         // ...SOCKET(SET_FIELD, field id, serialized field value)
 
         // Server updates the value of the field in its fieldstore
-        Utils.Emit(Server.Socket, MessageStore.SetField, field.ToJson())
+        Utils.Emit(Server.Socket, MessageStore.SetField, field.ToJson());
     }
 }
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 613b94abd..72e6e57ab 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -64,7 +64,7 @@ export namespace Documents {
     const webProtoId = "webProto";
     const collProtoId = "collectionProto";
     const kvpProtoId = "kvpProto";
-    const videoProtoId = "videoProto"
+    const videoProtoId = "videoProto";
     const audioProtoId = "audioProto";
 
     export function initProtos(): Promise<void> {
@@ -102,7 +102,7 @@ export namespace Documents {
         if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); }
         if (options.panx !== undefined) { doc.SetNumber(KeyStore.PanX, options.panx); }
         if (options.pany !== undefined) { doc.SetNumber(KeyStore.PanY, options.pany); }
-        return doc
+        return doc;
     }
 
     function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Document {
@@ -110,10 +110,12 @@ export namespace Documents {
     }
     function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: [T, { new(): U }] | Document, id?: string) {
         var deleg = doc.MakeDelegate(id);
-        if (value instanceof Document)
-            deleg.Set(KeyStore.Data, value)
-        else
+        if (value instanceof Document) {
+            deleg.Set(KeyStore.Data, value);
+        }
+        else {
             deleg.SetData(KeyStore.Data, value[0], value[1]);
+        }
         return assignOptions(deleg, options);
     }
 
@@ -134,7 +136,7 @@ export namespace Documents {
     function CreateTextPrototype(): Document {
         let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
             { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
-        return textProto
+        return textProto;
     }
     function CreatePdfPrototype(): Document {
         let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("AnnotationsKey"),
@@ -168,7 +170,7 @@ export namespace Documents {
     }
     function CreateAudioPrototype(): Document {
         let audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
+            { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
         return audioProto;
     }
 
@@ -205,22 +207,22 @@ export namespace Documents {
         return assignToDelegate(SetInstanceOptions(webProto, options, [html, HtmlField]).MakeDelegate(), options);
     }
     export function KVPDocument(document: Document, options: DocumentOptions = {}, id?: string) {
-        return assignToDelegate(SetInstanceOptions(kvpProto, options, document, id), options)
+        return assignToDelegate(SetInstanceOptions(kvpProto, options, document, id), options);
     }
     export function FreeformDocument(documents: Array<Document>, options: DocumentOptions, id?: string, makePrototype: boolean = true) {
         if (!makePrototype) {
-            return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id)
+            return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id);
         }
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id).MakeDelegate(), options)
+        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id).MakeDelegate(), options);
     }
     export function SchemaDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Schema }, [documents, ListField], id), options)
+        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Schema }, [documents, ListField], id), options);
     }
     export function TreeDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Tree }, [documents, ListField], id), options)
+        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Tree }, [documents, ListField], id), options);
     }
     export function DockDocument(config: string, options: DocumentOptions, id?: string) {
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Docking }, [config, TextField], id), options)
+        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Docking }, [config, TextField], id), options);
     }
 
     export function CaptionDocument(doc: Document) {
@@ -240,13 +242,13 @@ export namespace Documents {
             <div style="position:relative; height:15%; text-align:center; ">`
             + FormattedTextBox.LayoutString("CaptionKey") +
             `</div> 
-        </div>` };
+        </div>`; }
     export function FixedCaption(fieldName: string = "Caption") {
         return `<div style="position:absolute; height:30px; bottom:0; width:100%">
             <div style="position:absolute; width:100%; height:100%; text-align:center;bottom:0;">`
             + FormattedTextBox.LayoutString(fieldName + "Key") +
             `</div> 
-        </div>` };
+        </div>`; }
 
     function OuterCaption() {
         return (`
@@ -258,7 +260,7 @@ export namespace Documents {
         <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
     </div>
 </div>       
-        `)
+        `);
     }
     function InnerCaption() {
         return (`
@@ -270,7 +272,7 @@ export namespace Documents {
             <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
         </div>
     </div>       
-            `)
+            `);
     }
 
     /*
@@ -293,6 +295,6 @@ export namespace Documents {
             <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
         </div>
     </div>       
-            `)
+            `);
     }
 }
\ No newline at end of file
diff --git a/src/client/northstar/core/BaseObject.ts b/src/client/northstar/core/BaseObject.ts
index f1761e643..ed3818071 100644
--- a/src/client/northstar/core/BaseObject.ts
+++ b/src/client/northstar/core/BaseObject.ts
@@ -1,5 +1,5 @@
-import { IEquatable } from '../utils/IEquatable'
-import { IDisposable } from '../utils/IDisposable'
+import { IEquatable } from '../utils/IEquatable';
+import { IDisposable } from '../utils/IDisposable';
 
 export class BaseObject implements IEquatable, IDisposable {
 
diff --git a/src/client/northstar/core/attribute/AttributeModel.ts b/src/client/northstar/core/attribute/AttributeModel.ts
index 230bfecc8..c89b1617c 100644
--- a/src/client/northstar/core/attribute/AttributeModel.ts
+++ b/src/client/northstar/core/attribute/AttributeModel.ts
@@ -1,5 +1,5 @@
-import { Attribute, DataType, VisualizationHint } from '../../model/idea/idea'
-import { BaseObject } from '../BaseObject'
+import { Attribute, DataType, VisualizationHint } from '../../model/idea/idea';
+import { BaseObject } from '../BaseObject';
 import { observable } from "mobx";
 
 export abstract class AttributeModel extends BaseObject {
@@ -93,7 +93,7 @@ export class BackendAttributeModel extends AttributeModel {
     }
 
     public get DisplayName(): string {
-        return this._displayName.ReplaceAll("_", " ");;
+        return this._displayName.ReplaceAll("_", " ");
     }
 
     public get CodeName(): string {
diff --git a/src/client/northstar/core/attribute/AttributeTransformationModel.ts b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
index f50b78d51..66485183b 100644
--- a/src/client/northstar/core/attribute/AttributeTransformationModel.ts
+++ b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
@@ -1,4 +1,4 @@
-;
+
 import { computed, observable } from "mobx";
 import { AggregateFunction } from "../../model/idea/idea";
 import { AttributeModel } from "./AttributeModel";
@@ -20,16 +20,21 @@ export class AttributeTransformationModel implements IEquatable {
         if (this.AggregateFunction === AggregateFunction.Count) {
             return "count";
         }
-        if (this.AggregateFunction === AggregateFunction.Avg)
+        if (this.AggregateFunction === AggregateFunction.Avg) {
             displayName = "avg(" + displayName + ")";
-        else if (this.AggregateFunction === AggregateFunction.Max)
+        }
+        else if (this.AggregateFunction === AggregateFunction.Max) {
             displayName = "max(" + displayName + ")";
-        else if (this.AggregateFunction === AggregateFunction.Min)
+ }
+        else if (this.AggregateFunction === AggregateFunction.Min) {
             displayName = "min(" + displayName + ")";
-        else if (this.AggregateFunction === AggregateFunction.Sum)
+ }
+        else if (this.AggregateFunction === AggregateFunction.Sum) {
             displayName = "sum(" + displayName + ")";
-        else if (this.AggregateFunction === AggregateFunction.SumE)
+ }
+        else if (this.AggregateFunction === AggregateFunction.SumE) {
             displayName = "sumE(" + displayName + ")";
+ }
 
         return displayName;
     }
diff --git a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
index 0b8e0d12c..a197c1305 100644
--- a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
+++ b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
@@ -1,5 +1,5 @@
 import { BackendAttributeModel, AttributeModel, CodeAttributeModel } from "./AttributeModel";
-import { DataType, VisualizationHint } from '../../model/idea/idea'
+import { DataType, VisualizationHint } from '../../model/idea/idea';
 
 export class CalculatedAttributeManager {
     public static AllCalculatedAttributes: Array<AttributeModel> = new Array<AttributeModel>();
diff --git a/src/client/northstar/core/brusher/IBaseBrushable.ts b/src/client/northstar/core/brusher/IBaseBrushable.ts
index 99a36636f..c46db4d22 100644
--- a/src/client/northstar/core/brusher/IBaseBrushable.ts
+++ b/src/client/northstar/core/brusher/IBaseBrushable.ts
@@ -1,6 +1,6 @@
-import { PIXIPoint } from '../../utils/MathUtil'
+import { PIXIPoint } from '../../utils/MathUtil';
 import { IEquatable } from '../../utils/IEquatable';
-import { Document } from '../../../../fields/Document'
+import { Document } from '../../../../fields/Document';
 
 export interface IBaseBrushable<T> extends IEquatable {
     BrusherModels: Array<Document>;
diff --git a/src/client/northstar/core/brusher/IBaseBrusher.ts b/src/client/northstar/core/brusher/IBaseBrusher.ts
index d7ae65464..d2de6ed62 100644
--- a/src/client/northstar/core/brusher/IBaseBrusher.ts
+++ b/src/client/northstar/core/brusher/IBaseBrusher.ts
@@ -1,4 +1,4 @@
-import { PIXIPoint } from '../../utils/MathUtil'
+import { PIXIPoint } from '../../utils/MathUtil';
 import { IEquatable } from '../../utils/IEquatable';
 
 
diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts
index 20fca77f5..e2ba3f652 100644
--- a/src/client/northstar/core/filter/FilterModel.ts
+++ b/src/client/northstar/core/filter/FilterModel.ts
@@ -15,7 +15,7 @@ export class FilterModel {
 
     public Equals(other: FilterModel): boolean {
         if (!Utils.EqualityHelper(this, other)) return false;
-        if (!this.isSame(this.ValueComparisons, (other as FilterModel).ValueComparisons)) return false;
+        if (!this.isSame(this.ValueComparisons, (other).ValueComparisons)) return false;
         return true;
     }
 
diff --git a/src/client/northstar/core/filter/IBaseFilterConsumer.ts b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
index 93f66a154..59d7adf4c 100644
--- a/src/client/northstar/core/filter/IBaseFilterConsumer.ts
+++ b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
@@ -1,5 +1,5 @@
-import { FilterOperand } from '../filter/FilterOperand'
-import { IEquatable } from '../../utils/IEquatable'
+import { FilterOperand } from '../filter/FilterOperand';
+import { IEquatable } from '../../utils/IEquatable';
 import { Document } from "../../../../fields/Document";
 
 export interface IBaseFilterConsumer extends IEquatable {
diff --git a/src/client/northstar/core/filter/IBaseFilterProvider.ts b/src/client/northstar/core/filter/IBaseFilterProvider.ts
index d082bfe12..fc3301b11 100644
--- a/src/client/northstar/core/filter/IBaseFilterProvider.ts
+++ b/src/client/northstar/core/filter/IBaseFilterProvider.ts
@@ -1,4 +1,4 @@
-import { FilterModel } from '../filter/FilterModel'
+import { FilterModel } from '../filter/FilterModel';
 
 export interface IBaseFilterProvider {
     FilterModels: Array<FilterModel>;
diff --git a/src/client/northstar/core/filter/ValueComparision.ts b/src/client/northstar/core/filter/ValueComparision.ts
index 1a3e461f5..80b1242a9 100644
--- a/src/client/northstar/core/filter/ValueComparision.ts
+++ b/src/client/northstar/core/filter/ValueComparision.ts
@@ -1,5 +1,5 @@
-import { Predicate } from '../../model/idea/idea'
-import { Utils } from '../../utils/Utils'
+import { Predicate } from '../../model/idea/idea';
+import { Utils } from '../../utils/Utils';
 import { AttributeModel } from '../attribute/AttributeModel';
 
 export class ValueComparison {
@@ -15,15 +15,19 @@ export class ValueComparison {
     }
 
     public Equals(other: Object): boolean {
-        if (!Utils.EqualityHelper(this, other))
+        if (!Utils.EqualityHelper(this, other)) {
             return false;
-        if (this.Predicate !== (other as ValueComparison).Predicate)
+        }
+        if (this.Predicate !== (other as ValueComparison).Predicate) {
             return false;
+        }
         let isComplex = (typeof this.Value === "object");
-        if (!isComplex && this.Value !== (other as ValueComparison).Value)
+        if (!isComplex && this.Value !== (other as ValueComparison).Value) {
             return false;
-        if (isComplex && !this.Value.Equals((other as ValueComparison).Value))
+        }
+        if (isComplex && !this.Value.Equals((other as ValueComparison).Value)) {
             return false;
+        }
         return true;
     }
 
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index fa2a9c008..6abde4677 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -41,7 +41,7 @@ export class HistogramField extends BasicField<HistogramOperation> {
 
             data: this.toString(),
             _id: this.Id
-        }
+        };
     }
 
     @action
diff --git a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
index cdae90c8b..6291ec1fc 100644
--- a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
+++ b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
 import { ChartType } from '../../northstar/model/binRanges/VisualBinRange';
 import { AggregateFunction, Bin, Brush, DoubleValueAggregateResult, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea";
@@ -29,7 +29,7 @@ export class HistogramBinPrimitiveCollection {
     private _histoBox: HistogramBox;
     private get histoOp() { return this._histoBox.HistoOp; }
     private get histoResult() { return this.histoOp.Result as HistogramResult; }
-    private get sizeConverter() { return this._histoBox.SizeConverter!; }
+    private get sizeConverter() { return this._histoBox.SizeConverter; }
     public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
     public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
 
@@ -99,13 +99,14 @@ export class HistogramBinPrimitiveCollection {
     private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number {
 
         let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!);
-        if (unNormalizedValue === undefined)
+        if (unNormalizedValue === undefined) {
             return brushFactorSum;
+        }
 
         var normalizedValue = (unNormalizedValue - this._histoBox.ValueRange[0]) / (Math.abs((this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0])) < HistogramBinPrimitiveCollection.TOLERANCE ?
             unNormalizedValue : this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0]);
 
-        let allUnNormalizedValue = this.getBinValue(2, bin, ModelHelpers.AllBrushIndex(this.histoResult))
+        let allUnNormalizedValue = this.getBinValue(2, bin, ModelHelpers.AllBrushIndex(this.histoResult));
 
         // bcz: are these calls needed?  
         let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
@@ -145,8 +146,9 @@ export class HistogramBinPrimitiveCollection {
             let [xFrom, xTo] = this.sizeConverter.DataToScreenPointRange(0, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.X, this.histoResult, brush.brushIndex!));
             let [yFrom, yTo] = this.sizeConverter.DataToScreenPointRange(1, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.Y, this.histoResult, brush.brushIndex!));
 
-            if (xFrom !== undefined && yFrom !== undefined && xTo !== undefined && yTo !== undefined)
+            if (xFrom !== undefined && yFrom !== undefined && xTo !== undefined && yTo !== undefined) {
                 this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
+            }
         }
         return 0;
     }
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index ec9413649..7df59ef07 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { action, computed, observable, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import Measure from "react-measure";
@@ -25,7 +25,7 @@ import { StyleConstants } from "../utils/StyleContants";
 
 @observer
 export class HistogramBox extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr) }
+    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr); }
     private _dropXRef = React.createRef<HTMLDivElement>();
     private _dropYRef = React.createRef<HTMLDivElement>();
     private _dropXDisposer?: DragManager.DragDropDisposer;
@@ -92,15 +92,15 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         }
         reaction(() => CurrentUserUtils.NorthstarDBCatalog, (catalog?: Catalog) => this.activateHistogramOperation(catalog), { fireImmediately: true });
         reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges));
-        reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 40))
+        reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 40));
         reaction(() => this.HistogramResult ? this.HistogramResult.binRanges : undefined,
             (binRanges: BinRange[] | undefined) => {
                 if (binRanges) {
                     this.VisualBinRanges.splice(0, this.VisualBinRanges.length, ...binRanges.map((br, ind) =>
-                        VisualBinRangeHelper.GetVisualBinRange(this.HistoOp.Schema!.distinctAttributeParameters, br, this.HistogramResult!, ind ? this.HistoOp.Y : this.HistoOp.X, this.ChartType)));
+                        VisualBinRangeHelper.GetVisualBinRange(this.HistoOp.Schema!.distinctAttributeParameters, br, this.HistogramResult, ind ? this.HistoOp.Y : this.HistoOp.X, this.ChartType)));
 
-                    let valueAggregateKey = ModelHelpers.CreateAggregateKey(this.HistoOp.Schema!.distinctAttributeParameters, this.HistoOp.V, this.HistogramResult!, ModelHelpers.AllBrushIndex(this.HistogramResult!));
-                    this.ValueRange = Object.values(this.HistogramResult!.bins!).reduce((prev, cur) => {
+                    let valueAggregateKey = ModelHelpers.CreateAggregateKey(this.HistoOp.Schema!.distinctAttributeParameters, this.HistoOp.V, this.HistogramResult, ModelHelpers.AllBrushIndex(this.HistogramResult));
+                    this.ValueRange = Object.values(this.HistogramResult.bins!).reduce((prev, cur) => {
                         let value = ModelHelpers.GetAggregateResult(cur, valueAggregateKey) as DoubleValueAggregateResult;
                         return value && value.hasResult ? [Math.min(prev[0], value.result!), Math.max(prev[1], value.result!)] : prev;
                     }, [Number.MAX_VALUE, Number.MIN_VALUE]);
@@ -109,10 +109,12 @@ export class HistogramBox extends React.Component<FieldViewProps> {
     }
 
     componentWillUnmount() {
-        if (this._dropXDisposer)
+        if (this._dropXDisposer) {
             this._dropXDisposer();
-        if (this._dropYDisposer)
+        }
+        if (this._dropYDisposer) {
             this._dropYDisposer();
+        }
     }
 
     activateHistogramOperation(catalog?: Catalog) {
@@ -128,7 +130,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                             this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => {
                                 brush.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length]);
                                 let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
-                                return { l: brush, b: brushed[0].Id === proto.Id ? brushed[1] : brushed[0] }
+                                return { l: brush, b: brushed[0].Id === proto.Id ? brushed[1] : brushed[0] };
                             }));
                         }, { fireImmediately: true });
                     reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
@@ -146,7 +148,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         let roff = this.SizeConverter.RightOffset;
         let boff = this.SizeConverter.BottomOffset;
         return (
-            <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height })}>
+            <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height; })}>
                 {({ measureRef }) =>
                     <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(-50%, -50%)` }}>
                         <div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} >
@@ -168,7 +170,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                     </div>
                 }
             </Measure>
-        )
+        );
     }
 }
 
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
index a78703247..4c5bdb14b 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { computed, observable, reaction, runInAction, trace, action } from "mobx";
 import { observer } from "mobx-react";
 import { Utils as DashUtils } from '../../../Utils';
@@ -28,17 +28,18 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
     @computed get selectedPrimitives() { return this._selectedPrims.map(bp => this.drawRect(bp.Rect, bp.BarAxis, undefined, "border")); }
     @computed get barPrimitives() {
         let histoResult = this.props.HistoBox.HistogramResult;
-        if (!histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length)
+        if (!histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length) {
             return (null);
+        }
         let allBrushIndex = ModelHelpers.AllBrushIndex(histoResult);
         return Object.keys(histoResult.bins).reduce((prims: JSX.Element[], key: string) => {
-            let drawPrims = new HistogramBinPrimitiveCollection(histoResult!.bins![key], this.props.HistoBox);
+            let drawPrims = new HistogramBinPrimitiveCollection(histoResult.bins![key], this.props.HistoBox);
             let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex,
-                ModelHelpers.GetBinFilterModel(histoResult!.bins![key], allBrushIndex, histoResult!, this.histoOp.X, this.histoOp.Y));
+                ModelHelpers.GetBinFilterModel(histoResult.bins![key], allBrushIndex, histoResult, this.histoOp.X, this.histoOp.Y));
             drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(bp =>
                 prims.push(...[{ r: bp.Rect, c: bp.Color }, { r: bp.MarginRect, c: StyleConstants.MARGIN_BARS_COLOR }].map(pair => this.drawRect(pair.r, bp.BarAxis, pair.c, "bar", toggle))));
             return prims;
-        }, [] as JSX.Element[])
+        }, [] as JSX.Element[]);
     }
 
     componentDidMount() {
@@ -47,8 +48,9 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
 
     private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) {
         let rawAllBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex === allBrushIndex);
-        if (!rawAllBrushPrim)
-            return () => { }
+        if (!rawAllBrushPrim) {
+            return () => { };
+        }
         let allBrushPrim = rawAllBrushPrim;
         return () => runInAction(() => {
             if (ArrayUtil.Contains(this.histoOp.FilterModels, filterModel)) {
@@ -59,23 +61,25 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
                 this._selectedPrims.push(allBrushPrim);
                 this.histoOp.AddFilterModels([filterModel]);
             }
-        })
+        });
     }
 
     private renderGridLinesAndLabels(axis: number) {
         trace();
-        if (!this.props.HistoBox.SizeConverter.Initialized)
+        if (!this.props.HistoBox.SizeConverter.Initialized) {
             return (null);
+        }
         let labels = this.props.HistoBox.VisualBinRanges[axis].GetLabels();
         return <svg className="histogramboxprimitives-svgContainer">
             {labels.reduce((prims, binLabel, i) => {
                 let r = this.props.HistoBox.SizeConverter.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis);
                 prims.push(this.drawLine(r.xFrom, r.yFrom, axis === 0 ? 0 : r.xTo - r.xFrom, axis === 0 ? r.yTo - r.yFrom : 0));
-                if (i === labels.length - 1)
+                if (i === labels.length - 1) {
                     prims.push(this.drawLine(axis === 0 ? r.xTo : r.xFrom, axis === 0 ? r.yFrom : r.yTo, axis === 0 ? 0 : r.xTo - r.xFrom, axis === 0 ? r.yTo - r.yFrom : 0));
+                }
                 return prims;
             }, [] as JSX.Element[])}
-        </svg>
+        </svg>;
     }
 
     drawLine(xFrom: number, yFrom: number, width: number, height: number) {
@@ -89,9 +93,9 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
         }
         let trans2Xpercent = `${(xFrom + width) / this.renderDimension * 100}%`;
         let trans2Ypercent = `${(yFrom + height) / this.renderDimension * 100}%`;
-        let trans1Xpercent = `${xFrom / this.renderDimension * 100}%`
-        let trans1Ypercent = `${yFrom / this.renderDimension * 100}%`
-        return <line className="histogramboxprimitives-line" key={DashUtils.GenerateGuid()} x1={trans1Xpercent} x2={`${trans2Xpercent}`} y1={trans1Ypercent} y2={`${trans2Ypercent}`} />
+        let trans1Xpercent = `${xFrom / this.renderDimension * 100}%`;
+        let trans1Ypercent = `${yFrom / this.renderDimension * 100}%`;
+        return <line className="histogramboxprimitives-line" key={DashUtils.GenerateGuid()} x1={trans1Xpercent} x2={`${trans2Xpercent}`} y1={trans1Ypercent} y2={`${trans2Ypercent}`} />;
     }
     drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, classExt: string, tapHandler: () => void = () => { }) {
         if (r.height < 0) {
@@ -102,11 +106,11 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
             r.x += r.width;
             r.width = -r.width;
         }
-        let transXpercent = `${r.x / this.renderDimension * 100}%`
-        let transYpercent = `${r.y / this.renderDimension * 100}%`
+        let transXpercent = `${r.x / this.renderDimension * 100}%`;
+        let transYpercent = `${r.y / this.renderDimension * 100}%`;
         let widthXpercent = `${r.width / this.renderDimension * 100}%`;
         let heightYpercent = `${r.height / this.renderDimension * 100}%`;
-        return (<rect className={`histogramboxprimitives-${classExt}`} key={DashUtils.GenerateGuid()} onPointerDown={(e: React.PointerEvent) => { if (e.button === 0) tapHandler() }}
+        return (<rect className={`histogramboxprimitives-${classExt}`} key={DashUtils.GenerateGuid()} onPointerDown={(e: React.PointerEvent) => { if (e.button === 0) tapHandler(); }}
             x={transXpercent} width={`${widthXpercent}`} y={transYpercent} height={`${heightYpercent}`} fill={color ? `${LABColor.RGBtoHexString(color)}` : "transparent"} />);
     }
     render() {
@@ -118,6 +122,6 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
                 {this.barPrimitives}
                 {this.selectedPrimitives}
             </svg>
-        </div>
+        </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
index dd62e9146..5785fe838 100644
--- a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { action, computed, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Utils as DashUtils } from '../../../Utils';
@@ -13,7 +13,7 @@ import { HistogramPrimitivesProps } from "./HistogramBoxPrimitives";
 export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
     componentDidMount() {
         reaction(() => [this.props.HistoBox.PanelWidth, this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
-            (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0] as number, fields[1] as number, this.props.HistoBox), { fireImmediately: true });
+            (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0], fields[1], this.props.HistoBox), { fireImmediately: true });
     }
 
     @action
@@ -32,8 +32,9 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
     private renderGridLinesAndLabels(axis: number) {
         let sc = this.props.HistoBox.SizeConverter;
         let vb = this.props.HistoBox.VisualBinRanges;
-        if (!vb.length || !sc.Initialized)
+        if (!vb.length || !sc.Initialized) {
             return (null);
+        }
         let dim = (axis === 0 ? this.props.HistoBox.PanelWidth : this.props.HistoBox.PanelHeight) / ((axis === 0 && vb[axis] instanceof NominalVisualBinRange) ?
             (12 + 5) : //  (<number>FontStyles.AxisLabel.fontSize + 5)));
             sc.MaxLabelSizes[axis].coords[axis] + 5);
@@ -52,8 +53,8 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
                     xStart += Math.max(textWidth / 2, (1 - textWidth / space) * textWidth / 2) - textHeight / 2;
                 }
 
-                let xPercent = axis === 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`
-                let yPercent = axis === 0 ? `${this.props.HistoBox.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`
+                let xPercent = axis === 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`;
+                let yPercent = axis === 0 ? `${this.props.HistoBox.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`;
 
                 prims.push(
                     <div className="histogramLabelPrimitives-placer" key={DashUtils.GenerateGuid()} style={{ transform: `translate(${xPercent}, ${yPercent})` }}>
@@ -61,7 +62,7 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
                             {label}
                         </div>
                     </div>
-                )
+                );
             }
             return prims;
         }, [] as JSX.Element[]);
@@ -73,7 +74,7 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
         return <div className="histogramLabelPrimitives-container">
             {xaxislines}
             {yaxislines}
-        </div>
+        </div>;
     }
 
 }
\ No newline at end of file
diff --git a/src/client/northstar/manager/Gateway.ts b/src/client/northstar/manager/Gateway.ts
index ba6fe2ad5..1c8d3fd73 100644
--- a/src/client/northstar/manager/Gateway.ts
+++ b/src/client/northstar/manager/Gateway.ts
@@ -1,4 +1,4 @@
-import { Catalog, OperationReference, Result, CompileResults } from "../model/idea/idea"
+import { Catalog, OperationReference, Result, CompileResults } from "../model/idea/idea";
 import { computed, observable, action } from "mobx";
 
 export class Gateway {
diff --git a/src/client/northstar/model/ModelExtensions.ts b/src/client/northstar/model/ModelExtensions.ts
index e4bf77ed8..29f80d2d1 100644
--- a/src/client/northstar/model/ModelExtensions.ts
+++ b/src/client/northstar/model/ModelExtensions.ts
@@ -1,7 +1,7 @@
-import { AttributeParameters, Brush, MarginAggregateParameters, SingleDimensionAggregateParameters, Solution } from '../model/idea/idea'
-import { Utils } from '../utils/Utils'
+import { AttributeParameters, Brush, MarginAggregateParameters, SingleDimensionAggregateParameters, Solution } from '../model/idea/idea';
+import { Utils } from '../utils/Utils';
 
-import { FilterModel } from '../core/filter/FilterModel'
+import { FilterModel } from '../core/filter/FilterModel';
 
 (SingleDimensionAggregateParameters as any).prototype.Equals = function (other: Object) {
     if (!Utils.EqualityHelper(this, other)) return false;
@@ -9,13 +9,13 @@ import { FilterModel } from '../core/filter/FilterModel'
         (other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
     if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any).Equals((other as SingleDimensionAggregateParameters).attributeParameters)) return false;
     return true;
-}
+};
 
 {
     (AttributeParameters as any).prototype.Equals = function (other: AttributeParameters) {
-        return (<any>this).constructor.name === (<any>other).constructor.name &&
+        return (this).constructor.name === (<any>other).constructor.name &&
             this.rawName === other.rawName;
-    }
+    };
 }
 
 {
@@ -23,7 +23,7 @@ import { FilterModel } from '../core/filter/FilterModel'
         if (!Utils.EqualityHelper(this, other)) return false;
         if ((this as Solution).solutionId !== (other as Solution).solutionId) return false;
         return true;
-    }
+    };
 }
 
 {
@@ -35,7 +35,7 @@ import { FilterModel } from '../core/filter/FilterModel'
 
         if ((this as MarginAggregateParameters).aggregateFunction !== (other as MarginAggregateParameters).aggregateFunction) return false;
         return true;
-    }
+    };
 }
 
 {
@@ -44,5 +44,5 @@ import { FilterModel } from '../core/filter/FilterModel'
         if ((this as Brush).brushEnum !== (other as Brush).brushEnum) return false;
         if ((this as Brush).brushIndex !== (other as Brush).brushIndex) return false;
         return true;
-    }
+    };
 }
\ No newline at end of file
diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts
index 1a58e6180..ac807b41f 100644
--- a/src/client/northstar/model/ModelHelpers.ts
+++ b/src/client/northstar/model/ModelHelpers.ts
@@ -64,7 +64,7 @@ export class ModelHelpers {
             if (aggParams) {
                 aggregateParameters.push(aggParams);
 
-                var margin = new MarginAggregateParameters()
+                var margin = new MarginAggregateParameters();
                 margin.aggregateFunction = agg.AggregateFunction;
                 margin.attributeParameters = ModelHelpers.GetAttributeParameters(agg.AttributeModel);
                 margin.distinctAttributeParameters = distinctAttributeParameters;
@@ -106,7 +106,7 @@ export class ModelHelpers {
                 {
                     rawName: am.CodeName,
                     visualizationHints: am.VisualizationHints,
-                    id: (am as BackendAttributeModel).Id
+                    id: (am).Id
                 });
         }
         else if (am instanceof CodeAttributeModel) {
@@ -114,11 +114,11 @@ export class ModelHelpers {
                 {
                     rawName: am.CodeName,
                     visualizationHints: am.VisualizationHints,
-                    code: (am as CodeAttributeModel).Code
+                    code: (am).Code
                 });
         }
         else {
-            throw new Exception()
+            throw new Exception();
         }
     }
 
diff --git a/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
index 995bf4e0b..120b034f2 100644
--- a/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
@@ -1,5 +1,5 @@
-import { AlphabeticBinRange, BinLabel } from '../../model/idea/idea'
-import { VisualBinRange } from './VisualBinRange'
+import { AlphabeticBinRange, BinLabel } from '../../model/idea/idea';
+import { VisualBinRange } from './VisualBinRange';
 
 export class AlphabeticVisualBinRange extends VisualBinRange {
     public DataBinRange: AlphabeticBinRange;
diff --git a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
index f5872aa4c..776e643cd 100644
--- a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
@@ -1,5 +1,5 @@
-import { DateTimeBinRange, DateTimeStep, DateTimeStepGranularity } from '../idea/idea'
-import { VisualBinRange } from './VisualBinRange'
+import { DateTimeBinRange, DateTimeStep, DateTimeStepGranularity } from '../idea/idea';
+import { VisualBinRange } from './VisualBinRange';
 
 export class DateTimeVisualBinRange extends VisualBinRange {
     public DataBinRange: DateTimeBinRange;
diff --git a/src/client/northstar/model/binRanges/NominalVisualBinRange.ts b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
index 407ff3ea6..42509d797 100644
--- a/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
@@ -1,5 +1,5 @@
-import { NominalBinRange, BinLabel } from '../../model/idea/idea'
-import { VisualBinRange } from './VisualBinRange'
+import { NominalBinRange, BinLabel } from '../../model/idea/idea';
+import { VisualBinRange } from './VisualBinRange';
 
 export class NominalVisualBinRange extends VisualBinRange {
     public DataBinRange: NominalBinRange;
diff --git a/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
index 80886416b..c579c8e5f 100644
--- a/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
@@ -1,4 +1,4 @@
-import { QuantitativeBinRange } from '../idea/idea'
+import { QuantitativeBinRange } from '../idea/idea';
 import { VisualBinRange } from './VisualBinRange';
 import { format } from "d3-format";
 
diff --git a/src/client/northstar/model/binRanges/VisualBinRange.ts b/src/client/northstar/model/binRanges/VisualBinRange.ts
index a0766e494..449a22e91 100644
--- a/src/client/northstar/model/binRanges/VisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/VisualBinRange.ts
@@ -1,4 +1,4 @@
-import { BinLabel } from '../../model/idea/idea'
+import { BinLabel } from '../../model/idea/idea';
 
 export abstract class VisualBinRange {
 
diff --git a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
index 63ee61909..9671e55f8 100644
--- a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
+++ b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
@@ -16,18 +16,18 @@ export class VisualBinRangeHelper {
 
     public static GetNonAggregateVisualBinRange(dataBinRange: BinRange): VisualBinRange {
         if (dataBinRange instanceof NominalBinRange) {
-            return new NominalVisualBinRange(dataBinRange as NominalBinRange);
+            return new NominalVisualBinRange(dataBinRange);
         }
         else if (dataBinRange instanceof QuantitativeBinRange) {
-            return new QuantitativeVisualBinRange(dataBinRange as QuantitativeBinRange);
+            return new QuantitativeVisualBinRange(dataBinRange);
         }
         else if (dataBinRange instanceof AlphabeticBinRange) {
-            return new AlphabeticVisualBinRange(dataBinRange as AlphabeticBinRange);
+            return new AlphabeticVisualBinRange(dataBinRange);
         }
         else if (dataBinRange instanceof DateTimeBinRange) {
-            return new DateTimeVisualBinRange(dataBinRange as DateTimeBinRange);
+            return new DateTimeVisualBinRange(dataBinRange);
         }
-        throw new Exception()
+        throw new Exception();
     }
 
     public static GetVisualBinRange(distinctAttributeParameters: AttributeParameters | undefined, dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange {
@@ -51,13 +51,13 @@ export class VisualBinRangeHelper {
                         }
                     }
                 }
-            };
+            }
 
             let visualBinRange = QuantitativeVisualBinRange.Initialize(minValue, maxValue, 10, false);
 
             if (chartType === ChartType.HorizontalBar || chartType === ChartType.VerticalBar) {
                 visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue),
-                    Math.max(0, (visualBinRange as QuantitativeVisualBinRange).DataBinRange.maxValue!),
+                    Math.max(0, (visualBinRange).DataBinRange.maxValue!),
                     SETTINGS_X_BINS, false);
             }
             else if (chartType === ChartType.SinglePoint) {
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
index a14337763..c6d5f0a15 100644
--- a/src/client/northstar/operations/BaseOperation.ts
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -1,4 +1,4 @@
-import { FilterModel } from '../core/filter/FilterModel'
+import { FilterModel } from '../core/filter/FilterModel';
 import { ErrorResult, Exception, OperationParameters, OperationReference, Result, ResultParameters } from '../model/idea/idea';
 import { action, computed, observable } from "mobx";
 import { Gateway } from '../manager/Gateway';
@@ -62,8 +62,9 @@ export abstract class BaseOperation {
             }
 
             let operationParameters = this.CreateOperationParameters();
-            if (this.Result)
-                this.Result.progress = 0; // bcz: used to set Result to undefined, but that causes the display to blink
+            if (this.Result) {
+                this.Result.progress = 0;
+            } // bcz: used to set Result to undefined, but that causes the display to blink
             this.Error = "";
             let salt = Math.random().toString();
             this.RequestSalt = salt;
@@ -97,7 +98,7 @@ export abstract class BaseOperation {
                 pollPromise.Start(async () => {
                     let result = await Gateway.Instance.GetResult(resultParameters.toJSON());
                     if (result instanceof ErrorResult) {
-                        throw new Error((result as ErrorResult).message);
+                        throw new Error((result).message);
                     }
                     if (this.RequestSalt === pollPromise.RequestSalt) {
                         if (result && (!this.Result || this.Result.progress !== result.progress)) {
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index a3ddc1c98..760106023 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -62,7 +62,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     @computed
     public get FilterString(): string {
         let filterModels: FilterModel[] = [];
-        return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true)
+        return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true);
     }
 
     @computed
diff --git a/src/client/northstar/utils/ArrayUtil.ts b/src/client/northstar/utils/ArrayUtil.ts
index a52ca6d96..12b8d8e77 100644
--- a/src/client/northstar/utils/ArrayUtil.ts
+++ b/src/client/northstar/utils/ArrayUtil.ts
@@ -50,7 +50,7 @@ export class ArrayUtil {
         if (filtered.length > 0) {
             return filtered[0];
         }
-        throw new Exception()
+        throw new Exception();
     }
 
     public static FirstOrDefault<T>(arr: T[], predicate: (x: any) => boolean): T | undefined {
diff --git a/src/client/northstar/utils/Extensions.ts b/src/client/northstar/utils/Extensions.ts
index 7c2b7fc9d..df14d4da0 100644
--- a/src/client/northstar/utils/Extensions.ts
+++ b/src/client/northstar/utils/Extensions.ts
@@ -6,7 +6,7 @@ interface String {
 String.prototype.ReplaceAll = function (toReplace: string, replacement: string): string {
     var target = this;
     return target.split(toReplace).join(replacement);
-}
+};
 
 String.prototype.Truncate = function (length: number, replacement: string): String {
     var target = this;
@@ -14,7 +14,7 @@ String.prototype.Truncate = function (length: number, replacement: string): Stri
         target = target.slice(0, Math.max(0, length - replacement.length)) + replacement;
     }
     return target;
-}
+};
 
 interface Math {
     log10(val: number): number;
@@ -22,7 +22,7 @@ interface Math {
 
 Math.log10 = function (val: number): number {
     return Math.log(val) / Math.LN10;
-}
+};
 
 declare interface ObjectConstructor {
     assign(...objects: Object[]): Object;
diff --git a/src/client/northstar/utils/GeometryUtil.ts b/src/client/northstar/utils/GeometryUtil.ts
index 6d8acea20..d5220c479 100644
--- a/src/client/northstar/utils/GeometryUtil.ts
+++ b/src/client/northstar/utils/GeometryUtil.ts
@@ -8,15 +8,19 @@ export class GeometryUtil {
         let minY: number = Number.MAX_VALUE;
         let maxX: number = Number.MIN_VALUE;
         let maxY: number = Number.MIN_VALUE;
-        for (var i = 0; i < points.length; i++) {
-            if (points[i].x < minX)
-                minX = points[i].x;
-            if (points[i].y < minY)
-                minY = points[i].y;
-            if (points[i].x > maxX)
-                maxX = points[i].x;
-            if (points[i].y > maxY)
-                maxY = points[i].y;
+        for (const point of points) {
+            if (point.x < minX) {
+                minX = point.x;
+            }
+            if (point.y < minY) {
+                minY = point.y;
+            }
+            if (point.x > maxX) {
+                maxX = point.x;
+            }
+            if (point.y > maxY) {
+                maxY = point.y;
+            }
         }
         return new PIXIRectangle(minX * scale - padding, minY * scale - padding, (maxX - minX) * scale + padding * 2, (maxY - minY) * scale + padding * 2);
     }
@@ -35,7 +39,7 @@ export class GeometryUtil {
                 nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
                 ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
             return new PIXIPoint(nx, ny);
-        }
+        };
         return points.map(p => rotate(center.x, center.y, p.x, p.y, angle));
     }
 
@@ -54,9 +58,9 @@ export class GeometryUtil {
             return [];
         }
 
-        for (let v = 0; v < points.length; v++) {
-            x = points[v].x;
-            y = points[v].y;
+        for (const point of points) {
+            x = point.x;
+            y = point.y;
             sum_x += x;
             sum_y += y;
             sum_xx += x * x;
@@ -68,8 +72,8 @@ export class GeometryUtil {
         let b = (sum_y / count) - (m * sum_x) / count;
         let result: PIXIPoint[] = new Array<PIXIPoint>();
 
-        for (let v = 0; v < points.length; v++) {
-            x = points[v].x;
+        for (const point of points) {
+            x = point.x;
             y = x * m + b;
             result.push(new PIXIPoint(x, y));
         }
diff --git a/src/client/northstar/utils/MathUtil.ts b/src/client/northstar/utils/MathUtil.ts
index 7aa255096..4b44f40c3 100644
--- a/src/client/northstar/utils/MathUtil.ts
+++ b/src/client/northstar/utils/MathUtil.ts
@@ -16,11 +16,11 @@ export class PIXIRectangle {
     public x: number;
     public y: number;
     public width: number;
-    public height: number
-    public get left() { return this.x }
+    public height: number;
+    public get left() { return this.x; }
     public get right() { return this.x + this.width; }
-    public get top() { return this.y }
-    public get bottom() { return this.top + this.height }
+    public get top() { return this.y; }
+    public get bottom() { return this.top + this.height; }
     public static get EMPTY() { return new PIXIRectangle(0, 0, -1, -1); }
     constructor(x: number, y: number, width: number, height: number) {
         this.x = x;
@@ -127,14 +127,18 @@ export class MathUtil {
     }
 
     public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean {
-        if (p.x < rect.left - this.EPSILON)
+        if (p.x < rect.left - this.EPSILON) {
             return false;
-        if (p.x > rect.right + this.EPSILON)
+        }
+        if (p.x > rect.right + this.EPSILON) {
             return false;
-        if (p.y < rect.top - this.EPSILON)
+        }
+        if (p.y < rect.top - this.EPSILON) {
             return false;
-        if (p.y > rect.bottom + this.EPSILON)
+        }
+        if (p.y > rect.bottom + this.EPSILON) {
             return false;
+        }
 
         return true;
     }
@@ -145,23 +149,27 @@ export class MathUtil {
         var r3 = new PIXIPoint(rect.right, rect.bottom);
         var r4 = new PIXIPoint(rect.left, rect.bottom);
         var ret = new Array<PIXIPoint>();
-        var dist = this.Dist(lineFrom, lineTo)
+        var dist = this.Dist(lineFrom, lineTo);
         var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2);
         if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
             ret.push(inter);
+        }
         inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3);
         if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
             ret.push(inter);
+        }
         inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4);
         if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
             ret.push(inter);
+        }
         inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1);
         if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
             ret.push(inter);
+        }
         return ret;
     }
 
@@ -178,7 +186,7 @@ export class MathUtil {
     }
 
     public static Dot(p1: PIXIPoint, p2: PIXIPoint): number {
-        return p1.x * p2.x + p1.y * p2.y
+        return p1.x * p2.x + p1.y * p2.y;
     }
 
     public static Normalize(p1: PIXIPoint) {
@@ -193,7 +201,7 @@ export class MathUtil {
     public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number {
         const a = p1.x - p2.x;
         const b = p1.y - p2.y;
-        return (a * a + b * b)
+        return (a * a + b * b);
     }
 
     public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean {
diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts
index b5c4a16ab..a52890ed9 100644
--- a/src/client/northstar/utils/SizeConverter.ts
+++ b/src/client/northstar/utils/SizeConverter.ts
@@ -32,8 +32,8 @@ export class SizeConverter {
         this.Initialized++;
         var xLabels = visualBinRanges[0].GetLabels();
         var yLabels = visualBinRanges[1].GetLabels();
-        var xLabelStrings = xLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length });
-        var yLabelStrings = yLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length });
+        var xLabelStrings = xLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length; });
+        var yLabelStrings = yLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length; });
 
         var metricsX = { width: 75 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number,
         //xLabelStrings[0]!.slice(0, 20)) // StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS));
@@ -62,19 +62,20 @@ export class SizeConverter {
 
     public DataToScreenPointRange(axis: number, bin: Bin, aggregateKey: AggregateKey) {
         var value = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult;
-        if (value && value.hasResult)
+        if (value && value.hasResult) {
             return [this.DataToScreenCoord(value.result!, axis) - 5,
             this.DataToScreenCoord(value.result!, axis) + 5];
+        }
         return [undefined, undefined];
     }
 
     public DataToScreenXAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) {
         var value = visualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![index]);
-        return [this.DataToScreenX(value), this.DataToScreenX(visualBinRanges[index].AddStep(value))]
+        return [this.DataToScreenX(value), this.DataToScreenX(visualBinRanges[index].AddStep(value))];
     }
     public DataToScreenYAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) {
         var value = visualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![index]);
-        return [this.DataToScreenY(value), this.DataToScreenY(visualBinRanges[index].AddStep(value))]
+        return [this.DataToScreenY(value), this.DataToScreenY(visualBinRanges[index].AddStep(value))];
     }
 
     public DataToScreenX(x: number): number {
@@ -85,8 +86,9 @@ export class SizeConverter {
         return flip ? (this.RenderDimension) - retY : retY;
     }
     public DataToScreenCoord(v: number, axis: number) {
-        if (axis === 0)
+        if (axis === 0) {
             return this.DataToScreenX(v);
+        }
         return this.DataToScreenY(v);
     }
     public DataToScreenRange(minVal: number, maxVal: number, axis: number) {
@@ -94,6 +96,6 @@ export class SizeConverter {
         let xTo = this.DataToScreenX(axis === 0 ? maxVal : this.DataMaxs[0]);
         let yFrom = this.DataToScreenY(axis === 1 ? minVal : this.DataMins[1]);
         let yTo = this.DataToScreenY(axis === 1 ? maxVal : this.DataMaxs[1]);
-        return { xFrom, yFrom, xTo, yTo }
+        return { xFrom, yFrom, xTo, yTo };
     }
 }
\ No newline at end of file
diff --git a/src/client/northstar/utils/StyleContants.ts b/src/client/northstar/utils/StyleContants.ts
index ac8617e3b..e9b6e0297 100644
--- a/src/client/northstar/utils/StyleContants.ts
+++ b/src/client/northstar/utils/StyleContants.ts
@@ -11,7 +11,7 @@ export class StyleConstants {
 
     static OPERATOR_MENU_LARGE: number = 35;
     static OPERATOR_MENU_SMALL: number = 25;
-    static BRUSH_PALETTE: number[] = [0x42b43c, 0xfa217f, 0x6a9c75, 0xfb5de7, 0x25b8ea, 0x9b5bc4, 0xda9f63, 0xe23209, 0xfb899b, 0x94a6fd]
+    static BRUSH_PALETTE: number[] = [0x42b43c, 0xfa217f, 0x6a9c75, 0xfb5de7, 0x25b8ea, 0x9b5bc4, 0xda9f63, 0xe23209, 0xfb899b, 0x94a6fd];
     static GAP: number = 3;
 
     static BACKGROUND_COLOR: number = 0xF3F3F3;
diff --git a/src/client/northstar/utils/Utils.ts b/src/client/northstar/utils/Utils.ts
index c96b4cbd9..d071dec62 100644
--- a/src/client/northstar/utils/Utils.ts
+++ b/src/client/northstar/utils/Utils.ts
@@ -1,7 +1,7 @@
-import { IBaseBrushable } from '../core/brusher/IBaseBrushable'
-import { IBaseFilterConsumer } from '../core/filter/IBaseFilterConsumer'
-import { IBaseFilterProvider } from '../core/filter/IBaseFilterProvider'
-import { AggregateFunction } from '../model/idea/idea'
+import { IBaseBrushable } from '../core/brusher/IBaseBrushable';
+import { IBaseFilterConsumer } from '../core/filter/IBaseFilterConsumer';
+import { IBaseFilterProvider } from '../core/filter/IBaseFilterProvider';
+import { AggregateFunction } from '../model/idea/idea';
 
 export class Utils {
 
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index fb489edb6..f38b8ca75 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,7 +1,7 @@
-import React = require('react')
+import React = require('react');
 import { observer } from 'mobx-react';
 import { observable, action, computed } from 'mobx';
-import { Document } from "../../fields/Document"
+import { Document } from "../../fields/Document";
 import { DocumentView } from '../views/nodes/DocumentView';
 import { KeyStore } from '../../fields/KeyStore';
 import { FieldWaiting } from '../../fields/Field';
@@ -50,7 +50,7 @@ export class DocumentManager {
             if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) {
                 toReturn = view;
             }
-        })
+        });
 
         return (toReturn);
     }
@@ -71,7 +71,7 @@ export class DocumentManager {
                     toReturn.push(view);
                 }
             }
-        })
+        });
 
         return (toReturn);
     }
@@ -86,8 +86,8 @@ export class DocumentManager {
                         let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document);
                         if (linkToDoc && linkToDoc !== FieldWaiting) {
                             DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
-                                pairs.push({ a: dv, b: docView1, l: link })
-                            })
+                                pairs.push({ a: dv, b: docView1, l: link });
+                            });
                         }
                     }
                     return pairs;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index f95b2c29d..4849ae9f7 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -103,7 +103,7 @@ export namespace DragManager {
         element.addEventListener("dashOnDrop", handler);
         return () => {
             element.removeEventListener("dashOnDrop", handler);
-            delete element.dataset.canDrop
+            delete element.dataset.canDrop;
         };
     }
 
@@ -216,8 +216,9 @@ export namespace DragManager {
         const moveHandler = (e: PointerEvent) => {
             e.stopPropagation();
             e.preventDefault();
-            if (dragData instanceof DocumentDragData)
+            if (dragData instanceof DocumentDragData) {
                 dragData.aliasOnDrop = e.ctrlKey || e.altKey;
+            }
             if (e.shiftKey) {
                 abortDrag();
                 CollectionDockingView.Instance.StartOtherDrag(docs, {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 2a3c1da6e..92944bec0 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,13 +1,13 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model"
-import { joinUp, lift, setBlockType, toggleMark, wrapIn } from 'prosemirror-commands'
-import { redo, undo } from 'prosemirror-history'
-import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list'
+import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model";
+import { joinUp, lift, setBlockType, toggleMark, wrapIn } from 'prosemirror-commands';
+import { redo, undo } from 'prosemirror-history';
+import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list';
 import { EditorState, Transaction, NodeSelection, } from "prosemirror-state";
 import { EditorView, } from "prosemirror-view";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
-  preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]
+  preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
 
 
 // :: Object
@@ -24,7 +24,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     content: "inline*",
     group: "block",
     parseDOM: [{ tag: "p" }],
-    toDOM() { return pDOM }
+    toDOM() { return pDOM; }
   },
 
   // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
@@ -33,14 +33,14 @@ export const nodes: { [index: string]: NodeSpec } = {
     group: "block",
     defining: true,
     parseDOM: [{ tag: "blockquote" }],
-    toDOM() { return blockquoteDOM }
+    toDOM() { return blockquoteDOM; }
   },
 
   // :: NodeSpec A horizontal rule (`<hr>`).
   horizontal_rule: {
     group: "block",
     parseDOM: [{ tag: "hr" }],
-    toDOM() { return hrDOM }
+    toDOM() { return hrDOM; }
   },
 
   // :: NodeSpec A heading textblock, with a `level` attribute that
@@ -57,7 +57,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     { tag: "h4", attrs: { level: 4 } },
     { tag: "h5", attrs: { level: 5 } },
     { tag: "h6", attrs: { level: 6 } }],
-    toDOM(node: any) { return ["h" + node.attrs.level, 0] }
+    toDOM(node: any) { return ["h" + node.attrs.level, 0]; }
   },
 
   // :: NodeSpec A code listing. Disallows marks or non-text inline
@@ -70,7 +70,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     code: true,
     defining: true,
     parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
-    toDOM() { return preDOM }
+    toDOM() { return preDOM; }
   },
 
   // :: NodeSpec The text node.
@@ -96,10 +96,10 @@ export const nodes: { [index: string]: NodeSpec } = {
           src: dom.getAttribute("src"),
           title: dom.getAttribute("title"),
           alt: dom.getAttribute("alt")
-        }
+        };
       }
     }],
-    toDOM(node: any) { return ["img", node.attrs] }
+    toDOM(node: any) { return ["img", node.attrs]; }
   },
 
   // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
@@ -108,7 +108,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     group: "inline",
     selectable: false,
     parseDOM: [{ tag: "br" }],
-    toDOM() { return brDOM }
+    toDOM() { return brDOM; }
   },
 
   ordered_list: {
@@ -136,7 +136,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     ...listItem,
     content: 'paragraph block*'
   }
-}
+};
 
 const emDOM: DOMOutputSpecArray = ["em", 0];
 const strongDOM: DOMOutputSpecArray = ["strong", 0];
@@ -156,17 +156,17 @@ export const marks: { [index: string]: MarkSpec } = {
     inclusive: false,
     parseDOM: [{
       tag: "a[href]", getAttrs(dom: any) {
-        return { href: dom.getAttribute("href"), title: dom.getAttribute("title") }
+        return { href: dom.getAttribute("href"), title: dom.getAttribute("title") };
       }
     }],
-    toDOM(node: any) { return ["a", node.attrs, 0] }
+    toDOM(node: any) { return ["a", node.attrs, 0]; }
   },
 
   // :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
   // Has parse rules that also match `<i>` and `font-style: italic`.
   em: {
     parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }],
-    toDOM() { return emDOM }
+    toDOM() { return emDOM; }
   },
 
   // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
@@ -175,7 +175,7 @@ export const marks: { [index: string]: MarkSpec } = {
     parseDOM: [{ tag: "strong" },
     { tag: "b" },
     { style: "font-weight" }],
-    toDOM() { return strongDOM }
+    toDOM() { return strongDOM; }
   },
 
   underline: {
@@ -221,9 +221,9 @@ export const marks: { [index: string]: MarkSpec } = {
   // :: MarkSpec Code font mark. Represented as a `<code>` element.
   code: {
     parseDOM: [{ tag: "code" }],
-    toDOM() { return codeDOM }
+    toDOM() { return codeDOM; }
   }
-}
+};
 
 // :: Schema
 // This schema rougly corresponds to the document schema used by
@@ -233,4 +233,4 @@ export const marks: { [index: string]: MarkSpec } = {
 //
 // To reuse elements from this schema, extend or read from its
 // `spec.nodes` and `spec.marks` [properties](#model.Schema.spec).
-export const schema = new Schema({ nodes, marks })
\ No newline at end of file
+export const schema = new Schema({ nodes, marks });
\ No newline at end of file
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index bf9b8266f..468484928 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -14,7 +14,7 @@ import { ListField } from "../../fields/ListField";
 // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
 
 // @ts-ignore
-import * as typescriptlib from '!!raw-loader!./type_decls.d'
+import * as typescriptlib from '!!raw-loader!./type_decls.d';
 import { Documents } from "../documents/Documents";
 import { Key } from "../../fields/Key";
 
@@ -50,7 +50,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
 
     let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField, Key];
     let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)];
-    let params: any[] = [KeyStore, Documents, ...fieldTypes]
+    let params: any[] = [KeyStore, Documents, ...fieldTypes];
     let compiledFunction = new Function(...paramNames, `return ${script}`);
     let run = (args: { [name: string]: any } = {}): ScriptResult => {
         let argsArray: any[] = [];
@@ -67,7 +67,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
         } catch (error) {
             return { success: false, error };
         }
-    }
+    };
     return { compiled: true, run };
 }
 
@@ -90,14 +90,14 @@ class ScriptingCompilerHost {
     }
     // getDefaultLibFileName(options: ts.CompilerOptions): string {
     getDefaultLibFileName(options: any): string {
-        return 'node_modules/typescript/lib/lib.d.ts' // No idea what this means...
+        return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
     }
     writeFile(fileName: string, content: string) {
         const file = this.files.find(file => file.fileName === fileName);
         if (file) {
             file.content = content;
         } else {
-            this.files.push({ fileName, content })
+            this.files.push({ fileName, content });
         }
     }
     getCurrentDirectory(): string {
diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx
index b6b088170..a209874a3 100644
--- a/src/client/util/ScrollBox.tsx
+++ b/src/client/util/ScrollBox.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 
 export class ScrollBox extends React.Component {
     onWheel = (e: React.WheelEvent) => {
@@ -16,6 +16,6 @@ export class ScrollBox extends React.Component {
             }} 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
index 1bb15c86a..5ddaafc72 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -23,7 +23,7 @@ export namespace SelectionManager {
 
         @action
         DeselectAll(): void {
-            manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false))
+            manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false));
             manager.SelectedDocuments = [];
         }
     }
@@ -46,7 +46,7 @@ export namespace SelectionManager {
             }
         }
 
-        manager.DeselectAll()
+        manager.DeselectAll();
         if (found) manager.SelectDoc(found, false);
         Main.Instance.SetTextDoc(undefined, undefined);
     }
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 913472aa0..bd5753093 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -5,12 +5,12 @@ import { keymap } from "prosemirror-keymap";
 import { EditorState, Transaction, NodeSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { schema } from "./RichTextSchema";
-import { Schema, NodeType } from "prosemirror-model"
-import React = require("react")
+import { Schema, NodeType } from "prosemirror-model";
+import React = require("react");
 import "./TooltipTextMenu.scss";
 const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
-import { library } from '@fortawesome/fontawesome-svg-core'
-import { wrapInList, bulletList } from 'prosemirror-schema-list'
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { wrapInList, bulletList } from 'prosemirror-schema-list';
 import { faListUl } from '@fortawesome/free-solid-svg-icons';
 
 
@@ -39,7 +39,7 @@ export class TooltipTextMenu {
             { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") },
             //this doesn't work currently - look into notion of active block
             { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
-        ]
+        ];
         items.forEach(({ dom }) => this.tooltip.appendChild(dom));
 
         //pointer down handler to activate button effects
@@ -52,8 +52,8 @@ export class TooltipTextMenu {
                     //uncomment this if we want the bullet button to disappear if current selection is bulleted
                     // dom.style.display = active ? "" : "none"
                 }
-            })
-        })
+            });
+        });
 
         this.update(view, undefined);
     }
@@ -99,32 +99,32 @@ export class TooltipTextMenu {
         return {
             command: setBlockType(schema.nodes.heading, { level }),
             dom: this.icon("H" + level, "heading")
-        }
+        };
     }
 
     //updates the tooltip menu when the selection changes
     update(view: EditorView, lastState: EditorState | undefined) {
-        let state = view.state
+        let state = view.state;
         // Don't do anything if the document/selection didn't change
         if (lastState && lastState.doc.eq(state.doc) &&
-            lastState.selection.eq(state.selection)) return
+            lastState.selection.eq(state.selection)) return;
 
         // Hide the tooltip if the selection is empty
         if (state.selection.empty) {
-            this.tooltip.style.display = "none"
-            return
+            this.tooltip.style.display = "none";
+            return;
         }
 
         // Otherwise, reposition it and update its content
-        this.tooltip.style.display = ""
-        let { from, to } = state.selection
-        let start = view.coordsAtPos(from), end = view.coordsAtPos(to)
+        this.tooltip.style.display = "";
+        let { from, to } = state.selection;
+        let start = view.coordsAtPos(from), end = view.coordsAtPos(to);
         // The box in which the tooltip is positioned, to use as base
-        let box = this.tooltip.offsetParent!.getBoundingClientRect()
+        let box = this.tooltip.offsetParent!.getBoundingClientRect();
         // Find a center-ish x position from the selection endpoints (when
         // crossing lines, end may be more to the left)
-        let left = Math.max((start.left + end.left) / 2, start.left + 3)
-        this.tooltip.style.left = (left - box.left) + "px"
+        let left = Math.max((start.left + end.left) / 2, start.left + 3);
+        this.tooltip.style.left = (left - box.left) + "px";
         let width = Math.abs(start.left - end.left) / 2;
         let mid = Math.min(start.left, end.left) + width;
 
@@ -133,5 +133,5 @@ export class TooltipTextMenu {
         this.tooltip.style.bottom = (box.bottom - start.top) + "px";
     }
 
-    destroy() { this.tooltip.remove() }
+    destroy() { this.tooltip.remove(); }
 }
\ No newline at end of file
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index ed4282874..e9170ec36 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -62,19 +62,19 @@ export class Transform {
         return this;
     }
 
-    translated = (x: number, y: number): Transform => this.copy().translate(x, y)
+    translated = (x: number, y: number): Transform => this.copy().translate(x, y);
 
-    preTranslated = (x: number, y: number): Transform => this.copy().preTranslate(x, y)
+    preTranslated = (x: number, y: number): Transform => this.copy().preTranslate(x, y);
 
-    scaled = (scale: number): Transform => this.copy().scale(scale)
+    scaled = (scale: number): Transform => this.copy().scale(scale);
 
-    scaledAbout = (scale: number, x: number, y: number): Transform => this.copy().scaleAbout(scale, x, y)
+    scaledAbout = (scale: number, x: number, y: number): Transform => this.copy().scaleAbout(scale, x, y);
 
-    preScaled = (scale: number): Transform => this.copy().preScale(scale)
+    preScaled = (scale: number): Transform => this.copy().preScale(scale);
 
-    transformed = (transform: Transform): Transform => this.copy().transform(transform)
+    transformed = (transform: Transform): Transform => this.copy().transform(transform);
 
-    preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform)
+    preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform);
 
     transformPoint = (x: number, y: number): [number, number] => {
         x *= this._scale;
@@ -84,7 +84,7 @@ export class Transform {
         return [x, y];
     }
 
-    transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale]
+    transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale];
 
     transformBounds(x: number, y: number, width: number, height: number): { x: number, y: number, width: number, height: number } {
         [x, y] = this.transformPoint(x, y);
@@ -92,8 +92,8 @@ export class Transform {
         return { x, y, width, height };
     }
 
-    inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale)
+    inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale);
 
-    copy = () => new Transform(this._translateX, this._translateY, this._scale)
+    copy = () => new Transform(this._translateX, this._translateY, this._scale);
 
 }
\ No newline at end of file
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
index 1b251da25..532ba78eb 100644
--- a/src/client/util/TypedEvent.ts
+++ b/src/client/util/TypedEvent.ts
@@ -36,5 +36,5 @@ export class TypedEvent<T> {
     this.listenersOncer = [];
   }
 
-  pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e))
+  pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e));
 }
\ No newline at end of file
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index b4ea4acae..27aed4bac 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,5 +1,5 @@
 import { observable, action } from "mobx";
-import 'source-map-support/register'
+import 'source-map-support/register';
 import { Without } from "../../Utils";
 import { string } from "prop-types";
 
@@ -31,9 +31,9 @@ function propertyDecorator(target: any, key: string | symbol) {
                         batch.end();
                     }
                 }
-            })
+            });
         }
-    })
+    });
 }
 
 export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any;
@@ -43,11 +43,11 @@ export function undoBatch(target: any, key?: string | symbol, descriptor?: Typed
         return function () {
             let batch = UndoManager.StartBatch("");
             try {
-                return target.apply(undefined, arguments)
+                return target.apply(undefined, arguments);
             } finally {
                 batch.end();
             }
-        }
+        };
     }
     if (!descriptor) {
         propertyDecorator(target, key);
@@ -58,11 +58,11 @@ export function undoBatch(target: any, key?: string | symbol, descriptor?: Typed
     descriptor.value = function (...args: any[]) {
         let batch = UndoManager.StartBatch(getBatchName(target, key));
         try {
-            return oldFunction.apply(this, args)
+            return oldFunction.apply(this, args);
         } finally {
             batch.end();
         }
-    }
+    };
 
     return descriptor;
 }
@@ -117,8 +117,8 @@ export namespace UndoManager {
             EndBatch(cancel);
         }
 
-        end = () => { this.dispose(false); }
-        cancel = () => { this.dispose(true); }
+        end = () => { this.dispose(false); };
+        cancel = () => { this.dispose(true); };
     }
 
     export function StartBatch(batchName: string): Batch {
@@ -138,7 +138,7 @@ export namespace UndoManager {
             redoStack.length = 0;
             currentBatch = undefined;
         }
-    })
+    });
 
     export function RunInBatch(fn: () => void, batchName: string) {
         let batch = StartBatch(batchName);
@@ -166,7 +166,7 @@ export namespace UndoManager {
         undoing = false;
 
         redoStack.push(commands);
-    })
+    });
 
     export const Redo = action(() => {
         if (redoStack.length === 0) {
@@ -185,6 +185,6 @@ export namespace UndoManager {
         undoing = false;
 
         undoStack.push(commands);
-    })
+    });
 
 }
\ No newline at end of file
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index cfa8ea7b7..615a928ad 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -2,11 +2,11 @@ import React = require("react");
 import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
 import { observable, action } from "mobx";
 import { observer } from "mobx-react";
-import "./ContextMenu.scss"
+import "./ContextMenu.scss";
 
 @observer
 export class ContextMenu extends React.Component {
-    static Instance: ContextMenu
+    static Instance: ContextMenu;
 
     @observable private _items: Array<ContextMenuProps> = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault() }];
     @observable private _pageX: number = 0;
@@ -22,15 +22,15 @@ export class ContextMenu extends React.Component {
     constructor(props: Readonly<{}>) {
         super(props);
 
-        this.ref = React.createRef()
+        this.ref = React.createRef();
 
         ContextMenu.Instance = this;
     }
 
     @action
     clearItems() {
-        this._items = []
-        this._display = "none"
+        this._items = [];
+        this._display = "none";
     }
 
     @action
@@ -56,7 +56,7 @@ export class ContextMenu extends React.Component {
 
         this._searchString = "";
 
-        this._display = "flex"
+        this._display = "flex";
     }
 
     intersects = (x: number, y: number): boolean => {
@@ -86,7 +86,7 @@ export class ContextMenu extends React.Component {
                 {this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1).
                     map(prop => <ContextMenuItem {...prop} key={prop.description} />)}
             </div>
-        )
+        );
     }
 
     @action
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 4801c1555..70813f0dd 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -11,7 +11,7 @@ export interface SubmenuProps {
 }
 
 export interface ContextMenuItemProps {
-    type: ContextMenuProps | SubmenuProps
+    type: ContextMenuProps | SubmenuProps;
 }
 
 export class ContextMenuItem extends React.Component<ContextMenuProps> {
@@ -20,6 +20,6 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
             <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.tsx b/src/client/views/DocumentDecorations.tsx
index 285d145a2..b29fb6a2b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -21,8 +21,8 @@ export const Flyout = higflyout.default;
 
 @observer
 export class DocumentDecorations extends React.Component<{}, { value: string }> {
-    static Instance: DocumentDecorations
-    private _resizer = ""
+    static Instance: DocumentDecorations;
+    private _resizer = "";
     private _isPointerDown = false;
     private keyinput: React.RefObject<HTMLInputElement>;
     private _documents: DocumentView[] = SelectionManager.SelectedDocuments();
@@ -40,8 +40,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
 
     constructor(props: Readonly<{}>) {
-        super(props)
-        DocumentDecorations.Instance = this
+        super(props);
+        DocumentDecorations.Instance = this;
         this.handleChange = this.handleChange.bind(this);
         this.keyinput = React.createRef();
     }
@@ -49,7 +49,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @action
     handleChange = (event: any) => {
         this._title = event.target.value;
-    };
+    }
 
     @action
     enterPressed = (e: any) => {
@@ -59,14 +59,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             var text = e.target.value;
             if (text[0] === '#') {
                 let command = text.slice(1, text.length);
-                this._fieldKey = new Key(command)
+                this._fieldKey = new Key(command);
                 // if (command === "Title" || command === "title") {
                 //     this._fieldKey = KeyStore.Title;
                 // }
                 // else if (command === "Width" || command === "width") {
                 //     this._fieldKey = KeyStore.Width;
                 // }
-                this._title = "changed"
+                this._title = "changed";
                 // TODO: Change field with switch statement
             }
             else {
@@ -89,7 +89,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             return {
                 x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
                 r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
-            }
+            };
         }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
     }
 
@@ -104,7 +104,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         document.addEventListener("pointermove", this.onBackgroundMove);
         document.removeEventListener("pointerup", this.onBackgroundUp);
         document.addEventListener("pointerup", this.onBackgroundUp);
-        this._lastDrag = [e.clientX, e.clientY]
+        this._lastDrag = [e.clientX, e.clientY];
         e.stopPropagation();
         e.preventDefault();
     }
@@ -127,7 +127,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 dragComplete: action(() => this._dragging = false),
             },
             hideSource: true
-        })
+        });
         e.stopPropagation();
     }
 
@@ -199,53 +199,53 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
     onLinkerButtonDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
-        document.removeEventListener("pointermove", this.onLinkerButtonMoved)
+        document.removeEventListener("pointermove", this.onLinkerButtonMoved);
         document.addEventListener("pointermove", this.onLinkerButtonMoved);
-        document.removeEventListener("pointerup", this.onLinkerButtonUp)
+        document.removeEventListener("pointerup", this.onLinkerButtonUp);
         document.addEventListener("pointerup", this.onLinkerButtonUp);
     }
     onLinkerButtonUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onLinkerButtonMoved)
-        document.removeEventListener("pointerup", this.onLinkerButtonUp)
+        document.removeEventListener("pointermove", this.onLinkerButtonMoved);
+        document.removeEventListener("pointerup", this.onLinkerButtonUp);
         e.stopPropagation();
     }
 
     onLinkerButtonMoved = (e: PointerEvent): void => {
         if (this._linkerButton.current !== null) {
-            document.removeEventListener("pointermove", this.onLinkerButtonMoved)
-            document.removeEventListener("pointerup", this.onLinkerButtonUp)
+            document.removeEventListener("pointermove", this.onLinkerButtonMoved);
+            document.removeEventListener("pointerup", this.onLinkerButtonUp);
             let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0]);
             DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
                 handlers: {
                     dragComplete: action(() => { }),
                 },
                 hideSource: false
-            })
+            });
         }
         e.stopPropagation();
     }
 
     onLinkButtonDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
-        document.removeEventListener("pointermove", this.onLinkButtonMoved)
+        document.removeEventListener("pointermove", this.onLinkButtonMoved);
         document.addEventListener("pointermove", this.onLinkButtonMoved);
-        document.removeEventListener("pointerup", this.onLinkButtonUp)
+        document.removeEventListener("pointerup", this.onLinkButtonUp);
         document.addEventListener("pointerup", this.onLinkButtonUp);
     }
 
     onLinkButtonUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onLinkButtonMoved)
-        document.removeEventListener("pointerup", this.onLinkButtonUp)
+        document.removeEventListener("pointermove", this.onLinkButtonMoved);
+        document.removeEventListener("pointerup", this.onLinkButtonUp);
         e.stopPropagation();
     }
 
     onLinkButtonMoved = async (e: PointerEvent) => {
         if (this._linkButton.current !== null) {
-            document.removeEventListener("pointermove", this.onLinkButtonMoved)
+            document.removeEventListener("pointermove", this.onLinkButtonMoved);
             document.removeEventListener("pointerup", this.onLinkButtonUp);
 
             let sourceDoc = SelectionManager.SelectedDocuments()[0].props.Document;
-            let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document)
+            let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document);
             let draggedDocs = (srcTarg && srcTarg !== FieldWaiting) ?
                 srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc =>
                     (linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : [];
@@ -265,7 +265,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                         dragComplete: action(() => { }),
                     },
                     hideSource: false
-                })
+                });
             }
         }
         e.stopPropagation();
@@ -285,38 +285,38 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             case "":
                 break;
             case "documentDecorations-topLeftResizer":
-                dX = -1
-                dY = -1
-                dW = -(e.movementX)
-                dH = -(e.movementY)
+                dX = -1;
+                dY = -1;
+                dW = -(e.movementX);
+                dH = -(e.movementY);
                 break;
             case "documentDecorations-topRightResizer":
-                dW = e.movementX
-                dY = -1
-                dH = -(e.movementY)
+                dW = e.movementX;
+                dY = -1;
+                dH = -(e.movementY);
                 break;
             case "documentDecorations-topResizer":
-                dY = -1
-                dH = -(e.movementY)
+                dY = -1;
+                dH = -(e.movementY);
                 break;
             case "documentDecorations-bottomLeftResizer":
-                dX = -1
-                dW = -(e.movementX)
-                dH = e.movementY
+                dX = -1;
+                dW = -(e.movementX);
+                dH = e.movementY;
                 break;
             case "documentDecorations-bottomRightResizer":
-                dW = e.movementX
-                dH = e.movementY
+                dW = e.movementX;
+                dH = e.movementY;
                 break;
             case "documentDecorations-bottomResizer":
-                dH = e.movementY
+                dH = e.movementY;
                 break;
             case "documentDecorations-leftResizer":
-                dX = -1
-                dW = -(e.movementX)
+                dX = -1;
+                dW = -(e.movementX);
                 break;
             case "documentDecorations-rightResizer":
-                dW = e.movementX
+                dW = e.movementX;
                 break;
         }
 
@@ -338,14 +338,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 var nativeWidth = doc.GetNumber(KeyStore.NativeWidth, 0);
                 var nativeHeight = doc.GetNumber(KeyStore.NativeHeight, 0);
                 if (nativeWidth > 0 && nativeHeight > 0) {
-                    if (Math.abs(dW) > Math.abs(dH))
+                    if (Math.abs(dW) > Math.abs(dH)) {
                         actualdH = nativeHeight / nativeWidth * actualdW;
+                    }
                     else actualdW = nativeWidth / nativeHeight * actualdH;
                 }
                 doc.SetNumber(KeyStore.Width, actualdW);
                 doc.SetNumber(KeyStore.Height, actualdH);
             }
-        })
+        });
     }
 
     onPointerUp = (e: PointerEvent): void => {
@@ -362,10 +363,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (this._title === "changed" && this._documents.length > 0) {
             let field = this._documents[0].props.Document.Get(this._fieldKey);
             if (field instanceof TextField) {
-                return (field as TextField).GetValue();
+                return (field).GetValue();
             }
             else if (field instanceof NumberField) {
-                return (field as NumberField).GetValue().toString();
+                return (field).GetValue().toString();
             }
         }
         return this._title;
@@ -388,7 +389,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             return (null);
         }
         if (isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
-            console.log("DocumentDecorations: Bounds Error")
+            console.log("DocumentDecorations: Bounds Error");
             return (null);
         }
 
@@ -414,7 +415,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 top: bounds.y - this._resizeBorderWidth / 2,
                 pointerEvents: this._dragging ? "none" : "all",
                 zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0,
-            }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation() }} >
+            }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
             </div>
             <div id="documentDecorations-container" style={{
                 width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
@@ -440,6 +441,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 <div className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>∞</div>
             </div >
         </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 982aacdea..2f17c6c51 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,7 +1,7 @@
-import React = require('react')
+import React = require('react');
 import { observer } from 'mobx-react';
 import { observable, action, trace } from 'mobx';
-import "./EditableView.scss"
+import "./EditableView.scss";
 
 export interface EditableProps {
     /**
@@ -22,7 +22,7 @@ export interface EditableProps {
      * The contents to render when not editing
      */
     contents: any;
-    height: number
+    height: number;
     display?: string;
 }
 
@@ -55,14 +55,14 @@ export class EditableView extends React.Component<EditableProps> {
     render() {
         if (this.editing) {
             return <input defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
-                style={{ display: this.props.display }}></input>
+                style={{ display: this.props.display }}></input>;
         } else {
             return (
                 <div className="editableView-container-editing" style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
                     onClick={action(() => this.editing = true)} >
                     {this.props.contents}
                 </div>
-            )
+            );
         }
     }
 }
\ No newline at end of file
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 4ecc44119..47ee8eb85 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -82,7 +82,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
             });
             this.inkData = data;
         }
-    };
+    }
 
     @action
     onPointerUp = (e: PointerEvent): void => {
@@ -109,7 +109,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     @action
     onPointerMove = (e: PointerEvent): void => {
-        e.stopPropagation()
+        e.stopPropagation();
         e.preventDefault();
         if (InkingControl.Instance.selectedTool !== InkTool.Eraser) {
             let data = this.inkData;  // add points to new line as it is being drawn
@@ -120,7 +120,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
             }
             this.inkData = data;
         }
-    };
+    }
 
     relativeCoordinatesForEvent = (ex: number, ey: number): { x: number, y: number } => {
         let [x, y] = this.props.getScreenTransform().transformPoint(ex, ey);
@@ -137,14 +137,15 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     @computed
     get drawnPaths() {
-        let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1)
+        let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
         let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
-            if (strokeData.page === -1 || strokeData.page === curPage)
+            if (strokeData.page === -1 || strokeData.page === curPage) {
                 paths.push(<InkingStroke key={id} id={id} line={strokeData.pathData}
                     offsetX={this.maxCanvasDim - this.inkMidX}
                     offsetY={this.maxCanvasDim - this.inkMidY}
                     color={strokeData.color} width={strokeData.width}
-                    tool={strokeData.tool} deleteCallback={this.removeLine} />)
+                    tool={strokeData.tool} deleteCallback={this.removeLine} />);
+            }
             return paths;
         }, [] as JSX.Element[]);
         return [<svg className={`inkingCanvas-paths-markers`} key="Markers"
@@ -165,6 +166,6 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
                 {this.props.children()}
                 {this.drawnPaths}
             </div >
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 64ee66ec7..9a68f0671 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,10 +1,10 @@
 import { observable, action, computed } from "mobx";
 
-import { CirclePicker, ColorResult } from 'react-color'
+import { CirclePicker, ColorResult } from 'react-color';
 import React = require("react");
 import { InkTool } from "../../fields/InkField";
 import { observer } from "mobx-react";
-import "./InkingControl.scss"
+import "./InkingControl.scss";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
@@ -25,7 +25,7 @@ export class InkingControl extends React.Component {
 
     constructor(props: Readonly<{}>) {
         super(props);
-        InkingControl.Instance = this
+        InkingControl.Instance = this;
     }
 
     @action
@@ -66,9 +66,9 @@ export class InkingControl extends React.Component {
 
     selected = (tool: InkTool) => {
         if (this._selectedTool === tool) {
-            return { color: "#61aaa3" }
+            return { color: "#61aaa3" };
         }
-        return {}
+        return {};
     }
 
     @action
@@ -111,6 +111,6 @@ export class InkingControl extends React.Component {
                         onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} />
                 </li>
             </ul >
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 12b15a3f0..0f05da22c 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -30,7 +30,7 @@ export class InkingStroke extends React.Component<StrokeProps> {
     }
 
     parseData = (line: Array<{ x: number, y: number }>): string => {
-        return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L ")
+        return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L ");
     }
 
     createStyle() {
@@ -41,7 +41,7 @@ export class InkingStroke extends React.Component<StrokeProps> {
                     fill: "none",
                     stroke: this._strokeColor,
                     strokeWidth: this._strokeWidth + "px",
-                }
+                };
         }
     }
 
@@ -53,6 +53,6 @@ export class InkingStroke extends React.Component<StrokeProps> {
         return (
             <path d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }} strokeLinejoin="round" strokeLinecap="round"
                 onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 6376fd694..fd2e23c91 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -16,7 +16,7 @@ import { WorkspacesMenu } from '../../server/authentication/controllers/Workspac
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { MessageStore } from '../../server/Message';
 import { Utils, returnTrue, emptyFunction } from '../../Utils';
-import * as rp from 'request-promise'
+import * as rp from 'request-promise';
 import { RouteStore } from '../../server/RouteStore';
 import { ServerUtils } from '../../server/ServerUtil';
 import { Documents } from '../documents/Documents';
@@ -74,7 +74,7 @@ export class Main extends React.Component {
             if (pathname.length > 1 && pathname[pathname.length - 2] === 'doc') {
                 CurrentUserUtils.MainDocId = pathname[pathname.length - 1];
             }
-        };
+        }
 
         CurrentUserUtils.loadCurrentUser();
 
@@ -119,8 +119,8 @@ export class Main extends React.Component {
 
     initEventListeners = () => {
         // window.addEventListener("pointermove", (e) => this.reportLocation(e))
-        window.addEventListener("drop", (e) => e.preventDefault(), false) // drop event handler
-        window.addEventListener("dragover", (e) => e.preventDefault(), false) // drag event handler
+        window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler
+        window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler
         // click interactions for the context menu
         document.addEventListener("pointerdown", action(function (e: PointerEvent) {
             if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
@@ -139,15 +139,15 @@ export class Main extends React.Component {
                 } else {
                     this.createNewWorkspace();
                 }
-            })
+            });
         } else {
             Server.GetField(CurrentUserUtils.MainDocId).then(field => {
                 if (field instanceof Document) {
-                    this.openWorkspace(field)
+                    this.openWorkspace(field);
                 } else {
                     this.createNewWorkspace(CurrentUserUtils.MainDocId);
                 }
-            })
+            });
         }
     }
 
@@ -163,7 +163,7 @@ export class Main extends React.Component {
                 // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
                 setTimeout(() => {
                     this.openWorkspace(mainDoc);
-                    let pendingDocument = Documents.SchemaDocument([], { title: "New Mobile Uploads" })
+                    let pendingDocument = Documents.SchemaDocument([], { title: "New Mobile Uploads" });
                     mainDoc.Set(KeyStore.OptionalRightCollection, pendingDocument);
                 }, 0);
             }
@@ -182,7 +182,7 @@ export class Main extends React.Component {
                         if (f && f.Data.length > 0) {
                             CollectionDockingView.Instance.AddRightSplit(col);
                         }
-                    })
+                    });
                 }
             }, 100);
         });
@@ -191,7 +191,7 @@ export class Main extends React.Component {
     @observable
     workspacesShown: boolean = false;
 
-    areWorkspacesShown = () => this.workspacesShown
+    areWorkspacesShown = () => this.workspacesShown;
     @action
     toggleWorkspaces = () => {
         this.workspacesShown = !this.workspacesShown;
@@ -199,7 +199,7 @@ export class Main extends React.Component {
 
     pwidthFunc = () => this.pwidth;
     pheightFunc = () => this.pheight;
-    focusDocument = (doc: Document) => { }
+    focusDocument = (doc: Document) => { };
     noScaling = () => 1;
 
     @observable _textDoc?: Document = undefined;
@@ -208,8 +208,9 @@ export class Main extends React.Component {
     SetTextDoc(textDoc?: Document, div?: HTMLDivElement) {
         this._textDoc = undefined;
         this._textDoc = textDoc;
-        if (div)
+        if (div) {
             this._textRect = div.getBoundingClientRect();
+        }
     }
 
     @computed
@@ -221,7 +222,7 @@ export class Main extends React.Component {
             let h: number = this._textRect.height;
             return <div className="mainDiv-textInput" style={{ transform: `translate(${x}px, ${y}px)`, width: `${w}px`, height: `${h}px` }} >
                 <FormattedTextBox fieldKey={KeyStore.Archives} Document={this._textDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={Transform.Identity} focus={(doc) => { }} />
-            </ div>
+            </ div>;
         }
         else return (null);
     }
@@ -241,19 +242,19 @@ export class Main extends React.Component {
                 focus={this.focusDocument}
                 parentActive={returnTrue}
                 onActiveChanged={emptyFunction}
-                ContainingCollectionView={undefined} />
+                ContainingCollectionView={undefined} />;
     }
 
     /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
     @computed
     get nodesMenu() {
         let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
-        let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf"
+        let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf";
         let weburl = "https://cs.brown.edu/courses/cs166/";
         let audiourl = "http://techslides.com/demos/samples/sample.mp3";
         let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
 
-        let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
+        let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }));
         let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
         let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
         let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true }));
@@ -261,7 +262,7 @@ export class Main extends React.Component {
         let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" }));
         let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
         let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
-        let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }))
+        let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
 
         let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Document][] = [
             [React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
@@ -273,7 +274,7 @@ export class Main extends React.Component {
             [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
             [React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
             [React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
-        ]
+        ];
 
         return < div id="add-nodes-menu" >
             <input type="checkbox" id="add-menu-toggle" />
@@ -289,7 +290,7 @@ export class Main extends React.Component {
                         </div></li>)}
                 </ul>
             </div>
-        </div >
+        </div >;
     }
 
     /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
@@ -298,7 +299,7 @@ export class Main extends React.Component {
         let workspacesRef = React.createRef<HTMLDivElement>();
         let logoutRef = React.createRef<HTMLDivElement>();
 
-        let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}))
+        let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}));
         return [
             <button className="clear-db-button" key="clear-db" onClick={clearDatabase}>Clear Database</button>,
             <div id="toolbar" key="toolbar">
@@ -310,7 +311,7 @@ export class Main extends React.Component {
                 <button onClick={this.toggleWorkspaces}>Workspaces</button></div>,
             <div className="main-buttonDiv" key="logout" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}>
                 <button onClick={() => request.get(ServerUtils.prepend(RouteStore.logout), () => { })}>Log Out</button></div>
-        ]
+        ];
     }
 
     render() {
@@ -318,7 +319,7 @@ export class Main extends React.Component {
         let workspaces = this.userDocument.GetT<ListField<Document>>(KeyStore.Workspaces, ListField);
         if (workspaces && workspaces !== FieldWaiting) {
             workspaceMenu = <WorkspacesMenu active={this.mainContainer} open={this.openWorkspace} new={this.createNewWorkspace} allWorkspaces={workspaces.Data}
-                isShown={this.areWorkspacesShown} toggle={this.toggleWorkspaces} />
+                isShown={this.areWorkspacesShown} toggle={this.toggleWorkspaces} />;
         }
         return (
             <>
@@ -359,7 +360,7 @@ export class Main extends React.Component {
                             schemaDocuments.push(field);
                         } else {
                             var atmod = new ColumnAttributeModel(attr);
-                            let histoOp = new HistogramOperation(schema!.displayName!,
+                            let histoOp = new HistogramOperation(schema.displayName!,
                                 new AttributeTransformationModel(atmod, AggregateFunction.None),
                                 new AttributeTransformationModel(atmod, AggregateFunction.Count),
                                 new AttributeTransformationModel(atmod, AggregateFunction.Count));
@@ -368,7 +369,7 @@ export class Main extends React.Component {
                     }));
                 });
                 return schemaDoc;
-            })
+            });
         }
     }
     async initializeNorthstar(): Promise<void> {
@@ -386,7 +387,7 @@ export class Main extends React.Component {
 }
 
 (async () => {
-    await Documents.initProtos()
-    await CurrentUserUtils.loadCurrentUser()
+    await Documents.initProtos();
+    await CurrentUserUtils.loadCurrentUser();
     ReactDOM.render(<Main />, document.getElementById('root'));
-})()
+})();
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 7a5ab6e3c..32462a4c1 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -66,17 +66,20 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
     createsCycle(documentToAdd: Document, containerDocument: Document): boolean {
         let data = documentToAdd.GetList<Document>(KeyStore.Data, []);
         for (const doc of data) {
-            if (this.createsCycle(doc, containerDocument))
+            if (this.createsCycle(doc, containerDocument)) {
                 return true;
+            }
         }
         let annots = documentToAdd.GetList<Document>(KeyStore.Annotations, []);
         for (const annot of annots) {
-            if (this.createsCycle(annot, containerDocument))
+            if (this.createsCycle(annot, containerDocument)) {
                 return true;
+            }
         }
         for (let containerProto: FieldValue<Document> = containerDocument; containerProto && containerProto !== FieldWaiting; containerProto = containerProto.GetPrototype()) {
-            if (containerProto.Id === documentToAdd.Id)
+            if (containerProto.Id === documentToAdd.Id) {
                 return true;
+            }
         }
         return false;
     }
@@ -91,20 +94,23 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         }
         if (props.Document.Get(props.fieldKey) instanceof Field) {
             //TODO This won't create the field if it doesn't already exist
-            const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
+            const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>());
             if (!this.createsCycle(doc, props.Document)) {
-                if (!value.some(v => v.Id === doc.Id) || allowDuplicates)
+                if (!value.some(v => v.Id === doc.Id) || allowDuplicates) {
                     value.push(doc);
+                }
             }
-            else
+            else {
                 return false;
+            }
         } else {
             let proto = props.Document.GetPrototype();
             if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) {
                 props.Document.SetOnPrototype(props.fieldKey, new ListField([doc]));
             }
-            else
+            else {
                 return false;
+            }
         }
         return true;
     }
@@ -113,7 +119,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
     removeDocument(doc: Document): boolean {
         const props = this.props;
         //TODO This won't create the field if it doesn't already exist
-        const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
+        const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>());
         let index = -1;
         for (let i = 0; i < value.length; i++) {
             if (value[i].Id === doc.Id) {
@@ -125,16 +131,16 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             if (annotationOn === props.Document) {
                 doc.Set(KeyStore.AnnotationOn, undefined, true);
             }
-        })
+        });
 
         if (index !== -1) {
-            value.splice(index, 1)
+            value.splice(index, 1);
 
             // SelectionManager.DeselectAll()
-            ContextMenu.Instance.clearItems()
+            ContextMenu.Instance.clearItems();
             return true;
         }
-        return false
+        return false;
     }
 
     @action.bound
@@ -155,12 +161,12 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             moveDocument: this.moveDocument,
             active: this.active,
             onActiveChanged: this.onActiveChanged,
-        }
+        };
         return (
             <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
                 {this.props.children(this.collectionViewType, props)}
             </div>
-        )
+        );
     }
 
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c3757a377..ea6d3a247 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -32,7 +32,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 documentId: document.Id,
                 //collectionDockingView: CollectionDockingView.Instance
             }
-        }
+        };
     }
 
     private _goldenLayout: any = null;
@@ -58,7 +58,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         let newItemStackConfig = {
             type: 'stack',
             content: [CollectionDockingView.makeDocumentConfig(document)]
-        }
+        };
         var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
         this._goldenLayout.root.contentItems[0].addChild(docconfig);
         docconfig.callDownwards('_$init');
@@ -86,7 +86,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         let newItemStackConfig = {
             type: 'stack',
             content: [CollectionDockingView.makeDocumentConfig(document)]
-        }
+        };
 
         var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
 
@@ -124,8 +124,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 this._goldenLayout = new GoldenLayout(JSON.parse(config));
             }
             else {
-                if (config === JSON.stringify(this._goldenLayout.toConfig()))
+                if (config === JSON.stringify(this._goldenLayout.toConfig())) {
                     return;
+                }
                 try {
                     this._goldenLayout.unbind('itemDropped', this.itemDropped);
                     this._goldenLayout.unbind('tabCreated', this.tabCreated);
@@ -199,14 +200,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             let docid = (e.target as any).DashDocId;
             let tab = (e.target as any).parentElement as HTMLElement;
             Server.GetField(docid, action((f: Opt<Field>) => {
-                if (f instanceof Document)
-                    DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f as Document]), e.pageX, e.pageY,
+                if (f instanceof Document) {
+                    DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), e.pageX, e.pageY,
                         {
                             handlers: {
                                 dragComplete: action(() => { }),
                             },
                             hideSource: false
-                        })
+                        });
+                }
             }));
         }
         if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
@@ -220,7 +222,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     @undoBatch
     stateChanged = () => {
         var json = JSON.stringify(this._goldenLayout.toConfig());
-        this.props.Document.SetText(KeyStore.Data, json)
+        this.props.Document.SetText(KeyStore.Data, json);
     }
 
     itemDropped = () => {
@@ -236,7 +238,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                             if (tfield !== undefined) {
                                 tab.titleElement[0].textContent = f.Title;
                             }
-                        })
+                        });
                     }
                 }));
                 tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
@@ -281,7 +283,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 }
 
 interface DockedFrameProps {
-    documentId: FieldId,
+    documentId: FieldId;
     //collectionDockingView: CollectionDockingView
 }
 @observer
@@ -297,18 +299,19 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document));
     }
 
-    private _nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth)
-    private _nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight)
-    private _contentScaling = () => this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth)
+    private _nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth);
+    private _nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight);
+    private _contentScaling = () => this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth);
 
     ScreenToLocalTransform = () => {
         let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!);
-        return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this._contentScaling())
+        return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this._contentScaling());
     }
 
     render() {
-        if (!this._document)
+        if (!this._document) {
             return (null);
+        }
         var content =
             <div className="collectionDockingView-content" ref={this._mainCont}>
                 <DocumentView key={this._document.Id} Document={this._document}
@@ -324,10 +327,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                     onActiveChanged={emptyFunction}
                     focus={(doc: Document) => { }}
                     ContainingCollectionView={undefined} />
-            </div>
+            </div>;
 
         return <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
             {({ measureRef }) => <div ref={measureRef}>  {content} </div>}
-        </Measure>
+        </Measure>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 32f50afc0..97bac745c 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -2,7 +2,7 @@ import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
-import "./CollectionPDFView.scss"
+import "./CollectionPDFView.scss";
 import React = require("react");
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
@@ -44,7 +44,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
                 <CollectionFreeFormView {...props} />
                 {this.props.isSelected() ? this.uIButtons : (null)}
             </>
-        )
+        );
     }
 
     render() {
@@ -52,6 +52,6 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
             <CollectionBaseView {...this.props} className="collectionPdfView-cont" onContextMenu={this.onContextMenu}>
                 {this.subView}
             </CollectionBaseView>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 9b780f29b..52933818f 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -41,7 +41,7 @@ class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggl
             if (field instanceof Key) {
                 this.key = field;
             }
-        }))
+        }));
     }
 
     render() {
@@ -49,7 +49,7 @@ class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggl
             return (<div key={this.key.Id}>
                 <input type="checkbox" checked={this.props.checked} onChange={() => this.key && this.props.toggle(this.key)} />
                 {this.key.Name}
-            </div>)
+            </div>);
         }
         return (null);
     }
@@ -85,10 +85,10 @@ export class CollectionSchemaView extends CollectionSubView {
             focus: emptyFunction,
             active: returnFalse,
             onActiveChanged: emptyFunction,
-        }
+        };
         let contents = (
             <FieldView {...props} />
-        )
+        );
         let reference = React.createRef<HTMLDivElement>();
         let onItemDown = setupDrag(reference, () => props.Document, this.props.moveDocument);
         let applyToDoc = (doc: Document, run: (args?: { [name: string]: any }) => any) => {
@@ -106,7 +106,7 @@ export class CollectionSchemaView extends CollectionSubView {
                 }
             }
             return false;
-        }
+        };
         return (
             <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.Document.Id} ref={reference}>
                 <EditableView
@@ -138,11 +138,11 @@ export class CollectionSchemaView extends CollectionSubView {
                             if (val) {
                                 val.Data.forEach(doc => applyToDoc(doc, run));
                             }
-                        })
+                        });
                     }}>
                 </EditableView>
             </div>
-        )
+        );
     }
 
     private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
@@ -156,7 +156,7 @@ export class CollectionSchemaView extends CollectionSubView {
                 that._selectedIndex = rowInfo.index;
 
                 if (handleOriginal) {
-                    handleOriginal()
+                    handleOriginal();
                 }
             }),
             style: {
@@ -182,7 +182,7 @@ export class CollectionSchemaView extends CollectionSubView {
                     this.columns.splice(index, 1);
                 }
 
-            })
+            });
     }
 
     //toggles preview side-panel of schema
@@ -197,7 +197,7 @@ export class CollectionSchemaView extends CollectionSubView {
     @computed
     get findAllDocumentKeys(): { [id: string]: boolean } {
         const docs = this.props.Document.GetList<Document>(this.props.fieldKey, []);
-        let keys: { [id: string]: boolean } = {}
+        let keys: { [id: string]: boolean } = {};
         if (this._optionsActivated > -1) {
             // bcz: ugh.  this is untracked since otherwise a large collection of documents will blast the server for all their fields.
             //  then as each document's fields come back, we update the documents _proxies.  Each time we do this, the whole schema will be
@@ -206,7 +206,7 @@ export class CollectionSchemaView extends CollectionSubView {
             //  is displayed (unlikely) it won't show up until something else changes.
             untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false))));
         }
-        this.columns.forEach(key => keys[key.Id] = true)
+        this.columns.forEach(key => keys[key.Id] = true);
         return keys;
     }
 
@@ -248,10 +248,10 @@ export class CollectionSchemaView extends CollectionSubView {
     getContentScaling = (): number => this._contentScaling;
     getPanelWidth = (): number => this._panelWidth;
     getPanelHeight = (): number => this._panelHeight;
-    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling)
-    getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling)
+    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling);
+    getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling);
 
-    focusDocument = (doc: Document) => { }
+    focusDocument = (doc: Document) => { };
 
     onPointerDown = (e: React.PointerEvent): void => {
         if (this.props.isSelected()) {
@@ -273,8 +273,9 @@ export class CollectionSchemaView extends CollectionSubView {
         this.newKeyName = e.currentTarget.value;
     }
     onWheel = (e: React.WheelEvent): void => {
-        if (this.props.active())
+        if (this.props.active()) {
             e.stopPropagation();
+        }
     }
 
     @observable _optionsActivated: number = 0;
@@ -322,9 +323,9 @@ export class CollectionSchemaView extends CollectionSubView {
                     </div>
                 }
             </Measure>
-        )
+        );
         let dividerDragger = this.splitPercentage === 0 ? (null) :
-            <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />
+            <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
 
         //options button and menu
         let optionsMenu = !this.props.active() ? (null) : (<Flyout
@@ -379,6 +380,6 @@ export class CollectionSchemaView extends CollectionSubView {
                     {optionsMenu}
                 </div>
             </div >
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 59b89e119..bd385c27e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -14,7 +14,7 @@ import { NumberField } from "../../../fields/NumberField";
 import { ServerUtils } from "../../../server/ServerUtil";
 import { Server } from "../../Server";
 import { FieldViewProps } from "../nodes/FieldView";
-import * as rp from 'request-promise'
+import * as rp from 'request-promise';
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
@@ -58,8 +58,8 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                         let entry = new TupleField<[string, string], [number, number]>([textInfo, position]);
                         cursors.push(entry);
                     }
-                }))
-            })
+                }));
+            });
         }
     }
 
@@ -77,17 +77,17 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                 added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false);
             } else if (de.data.moveDocument) {
                 const move = de.data.moveDocument;
-                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || move(d, this.props.Document, this.props.addDocument), false)
+                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || move(d, this.props.Document, this.props.addDocument), false);
             } else {
-                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false)
+                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false);
             }
             e.stopPropagation();
             return added;
         }
         if (de.data instanceof DragManager.LinkDragData) {
             let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document;
-            if (sourceDoc) runInAction(() => {
-                let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document)
+            if (sourceDoc) { runInAction(() => {
+                let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document);
                 if (srcTarg && srcTarg !== FieldWaiting) {
                     let linkDocs = srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]);
                     linkDocs.map(linkDoc => {
@@ -97,9 +97,10 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                             de.data.droppedDocuments.push(dropdoc);
                             this.props.addDocument(dropdoc, false);
                         }
-                    })
+                    });
                 }
-            })
+            });
+            }
             return true;
         }
         return false;
@@ -133,7 +134,7 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                         alias.SetNumber(KeyStore.Height, options.height || options.width || 300);
                         this.props.addDocument(alias, false);
                     }
-                })
+                });
                 return undefined;
             }
             ctor = Documents.WebDocument;
@@ -151,8 +152,8 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
         if (text && text.startsWith("<div")) {
             return;
         }
-        e.stopPropagation()
-        e.preventDefault()
+        e.stopPropagation();
+        e.preventDefault();
 
         if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
             console.log("not good");
@@ -173,11 +174,11 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                 let prom = new Promise<string>(res =>
                     e.dataTransfer.items[i].getAsString(res)).then(action((s: string) => {
                         str = s;
-                        return rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s))
+                        return rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s));
                     })).then(res => {
                         let type = res.headers["content-type"];
                         if (type) {
-                            let doc = this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
+                            let doc = this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 });
                             if (doc) {
                                 this.props.addDocument(doc, false);
                             }
@@ -186,13 +187,13 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                 promises.push(prom);
                 // this.props.addDocument(Documents.WebDocument(s, { ...options, width: 300, height: 300 }), false)
             }
-            let type = item.type
+            let type = item.type;
             if (item.kind === "file") {
                 let file = item.getAsFile();
-                let formData = new FormData()
+                let formData = new FormData();
 
                 if (file) {
-                    formData.append('file', file)
+                    formData.append('file', file);
                 }
 
                 let prom = fetch(upload, {
@@ -201,15 +202,15 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                 }).then(async (res: Response) => {
                     const json = await res.json();
                     json.map((file: any) => {
-                        let path = window.location.origin + file
+                        let path = window.location.origin + file;
                         runInAction(() => {
-                            let doc = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300 })
+                            let doc = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300 });
 
                             let docs = this.props.Document.GetT(KeyStore.Data, ListField);
                             if (docs !== FieldWaiting) {
                                 if (!docs) {
                                     docs = new ListField<Document>();
-                                    this.props.Document.Set(KeyStore.Data, docs)
+                                    this.props.Document.Set(KeyStore.Data, docs);
                                 }
                                 if (doc) {
                                     docs.Data.push(doc);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 2bdf0baa6..659cff9fe 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -12,7 +12,7 @@ import { EditableView } from "../EditableView";
 import "./CollectionTreeView.scss";
 import { CollectionView } from "./CollectionView";
 import { CollectionSubView } from "./CollectionSubView";
-import React = require("react")
+import React = require("react");
 import { COLLECTION_BORDER_WIDTH } from './CollectionBaseView';
 import { props } from 'bluebird';
 
@@ -58,7 +58,7 @@ class TreeView extends React.Component<TreeViewProps> {
             return true;
         }
         //TODO This should check if it was removed
-        this.remove(document)
+        this.remove(document);
         return addDoc(document);
     }
 
@@ -69,7 +69,7 @@ class TreeView extends React.Component<TreeViewProps> {
             case BulletType.Collapsed: bullet = "caret-right"; break;
             case BulletType.Collapsible: bullet = "caret-down"; break;
         }
-        return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>
+        return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>;
     }
 
     /**
@@ -93,7 +93,7 @@ class TreeView extends React.Component<TreeViewProps> {
             <div className="docContainer" ref={reference} onPointerDown={onItemDown}>
                 {editableView(this.props.document.Title)}
                 <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
-            </div >)
+            </div >);
     }
 
     render() {
@@ -105,7 +105,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 bulletType = BulletType.Collapsible;
                 childElements = <ul>
                     {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
-                </ul >
+                </ul >;
             }
             else bulletType = BulletType.Collapsed;
         }
@@ -115,7 +115,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 {this.renderTitle()}
                 {childElements ? childElements : (null)}
             </li>
-        </div>
+        </div>;
     }
 }
 
@@ -136,7 +136,7 @@ export class CollectionTreeView extends CollectionSubView {
         let childrenElement = !children || children === FieldWaiting ? (null) :
             (children.Data.map(value =>
                 <TreeView document={value} key={value.Id} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
-            )
+            );
 
         return (
             <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 1acc76c85..b02983a2e 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -4,7 +4,7 @@ import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
 import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
 import React = require("react");
-import "./CollectionVideoView.scss"
+import "./CollectionVideoView.scss";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 
@@ -111,7 +111,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
                 <CollectionFreeFormView {...props} />
                 {this.props.isSelected() ? this.uIButtons : (null)}
             </>
-        )
+        );
     }
 
     render() {
@@ -119,6 +119,6 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
         return (
             <CollectionBaseView {...this.props} className="collectionVideoView-cont" contentRef={this.mainCont} onContextMenu={this.onContextMenu}>
                 {this.subView}
-            </CollectionBaseView>)
+            </CollectionBaseView>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index b31dcc888..8abd0a02d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react'
+import * as React from 'react';
 import { FieldViewProps, FieldView } from '../nodes/FieldView';
 import { CollectionBaseView, CollectionViewType, CollectionRenderProps } from './CollectionBaseView';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
@@ -13,26 +13,26 @@ import { undoBatch } from '../../util/UndoManager';
 
 @observer
 export class CollectionView extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(CollectionView, fieldStr) }
+    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(CollectionView, fieldStr); }
 
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         let props = { ...this.props, ...renderProps };
         switch (type) {
-            case CollectionViewType.Schema: return (<CollectionSchemaView {...props} />)
-            case CollectionViewType.Docking: return (<CollectionDockingView {...props} />)
-            case CollectionViewType.Tree: return (<CollectionTreeView {...props} />)
+            case CollectionViewType.Schema: return (<CollectionSchemaView {...props} />);
+            case CollectionViewType.Docking: return (<CollectionDockingView {...props} />);
+            case CollectionViewType.Tree: return (<CollectionTreeView {...props} />);
             case CollectionViewType.Freeform:
             default:
-                return (<CollectionFreeFormView {...props} />)
+                return (<CollectionFreeFormView {...props} />);
         }
         return (null);
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.Document.Id !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-            ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform)) })
-            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema)) })
-            ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree)) })
+            ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform)) });
+            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema)) });
+            ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree)) });
         }
     }
 
@@ -41,6 +41,6 @@ export class CollectionView extends React.Component<FieldViewProps> {
             <CollectionBaseView {...this.props} onContextMenu={this.onContextMenu}>
                 {this.SubView}
             </CollectionBaseView>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 3dfd74ec8..081b3eb6c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -32,6 +32,6 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                 style={{ strokeWidth: `${l.length * 5}` }}
                 x1={`${x1}`} y1={`${y1}`}
                 x2={`${x2}`} y2={`${y2}`} />
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index c288e7abf..cf058090d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -17,7 +17,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
 
     HackToAvoidReactionFiringUnnecessarily?: Document = undefined;
     componentDidMount() {
-        this.HackToAvoidReactionFiringUnnecessarily = this.props.Document
+        this.HackToAvoidReactionFiringUnnecessarily = this.props.Document;
         reaction(() =>
             DocumentManager.Instance.getAllDocumentViews(this.HackToAvoidReactionFiringUnnecessarily!).
                 map(dv => dv.props.Document.GetNumber(KeyStore.X, 0)),
@@ -31,13 +31,14 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
                         let x1w = srcDoc.GetNumber(KeyStore.Width, -1);
                         let x2 = dstDoc.GetNumber(KeyStore.X, 0);
                         let x2w = dstDoc.GetNumber(KeyStore.Width, -1);
-                        if (x1w < 0 || x2w < 0 || i === j)
+                        if (x1w < 0 || x2w < 0 || i === j) {
                             continue;
+                        }
                         let dstTarg = dstDoc;
                         let srcTarg = srcDoc;
                         let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => {
                             let bdocs = brush ? brush.GetList(KeyStore.BrushingDocs, [] as Document[]) : [];
-                            return (bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false)
+                            return (bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false);
                         });
                         let brushAction = (field: ListField<Document>) => {
                             let found = findBrush(field);
@@ -64,13 +65,13 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
 
                     }
                 }
-            })
+            });
     }
     documentAnchors(view: DocumentView) {
         let equalViews = [view];
         let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document);
         if (containerDoc && containerDoc instanceof Document) {
-            equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype()!)
+            equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype()!);
         }
         return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === this.props.Document);
     }
@@ -86,15 +87,16 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
                 if (!drawnPairs.reduce((found, drawnPair) => {
                     let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b);
                     if (match) {
-                        if (!drawnPair.l.reduce((found, link) => found || link.Id === connection.l.Id, false))
+                        if (!drawnPair.l.reduce((found, link) => found || link.Id === connection.l.Id, false)) {
                             drawnPair.l.push(connection.l);
+                        }
                     }
                     return match || found;
                 }, false)) {
                     drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] });
                 }
-            })
-            return drawnPairs
+            });
+            return drawnPairs;
         }, [] as { a: Document, b: Document, l: Document[] }[]);
         return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 17656e4ae..fc832264d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -57,7 +57,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
                 let id = entry.Data[0][0];
                 let email = entry.Data[0][1];
                 let point = entry.Data[1];
-                this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22")
+                this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22");
                 return (
                     <div
                         key={id}
@@ -69,7 +69,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
                         }}
                     >
                         <canvas
-                            ref={(el) => { if (el) this.crosshairs = el }}
+                            ref={(el) => { if (el) this.crosshairs = el; }}
                             width={20}
                             height={20}
                             style={{
@@ -93,7 +93,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
                     </div>
                 );
             }
-        })
+        });
     }
 
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1c3899b24..e694bc7a7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -49,7 +49,7 @@ export class CollectionFreeFormView extends CollectionSubView {
             if (dv) {
                 SelectionManager.SelectDoc(dv, true);
             }
-        })
+        });
     }
 
     public getActiveDocuments = () => {
@@ -70,8 +70,8 @@ export class CollectionFreeFormView extends CollectionSubView {
 
     private outerElement?: HTMLDivElement;
 
-    @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
-    @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
+    @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0); }
+    @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0); }
     @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
     @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
     @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
@@ -109,7 +109,7 @@ export class CollectionFreeFormView extends CollectionSubView {
                             d.SetNumber(KeyStore.Height, 300);
                         }
                         this.bringToFront(d);
-                    })
+                    });
                 }
             }
             return true;
@@ -133,8 +133,9 @@ export class CollectionFreeFormView extends CollectionSubView {
             document.addEventListener("pointerup", this.onPointerUp);
             this._lastX = this.DownX = e.pageX;
             this._lastY = this.DownY = e.pageY;
-            if (this.props.isSelected())
+            if (this.props.isSelected()) {
                 e.stopPropagation();
+            }
         }
     }
 
@@ -182,12 +183,13 @@ export class CollectionFreeFormView extends CollectionSubView {
             let transform = this.getTransform();
 
             let deltaScale = (1 - (e.deltaY / coefficient));
-            if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay)
+            if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay) {
                 deltaScale = 1 / this.zoomScaling;
+            }
             let [x, y] = transform.transformPoint(e.clientX, e.clientY);
 
-            let localTransform = this.getLocalTransform()
-            localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y)
+            let localTransform = this.getLocalTransform();
+            localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y);
             // console.log(localTransform)
 
             this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
@@ -209,7 +211,7 @@ export class CollectionFreeFormView extends CollectionSubView {
     onDrop = (e: React.DragEvent): void => {
         var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
         super.onDrop(e, { x: pt[0], y: pt[1] });
-    };
+    }
 
     onDragOver = (): void => {
     }
@@ -228,7 +230,7 @@ export class CollectionFreeFormView extends CollectionSubView {
             }
             return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0);
         }).map((doc, index) => {
-            doc.SetNumber(KeyStore.ZIndex, index + 1)
+            doc.SetNumber(KeyStore.ZIndex, index + 1);
         });
     }
 
@@ -268,7 +270,7 @@ export class CollectionFreeFormView extends CollectionSubView {
             focus: this.focusDocument,
             parentActive: this.props.active,
             onActiveChanged: this.props.active,
-        }
+        };
     }
 
     @computed
@@ -276,10 +278,11 @@ export class CollectionFreeFormView extends CollectionSubView {
         var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
         return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => {
             var page = doc.GetNumber(KeyStore.Page, -1);
-            if (page === curPage || page === -1)
+            if (page === curPage || page === -1) {
                 prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
+            }
             return prev;
-        }, [] as JSX.Element[])
+        }, [] as JSX.Element[]);
     }
 
     @computed
@@ -295,8 +298,8 @@ export class CollectionFreeFormView extends CollectionSubView {
                 layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
     }
 
-    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
-    getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
+    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform());
+    getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH);
     getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.scale).translate(this.panX, this.panY);
     noScaling = () => 1;
     childViews = () => this.views;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 704db1d4a..1e6faafb3 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -37,7 +37,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     @action
     cleanupInteractions = (all: boolean = false) => {
         if (all) {
-            document.removeEventListener("pointermove", this.onPointerMove, true)
+            document.removeEventListener("pointermove", this.onPointerMove, true);
             document.removeEventListener("pointerup", this.onPointerUp, true);
         } else {
             this._used = true;
@@ -52,7 +52,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             this._downX = this._lastX = e.pageX;
             this._downY = this._lastY = e.pageY;
             this._used = false;
-            document.addEventListener("pointermove", this.onPointerMove, true)
+            document.addEventListener("pointermove", this.onPointerMove, true);
             document.addEventListener("pointerup", this.onPointerUp, true);
             document.addEventListener("keydown", this.marqueeCommand, true);
         }
@@ -94,7 +94,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         let top = this._downY < this._lastY ? this._downY : this._lastY;
         let topLeft = this.props.getTransform().transformPoint(left, top);
         let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
-        return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }
+        return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
     }
 
     @action
@@ -180,9 +180,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             var y = doc.GetNumber(KeyStore.Y, 0);
             var w = doc.GetNumber(KeyStore.Width, 0);
             var h = doc.GetNumber(KeyStore.Height, 0);
-            if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect))
-                selection.push(doc)
-        })
+            if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
+                selection.push(doc);
+            }
+        });
         return selection;
     }
 
@@ -190,7 +191,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     get marqueeDiv() {
         let p = this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
         let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
-        return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />
+        return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />;
     }
 
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
index 599461f85..8eabb020a 100644
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
@@ -90,7 +90,7 @@ export class PreviewCursor extends React.Component<PreviewCursorProps>  {
                 {this.props.children}
                 <PreviewCursorPrompt setVisible={this.setVisible} getPoint={this.getPoint} getVisible={this.getVisible} />
             </div>
-        )
+        );
     }
 }
 
@@ -109,8 +109,9 @@ export class PreviewCursorPrompt extends React.Component<PromptProps> {
 
     render() {
         let p = this.props.getPoint();
-        if (this.props.getVisible() && this._promptRef.current)
+        if (this.props.getVisible() && this._promptRef.current) {
             this._promptRef.current.focus();
+        }
         return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._promptRef}
             style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, opacity: this.props.getVisible() ? 1 : 0 }}>
             I
diff --git a/src/client/views/nodes/Annotation.tsx b/src/client/views/nodes/Annotation.tsx
index e4f17940c..3e4ed6bf1 100644
--- a/src/client/views/nodes/Annotation.tsx
+++ b/src/client/views/nodes/Annotation.tsx
@@ -1,8 +1,8 @@
 import "./ImageBox.scss";
-import React = require("react")
-import { observer } from "mobx-react"
+import React = require("react");
+import { observer } from "mobx-react";
 import { observable, action } from 'mobx';
-import 'react-pdf/dist/Page/AnnotationLayer.css'
+import 'react-pdf/dist/Page/AnnotationLayer.css';
 
 interface IProps {
     Span: HTMLSpanElement;
@@ -29,13 +29,13 @@ export class Annotation extends React.Component<IProps> {
      */
     onColorChange = (e: React.PointerEvent) => {
         if (e.currentTarget.innerHTML === "r") {
-            this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)"
+            this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)";
         } else if (e.currentTarget.innerHTML === "b") {
-            this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)"
+            this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)";
         } else if (e.currentTarget.innerHTML === "y") {
-            this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)"
+            this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)";
         } else if (e.currentTarget.innerHTML === "g") {
-            this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)"
+            this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)";
         }
 
     }
@@ -54,11 +54,11 @@ export class Annotation extends React.Component<IProps> {
                     this.props.Highlights.splice(index, 1);
                 }
             }
-        })
+        });
 
         //removing from CurrAnno and Annotation array 
         this.props.Annotations.splice(index, 1);
-        this.props.CurrAnno.pop()
+        this.props.CurrAnno.pop();
 
         //removing span from div
         if (this.props.Span.parentElement) {
@@ -70,11 +70,11 @@ export class Annotation extends React.Component<IProps> {
                             if (item === e) {
                                 item.remove();
                             }
-                        })
+                        });
                         e.remove();
                     }
                 }
-            })
+            });
         }
 
 
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 1bd934c25..1493ff25b 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,18 +1,18 @@
-import React = require("react")
+import React = require("react");
 import { FieldViewProps, FieldView } from './FieldView';
 import { FieldWaiting } from '../../../fields/Field';
-import { observer } from "mobx-react"
+import { observer } from "mobx-react";
 import { ContextMenu } from "../../views/ContextMenu";
 import { observable, action } from 'mobx';
 import { KeyStore } from '../../../fields/KeyStore';
 import { AudioField } from "../../../fields/AudioField";
-import "./AudioBox.scss"
+import "./AudioBox.scss";
 import { NumberField } from "../../../fields/NumberField";
 
 @observer
 export class AudioBox extends React.Component<FieldViewProps> {
 
-    public static LayoutString() { return FieldView.LayoutString(AudioBox) }
+    public static LayoutString() { return FieldView.LayoutString(AudioBox); }
 
     constructor(props: FieldViewProps) {
         super(props);
@@ -28,7 +28,7 @@ export class AudioBox extends React.Component<FieldViewProps> {
 
 
     render() {
-        let field = this.props.Document.Get(this.props.fieldKey)
+        let field = this.props.Document.Get(this.props.fieldKey);
         let path = field === FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3" :
             field instanceof AudioField ? field.Data.href : "http://techslides.com/demos/samples/sample.mp3";
 
@@ -39,6 +39,6 @@ export class AudioBox extends React.Component<FieldViewProps> {
                     Not supported.
                 </audio>
             </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index dcf82ccd4..77f41105f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -35,24 +35,24 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
     @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
 
     set width(w: number) {
-        this.props.Document.SetData(KeyStore.Width, w, NumberField)
+        this.props.Document.SetData(KeyStore.Width, w, NumberField);
         if (this.nativeWidth && this.nativeHeight) {
-            this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w)
+            this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w);
         }
     }
 
     set height(h: number) {
         this.props.Document.SetData(KeyStore.Height, h, NumberField);
         if (this.nativeWidth && this.nativeHeight) {
-            this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h)
+            this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h);
         }
     }
 
     set zIndex(h: number) {
-        this.props.Document.SetData(KeyStore.ZIndex, h, NumberField)
+        this.props.Document.SetData(KeyStore.ZIndex, h, NumberField);
     }
 
-    contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1
+    contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
 
     getTransform = (): Transform =>
         this.props.ScreenToLocalTransform()
@@ -66,7 +66,7 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
             ScreenToLocalTransform={this.getTransform}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
-        />
+        />;
     }
     panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth();
     panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight();
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 34cc326aa..5836da396 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -26,7 +26,7 @@ import { FieldViewProps } from "./FieldView";
 import { Without } from "../../../Utils";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
-type BindingProps = Without<FieldViewProps, 'fieldKey'>
+type BindingProps = Without<FieldViewProps, 'fieldKey'>;
 export interface JsxBindings {
     props: BindingProps;
     [keyName: string]: BindingProps | Field;
@@ -92,7 +92,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             bindings={this.CreateBindings()}
             jsx={this.layout}
             showWarnings={true}
-            onError={(test: any) => { console.log(test) }}
-        />
+            onError={(test: any) => { console.log(test); }}
+        />;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ab9cd2d53..9670ca6b2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -100,8 +100,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         if (e.shiftKey && e.buttons === 2) {
             if (this.props.isTopMost) {
                 this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
-            } else
+            } else {
                 CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
+            }
             e.stopPropagation();
         } else {
             if (this.active && !e.isDefaultPrevented()) {
@@ -112,7 +113,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                 document.addEventListener("pointerup", this.onPointerUp);
             }
         }
-    };
+    }
 
     private dropDisposer?: DragManager.DragDropDisposer;
 
@@ -179,7 +180,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         }
         e.stopPropagation();
         e.preventDefault();
-    };
+    }
     onPointerUp = (e: PointerEvent): void => {
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
@@ -190,22 +191,22 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         ) {
             SelectionManager.SelectDoc(this, e.ctrlKey);
         }
-    };
+    }
     stopPropogation = (e: React.SyntheticEvent) => {
         e.stopPropagation();
-    };
+    }
 
     deleteClicked = (): void => {
         if (this.props.removeDocument) {
             this.props.removeDocument(this.props.Document);
         }
-    };
+    }
 
     fieldsClicked = (e: React.MouseEvent): void => {
         if (this.props.addDocument) {
             this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
         }
-    };
+    }
     fullScreenClicked = (e: React.MouseEvent): void => {
         CollectionDockingView.Instance.OpenFullScreen(this.props.Document);
         ContextMenu.Instance.clearItems();
@@ -214,7 +215,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             event: this.closeFullScreenClicked
         });
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
-    };
+    }
 
     closeFullScreenClicked = (e: React.MouseEvent): void => {
         CollectionDockingView.Instance.CloseFullScreen();
@@ -224,7 +225,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             event: this.fullScreenClicked
         });
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
-    };
+    }
 
     @action
     public minimize = (): void => {
@@ -234,7 +235,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             BooleanField
         );
         SelectionManager.DeselectAll();
-    };
+    }
 
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
@@ -276,7 +277,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             );
             e.stopPropagation();
         }
-    };
+    }
 
     onDrop = (e: React.DragEvent) => {
         if (e.isDefaultPrevented()) {
@@ -290,7 +291,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             e.stopPropagation();
             e.preventDefault();
         }
-    };
+    }
 
     @action
     onContextMenu = (e: React.MouseEvent): void => {
@@ -352,14 +353,14 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         });
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
         SelectionManager.SelectDoc(this, e.ctrlKey);
-    };
+    }
 
     isMinimized = () => {
         let field = this.props.Document.GetT(KeyStore.Minimized, BooleanField);
         if (field && field !== FieldWaiting) {
             return field.Data;
         }
-    };
+    }
 
     @action
     expand = () => {
@@ -368,13 +369,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             false as boolean,
             BooleanField
         );
-    };
+    }
 
-    isSelected = () => SelectionManager.IsSelected(this)
+    isSelected = () => SelectionManager.IsSelected(this);
 
     select = (ctrlPressed: boolean) => {
         SelectionManager.SelectDoc(this, ctrlPressed);
-    };
+    }
 
     render() {
         if (!this.props.Document) {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index d9422ae9b..07c5b332c 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { observer } from "mobx-react";
 import { computed } from "mobx";
 import { Field, FieldWaiting, FieldValue } from "../../../fields/Field";
@@ -7,7 +7,7 @@ import { TextField } from "../../../fields/TextField";
 import { NumberField } from "../../../fields/NumberField";
 import { RichTextField } from "../../../fields/RichTextField";
 import { ImageField } from "../../../fields/ImageField";
-import { VideoField } from "../../../fields/VideoField"
+import { VideoField } from "../../../fields/VideoField";
 import { Key } from "../../../fields/Key";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { ImageBox } from "./ImageBox";
@@ -57,22 +57,22 @@ export class FieldView extends React.Component<FieldViewProps> {
     render() {
         const field = this.field;
         if (!field) {
-            return <p>{'<null>'}</p>
+            return <p>{'<null>'}</p>;
         }
         if (field instanceof TextField) {
-            return <p>{field.Data}</p>
+            return <p>{field.Data}</p>;
         }
         else if (field instanceof RichTextField) {
-            return <FormattedTextBox {...this.props} />
+            return <FormattedTextBox {...this.props} />;
         }
         else if (field instanceof ImageField) {
-            return <ImageBox {...this.props} />
+            return <ImageBox {...this.props} />;
         }
         else if (field instanceof VideoField) {
-            return <VideoBox {...this.props} />
+            return <VideoBox {...this.props} />;
         }
         else if (field instanceof AudioField) {
-            return <AudioBox {...this.props} />
+            return <AudioBox {...this.props} />;
         }
         else if (field instanceof Document) {
             return (
@@ -92,25 +92,26 @@ export class FieldView extends React.Component<FieldViewProps> {
                     ContainingCollectionView={undefined}
                     parentActive={this.props.active}
                     onActiveChanged={this.props.onActiveChanged} />
-            )
+            );
         }
         else if (field instanceof ListField) {
             return (<div>
                 {(field as ListField<Field>).Data.map(f => f instanceof Document ? f.Title : f.GetValue().toString()).join(", ")}
-            </div>)
+            </div>);
         }
         // bcz: this belongs here, but it doesn't render well so taking it out for now
         // else if (field instanceof HtmlField) {
         //     return <WebBox {...this.props} />
         // }
         else if (field instanceof NumberField) {
-            return <p>{field.Data}</p>
+            return <p>{field.Data}</p>;
         }
         else if (field !== FieldWaiting) {
-            return <p>{JSON.stringify(field.GetValue())}</p>
+            return <p>{JSON.stringify(field.GetValue())}</p>;
         }
-        else
-            return <p> {"Waiting for server..."} </p>
+        else {
+            return <p> {"Waiting for server..."} </p>;
+ }
     }
 
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7a94be12b..beca6cdc6 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -61,7 +61,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
             );
             // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
         }
-    };
+    }
 
     get FieldDoc() { return this.props.fieldKey === KeyStore.Archives ? Main.Instance._textDoc! : this.props.Document; }
     get FieldKey() { return this.props.fieldKey === KeyStore.Archives ? KeyStore.Data : this.props.fieldKey; }
@@ -81,12 +81,13 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
         if (this.props.fieldKey === KeyStore.Archives) {
             this._inputReactionDisposer = reaction(() => Main.Instance._textDoc && Main.Instance._textDoc.Id,
                 () => {
-                    if (this._editorView)
-                        this._editorView!.destroy();
+                    if (this._editorView) {
+                        this._editorView.destroy();
+                    }
 
                     this.setupEditor(config);
                 }
-            )
+            );
         }
 
         this._reactionDisposer = reaction(
@@ -153,7 +154,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
         }
-    };
+    }
     onPointerUp = (e: React.PointerEvent): void => {
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
@@ -162,7 +163,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
             e.preventDefault();
             Main.Instance.SetTextDoc(this.props.Document, this._ref.current!);
         }
-    };
+    }
 
     onFocused = (e: React.FocusEvent): void => {
         if (this.props.fieldKey !== KeyStore.Archives) {
@@ -191,11 +192,11 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
         //     ]
         // })
         // e.stopPropagation()
-    };
+    }
 
     onPointerWheel = (e: React.WheelEvent): void => {
         e.stopPropagation();
-    };
+    }
 
     tooltipMenuPlugin() {
         return new Plugin({
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c5f29f7b0..6b0a3a799 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -9,13 +9,13 @@ import { KeyStore } from '../../../fields/KeyStore';
 import { ContextMenu } from "../../views/ContextMenu";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
-import React = require("react")
+import React = require("react");
 import { Utils } from '../../../Utils';
 
 @observer
 export class ImageBox extends React.Component<FieldViewProps> {
 
-    public static LayoutString() { return FieldView.LayoutString(ImageBox) }
+    public static LayoutString() { return FieldView.LayoutString(ImageBox); }
     private _ref: React.RefObject<HTMLDivElement>;
     private _imgRef: React.RefObject<HTMLImageElement>;
     private _downX: number = 0;
@@ -39,7 +39,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
     onLoad = (target: any) => {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
-        this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w)
+        this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
     }
 
     componentDidMount() {
@@ -86,7 +86,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
                 onMoveNextRequest={action(() =>
                     this._photoIndex = (this._photoIndex + 1) % images.length
                 )}
-            />)
+            />);
         }
     }
 
@@ -96,7 +96,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
             let url = field.Data.href;
             ContextMenu.Instance.addItem({
                 description: "Copy path", event: () => {
-                    Utils.CopyText(url)
+                    Utils.CopyText(url);
                 }
             });
         }
@@ -111,6 +111,6 @@ export class ImageBox extends React.Component<FieldViewProps> {
             <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} onContextMenu={this.specificContextMenu}>
                 <img src={path} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
                 {this.lightbox(path)}
-            </div>)
+            </div>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 8a7f32e6d..bcac113f0 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -7,7 +7,7 @@ import { KeyStore } from '../../../fields/KeyStore';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./KeyValueBox.scss";
 import { KeyValuePair } from "./KeyValuePair";
-import React = require("react")
+import React = require("react");
 import { CompileScript, ToField } from "../../util/Scripting";
 import { Key } from '../../../fields/Key';
 import { observable, action } from "mobx";
@@ -15,7 +15,7 @@ import { observable, action } from "mobx";
 @observer
 export class KeyValueBox extends React.Component<FieldViewProps> {
 
-    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr) }
+    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr); }
     @observable private _keyInput: string = "";
     @observable private _valueInput: string = "";
 
@@ -36,7 +36,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
             if (this._keyInput && this._valueInput) {
                 let doc = this.props.Document.GetT(KeyStore.Data, Document);
                 if (!doc || doc === FieldWaiting) {
-                    return
+                    return;
                 }
                 let realDoc = doc;
 
@@ -55,8 +55,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                         realDoc.Set(new Key(this._keyInput), dataField);
                     }
                 }
-                this._keyInput = ""
-                this._valueInput = ""
+                this._keyInput = "";
+                this._valueInput = "";
             }
         }
     }
@@ -73,7 +73,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     createTable = () => {
         let doc = this.props.Document.GetT(KeyStore.Data, Document);
         if (!doc || doc === FieldWaiting) {
-            return <tr><td>Loading...</td></tr>
+            return <tr><td>Loading...</td></tr>;
         }
         let realDoc = doc;
 
@@ -84,13 +84,13 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                 if (!(key in ids)) {
                     ids[key] = key;
                 }
-            })
+            });
         }
 
         let rows: JSX.Element[] = [];
         let i = 0;
         for (let key in ids) {
-            rows.push(<KeyValuePair doc={realDoc} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />)
+            rows.push(<KeyValuePair doc={realDoc} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />);
         }
         return rows;
     }
@@ -125,6 +125,6 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                     {this.newKeyValue()}
                 </tbody>
             </table>
-        </div>)
+        </div>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index c6a0a7296..a1050dc6e 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,14 +1,14 @@
 import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
 import "./KeyValueBox.scss";
 import "./KeyValuePair.scss";
-import React = require("react")
+import React = require("react");
 import { FieldViewProps, FieldView } from './FieldView';
 import { Opt, Field } from '../../../fields/Field';
-import { observer } from "mobx-react"
+import { observer } from "mobx-react";
 import { observable, action } from 'mobx';
 import { Document } from '../../../fields/Document';
 import { Key } from '../../../fields/Key';
-import { Server } from "../../Server"
+import { Server } from "../../Server";
 import { EditableView } from "../EditableView";
 import { CompileScript, ToField } from "../../util/Scripting";
 import { Transform } from '../../util/Transform';
@@ -25,7 +25,7 @@ export interface KeyValuePairProps {
 export class KeyValuePair extends React.Component<KeyValuePairProps> {
 
     @observable
-    private key: Opt<Key>
+    private key: Opt<Key>;
 
     constructor(props: KeyValuePairProps) {
         super(props);
@@ -41,7 +41,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
 
     render() {
         if (!this.key) {
-            return <tr><td>error</td><td></td></tr>
+            return <tr><td>error</td><td></td></tr>;
 
         }
         let props: FieldViewProps = {
@@ -55,7 +55,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             onActiveChanged: emptyFunction,
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyFunction,
-        }
+        };
         let contents = (
             <FieldView {...props} />
         );
@@ -101,6 +101,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                         return false;
                     }}></EditableView></td>
             </tr>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 4791d6029..b016a3d48 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,8 +2,8 @@ import { observable, computed, action } from "mobx";
 import React = require("react");
 import { SelectionManager } from "../../util/SelectionManager";
 import { observer } from "mobx-react";
-import './LinkBox.scss'
-import { KeyStore } from '../../../fields/KeyStore'
+import './LinkBox.scss';
+import { KeyStore } from '../../../fields/KeyStore';
 import { props } from "bluebird";
 import { DocumentView } from "./DocumentView";
 import { Document } from "../../../fields/Document";
@@ -30,7 +30,7 @@ interface Props {
     linkName: String;
     pairedDoc: Document;
     type: String;
-    showEditor: () => void
+    showEditor: () => void;
 }
 
 @observer
@@ -49,15 +49,16 @@ export class LinkBox extends React.Component<Props> {
                 } else if (contextDoc instanceof Document) {
                     this.props.pairedDoc.GetTAsync(KeyStore.Page, NumberField).then((pfield: any) => {
                         contextDoc.GetTAsync(KeyStore.CurPage, NumberField).then((cfield: any) => {
-                            if (pfield !== cfield)
+                            if (pfield !== cfield) {
                                 contextDoc.SetNumber(KeyStore.CurPage, pfield.Data);
+                            }
                             let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
                             if (contextView) {
                                 contextView.props.focus(contextDoc);
                             } else {
                                 CollectionDockingView.Instance.AddRightSplit(contextDoc);
                             }
-                        })
+                        });
                     });
                 }
             });
@@ -80,7 +81,7 @@ export class LinkBox extends React.Component<Props> {
                     if (field) {
                         field.Data.splice(field.Data.indexOf(this.props.linkDoc));
                     }
-                })
+                });
             }
         });
         this.props.linkDoc.GetTAsync(KeyStore.LinkedToDocs, Document, field => {
@@ -89,7 +90,7 @@ export class LinkBox extends React.Component<Props> {
                     if (field) {
                         field.Data.splice(field.Data.indexOf(this.props.linkDoc));
                     }
-                })
+                });
             }
         });
     }
@@ -117,6 +118,6 @@ export class LinkBox extends React.Component<Props> {
                         <FontAwesomeIcon className="fa-icon-delete" icon="times" size="sm" /></div>
                 </div>
             </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index 3f7b4bf2d..bde50fed8 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -2,8 +2,8 @@ import { observable, computed, action } from "mobx";
 import React = require("react");
 import { SelectionManager } from "../../util/SelectionManager";
 import { observer } from "mobx-react";
-import './LinkEditor.scss'
-import { KeyStore } from '../../../fields/KeyStore'
+import './LinkEditor.scss';
+import { KeyStore } from '../../../fields/KeyStore';
 import { props } from "bluebird";
 import { DocumentView } from "./DocumentView";
 import { Document } from "../../../fields/Document";
@@ -43,7 +43,7 @@ export class LinkEditor extends React.Component<Props> {
                 <div className="save-button" onPointerDown={this.onSaveButtonPressed}>SAVE</div>
             </div>
 
-        )
+        );
     }
 
     @action
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 6c2d24630..ac09da305 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -13,7 +13,7 @@ import React = require("react");
 
 interface Props {
     docView: DocumentView;
-    changeFlyout: () => void
+    changeFlyout: () => void;
 }
 
 @observer
@@ -25,9 +25,9 @@ export class LinkMenu extends React.Component<Props> {
         return links.map(link => {
             let doc = link.GetT(key, Document);
             if (doc && doc !== FieldWaiting) {
-                return <LinkBox key={doc.Id} linkDoc={link} linkName={link.Title} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />
+                return <LinkBox key={doc.Id} linkDoc={link} linkName={link.Title} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
             }
-        })
+        });
     }
 
     render() {
@@ -43,11 +43,11 @@ export class LinkMenu extends React.Component<Props> {
                         {this.renderLinkItems(linkFrom, KeyStore.LinkedFromDocs, "Source: ")}
                     </div>
                 </div>
-            )
+            );
         } else {
             return (
                 <LinkEditor linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor>
-            )
+            );
         }
 
     }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 774d9be3e..81ceb37f6 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -17,7 +17,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import "./PDFBox.scss";
 import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
-import React = require("react")
+import React = require("react");
 import { SelectionManager } from "../../util/SelectionManager";
 
 /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
@@ -55,7 +55,7 @@ import { SelectionManager } from "../../util/SelectionManager";
 export class PDFBox extends React.Component<FieldViewProps> {
     public static LayoutString() { return FieldView.LayoutString(PDFBox); }
 
-    private _mainDiv = React.createRef<HTMLDivElement>()
+    private _mainDiv = React.createRef<HTMLDivElement>();
     private _pdf = React.createRef<HTMLCanvasElement>();
 
     @observable private _renderAsSvg = true;
@@ -72,7 +72,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
 
     private _currTool: any; //keeps track of current tool button reference
     private _drawToolOn: boolean = false; //boolean that keeps track of the drawing tool 
-    private _drawTool = React.createRef<HTMLButtonElement>()//drawing tool button reference
+    private _drawTool = React.createRef<HTMLButtonElement>();//drawing tool button reference
 
     private _colorTool = React.createRef<HTMLButtonElement>(); //color button reference
     private _currColor: string = "black"; //current color that user selected (for ink/pen)
@@ -85,7 +85,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
     @observable private _perPageInfo: Object[] = []; //stores pageInfo
     @observable private _pageInfo: any = { area: [], divs: [], anno: [] }; //divs is array of objects linked to anno
 
-    @observable private _currAnno: any = []
+    @observable private _currAnno: any = [];
     @observable private _interactive: boolean = false;
     @observable private _loaded: boolean = false;
 
@@ -168,24 +168,24 @@ export class PDFBox extends React.Component<FieldViewProps> {
             let obj: Object = { parentDivs: [], spans: [] };
             //@ts-ignore
             if (range.commonAncestorContainer.className === 'react-pdf__Page__textContent') { //multiline highlighting case
-                obj = this.highlightNodes(range.commonAncestorContainer.childNodes)
+                obj = this.highlightNodes(range.commonAncestorContainer.childNodes);
             } else { //single line highlighting case
-                let parentDiv = range.commonAncestorContainer.parentElement
+                let parentDiv = range.commonAncestorContainer.parentElement;
                 if (parentDiv) {
                     if (parentDiv.className === 'react-pdf__Page__textContent') { //when highlight is overwritten
-                        obj = this.highlightNodes(parentDiv.childNodes)
+                        obj = this.highlightNodes(parentDiv.childNodes);
                     } else {
                         parentDiv.childNodes.forEach((child) => {
                             if (child.nodeName === 'SPAN') {
                                 //@ts-ignore
-                                obj.parentDivs.push(parentDiv)
+                                obj.parentDivs.push(parentDiv);
                                 //@ts-ignore
-                                child.id = "highlighted"
+                                child.id = "highlighted";
                                 //@ts-ignore
-                                obj.spans.push(child)
+                                obj.spans.push(child);
                                 child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
                             }
-                        })
+                        });
                     }
                 }
             }
@@ -196,21 +196,21 @@ export class PDFBox extends React.Component<FieldViewProps> {
     }
 
     highlightNodes = (nodes: NodeListOf<ChildNode>) => {
-        let temp = { parentDivs: [], spans: [] }
+        let temp = { parentDivs: [], spans: [] };
         nodes.forEach((div) => {
             div.childNodes.forEach((child) => {
                 if (child.nodeName === 'SPAN') {
                     //@ts-ignore
-                    temp.parentDivs.push(div)
+                    temp.parentDivs.push(div);
                     //@ts-ignore
-                    child.id = "highlighted"
+                    child.id = "highlighted";
                     //@ts-ignore
-                    temp.spans.push(child)
+                    temp.spans.push(child);
                     child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
                 }
-            })
+            });
 
-        })
+        });
         return temp;
     }
 
@@ -228,8 +228,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
                         index = this._pageInfo.divs.indexOf(obj);
                     }
                 }
-            })
-        })
+            });
+        });
 
         if (this._pageInfo.anno.length >= index + 1) {
             if (this._currAnno.length === 0) {
@@ -239,13 +239,13 @@ export class PDFBox extends React.Component<FieldViewProps> {
             if (this._currAnno.length === 0) { //if there are no current annotation
                 let div = span.offsetParent;
                 //@ts-ignore
-                let divX = div.style.left
+                let divX = div.style.left;
                 //@ts-ignore
-                let divY = div.style.top
+                let divY = div.style.top;
                 //slicing "px" from the end
                 divX = divX.slice(0, divX.length - 2); //gets X of the DIV element (parent of Span)
                 divY = divY.slice(0, divY.length - 2); //gets Y of the DIV element (parent of Span)
-                let annotation = <Annotation key={Utils.GenerateGuid()} Span={span} X={divX} Y={divY - 300} Highlights={this._pageInfo.divs} Annotations={this._pageInfo.anno} CurrAnno={this._currAnno} />
+                let annotation = <Annotation key={Utils.GenerateGuid()} Span={span} X={divX} Y={divY - 300} Highlights={this._pageInfo.divs} Annotations={this._pageInfo.anno} CurrAnno={this._currAnno} />;
                 this._pageInfo.anno.push(annotation);
                 this._currAnno.push(annotation);
             }
@@ -263,7 +263,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
                     this.makeEditableAndHighlight(color);
                 }
             } catch (ex) {
-                this.makeEditableAndHighlight(color)
+                this.makeEditableAndHighlight(color);
             }
         }
     }
@@ -305,7 +305,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
             }
 
             if (this._mainDiv.current) {
-                let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />
+                let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />;
                 this._pageInfo.area.push(sticky);
             }
             this._toolOn = false;
@@ -404,19 +404,19 @@ export class PDFBox extends React.Component<FieldViewProps> {
 
                         if (e instanceof HTMLCanvasElement) {
                             this._pdfCanvas = e;
-                            this._pdfContext = e.getContext("2d")
+                            this._pdfContext = e.getContext("2d");
 
                         }
 
-                    })
+                    });
                 }
-            })
+            });
         }
 
         // bcz: the number of pages should really be set when the document is imported.
         this.props.Document.SetNumber(KeyStore.NumPages, page._transport.numPages);
         if (this._perPageInfo.length === 0) { //Makes sure it only runs once
-            this._perPageInfo = [...Array(page._transport.numPages)]
+            this._perPageInfo = [...Array(page._transport.numPages)];
         }
         this._loaded = true;
     }
diff --git a/src/client/views/nodes/Sticky.tsx b/src/client/views/nodes/Sticky.tsx
index 4a4d69e90..11719831b 100644
--- a/src/client/views/nodes/Sticky.tsx
+++ b/src/client/views/nodes/Sticky.tsx
@@ -39,7 +39,7 @@ export class Sticky extends React.Component<IProps> {
       document.addEventListener("pointermove", this.drawMove);
       document.addEventListener("pointerup", this.drawUp);
     }
-  };
+  }
 
   //when user drags
   drawMove = (e: PointerEvent): void => {
@@ -49,7 +49,7 @@ export class Sticky extends React.Component<IProps> {
     //connects the point
     this.ctx.lineTo(x, y);
     this.ctx.stroke();
-  };
+  }
 
   /**
    * when user lifts the mouse, the drawing ends
@@ -58,7 +58,7 @@ export class Sticky extends React.Component<IProps> {
     this.ctx.closePath();
     console.log(this.ctx);
     document.removeEventListener("pointermove", this.drawMove);
-  };
+  }
 
   render() {
     return (
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index a08b320e8..314af64c9 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { observer } from "mobx-react";
 import { FieldWaiting, Opt } from '../../../fields/Field';
 import { VideoField } from '../../../fields/VideoField';
@@ -13,8 +13,8 @@ import { number } from "prop-types";
 export class VideoBox extends React.Component<FieldViewProps> {
 
     private _reactionDisposer: Opt<IReactionDisposer>;
-    private _videoRef = React.createRef<HTMLVideoElement>()
-    public static LayoutString() { return FieldView.LayoutString(VideoBox) }
+    private _videoRef = React.createRef<HTMLVideoElement>();
+    public static LayoutString() { return FieldView.LayoutString(VideoBox); }
 
     constructor(props: FieldViewProps) {
         super(props);
@@ -58,7 +58,7 @@ export class VideoBox extends React.Component<FieldViewProps> {
     render() {
         let field = this.props.Document.GetT(this.props.fieldKey, VideoField);
         if (!field || field === FieldWaiting) {
-            return <div>Loading</div>
+            return <div>Loading</div>;
         }
         let path = field.Data.href;
         trace();
@@ -73,6 +73,6 @@ export class VideoBox extends React.Component<FieldViewProps> {
                     </div>
                 }
             </Measure>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c1d389001..90ce72c41 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,9 +1,9 @@
 import "./WebBox.scss";
-import React = require("react")
+import React = require("react");
 import { WebField } from '../../../fields/WebField';
 import { FieldViewProps, FieldView } from './FieldView';
 import { FieldWaiting } from '../../../fields/Field';
-import { observer } from "mobx-react"
+import { observer } from "mobx-react";
 import { computed } from 'mobx';
 import { KeyStore } from '../../../fields/KeyStore';
 
@@ -33,6 +33,6 @@ export class WebBox extends React.Component<FieldViewProps> {
         return (
             <div className="webBox-cont"  >
                 {content}
-            </div>)
+            </div>);
     }
 }
\ No newline at end of file
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index c8de33f41..11f2b0c4e 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,10 +1,10 @@
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
-import JsxParser from 'react-jsx-parser'
+import JsxParser from 'react-jsx-parser';
 
 class Hello extends React.Component<{ firstName: string, lastName: string }> {
     render() {
-        return <div>Hello {this.props.firstName} {this.props.lastName}</div>
+        return <div>Hello {this.props.firstName} {this.props.lastName}</div>;
     }
 }
 
@@ -16,8 +16,8 @@ class Test extends React.Component {
                 firstName: "First",
                 lastName: "Last"
             }
-        }
-        return <JsxParser jsx={jsx} bindings={bindings} components={{ Hello }}></JsxParser>
+        };
+        return <JsxParser jsx={jsx} bindings={bindings} components={{ Hello }}></JsxParser>;
     }
 }
 
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index f13cccd24..857da1ebb 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -40,22 +40,22 @@ class ListViewer extends React.Component<{ field: ListField<Field> }>{
                 <div>
                     {this.props.field.Data.map(field => <DebugViewer fieldId={field.Id} key={field.Id} />)}
                 </div>
-            )
+            );
         } else {
-            content = <>[...] ({this.props.field.Id})</>
+            content = <>[...] ({this.props.field.Id})</>;
         }
         return (
             <div>
                 <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
                 {content}
             </div >
-        )
+        );
     }
 }
 
 @observer
 class DocumentViewer extends React.Component<{ field: Document }> {
-    private keyMap: ObservableMap<string, Key> = new ObservableMap
+    private keyMap: ObservableMap<string, Key> = new ObservableMap;
 
     private disposer?: Lambda;
 
@@ -67,12 +67,12 @@ class DocumentViewer extends React.Component<{ field: Document }> {
                         if (field && field instanceof Key) {
                             this.keyMap.set(id, field);
                         }
-                    })
+                    });
                 }
             });
-        }
-        this.disposer = this.props.field._proxies.observe(f)
-        f()
+        };
+        this.disposer = this.props.field._proxies.observe(f);
+        f();
     }
 
     componentWillUnmount() {
@@ -89,8 +89,8 @@ class DocumentViewer extends React.Component<{ field: Document }> {
                     <b>({key ? key.Name : kv[0]}): </b>
                     <DebugViewer fieldId={kv[1]}></DebugViewer>
                 </div>
-            )
-        })
+            );
+        });
         return (
             <div>
                 Document ({this.props.field.Id})
@@ -98,7 +98,7 @@ class DocumentViewer extends React.Component<{ field: Document }> {
                     {fields}
                 </div>
             </div>
-        )
+        );
     }
 }
 
@@ -111,15 +111,15 @@ class DebugViewer extends React.Component<{ fieldId: string }> {
     private error?: string;
 
     constructor(props: { fieldId: string }) {
-        super(props)
-        this.update()
+        super(props);
+        this.update();
     }
 
     update() {
         Server.GetField(this.props.fieldId, action((field: Opt<Field>) => {
             this.field = field;
             if (!field) {
-                this.error = `Field with id ${this.props.fieldId} not found`
+                this.error = `Field with id ${this.props.fieldId} not found`;
             }
         }));
 
@@ -130,20 +130,20 @@ class DebugViewer extends React.Component<{ fieldId: string }> {
         if (this.field) {
             // content = this.field.ToJson();
             if (this.field instanceof ListField) {
-                content = (<ListViewer field={this.field} />)
+                content = (<ListViewer field={this.field} />);
             } else if (this.field instanceof Document) {
-                content = (<DocumentViewer field={this.field} />)
+                content = (<DocumentViewer field={this.field} />);
             } else if (this.field instanceof BasicField) {
-                content = (<FieldViewer field={this.field} />)
+                content = (<FieldViewer field={this.field} />);
             } else if (this.field instanceof Key) {
-                content = (<KeyViewer field={this.field} />)
+                content = (<KeyViewer field={this.field} />);
             } else {
-                content = (<span>Unrecognized field type</span>)
+                content = (<span>Unrecognized field type</span>);
             }
         } else if (this.error) {
-            content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>
+            content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>;
         } else {
-            content = <span>Field loading: {this.props.fieldId}</span>
+            content = <span>Field loading: {this.props.fieldId}</span>;
         }
         return content;
     }
@@ -165,8 +165,8 @@ class Viewer extends React.Component {
     @action
     onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
         if (e.key === "Enter") {
-            this.ids.push(this.idToAdd)
-            this.idToAdd = ""
+            this.ids.push(this.idToAdd);
+            this.idToAdd = "";
         }
     }
 
@@ -180,7 +180,7 @@ class Viewer extends React.Component {
                     {this.ids.map(id => <DebugViewer fieldId={id} key={id}></DebugViewer>)}
                 </div>
             </>
-        )
+        );
     }
 }
 
diff --git a/src/fields/AudioField.ts b/src/fields/AudioField.ts
index 252a5b74e..996d2556d 100644
--- a/src/fields/AudioField.ts
+++ b/src/fields/AudioField.ts
@@ -25,7 +25,7 @@ export class AudioField extends BasicField<URL> {
             type: Types.Audio,
             data: this.Data.href,
             _id: this.Id
-        }
+        };
     }
 
 }
\ No newline at end of file
diff --git a/src/fields/BasicField.ts b/src/fields/BasicField.ts
index 3083a1937..17b1fc4e8 100644
--- a/src/fields/BasicField.ts
+++ b/src/fields/BasicField.ts
@@ -1,4 +1,4 @@
-import { Field, FieldId } from "./Field"
+import { Field, FieldId } from "./Field";
 import { observable, computed, action } from "mobx";
 import { Server } from "../client/Server";
 import { UndoManager } from "../client/util/UndoManager";
@@ -9,7 +9,7 @@ export abstract class BasicField<T> extends Field {
 
         this.data = data;
         if (save) {
-            Server.UpdateField(this)
+            Server.UpdateField(this);
         }
     }
 
@@ -36,7 +36,7 @@ export abstract class BasicField<T> extends Field {
         UndoManager.AddEvent({
             undo: () => this.Data = oldValue,
             redo: () => this.Data = value
-        })
+        });
         Server.UpdateField(this);
     }
 
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 4fa478f32..4584660fb 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -37,21 +37,24 @@ export class Document extends Field {
         }
     }
 
-    public Width = () => this.GetNumber(KeyStore.Width, 0)
+    public Width = () => this.GetNumber(KeyStore.Width, 0);
     public Height = () => this.GetNumber(KeyStore.Height, this.GetNumber(KeyStore.NativeWidth, 0) ? (this.GetNumber(KeyStore.NativeHeight, 0) / this.GetNumber(KeyStore.NativeWidth, 0)) * this.GetNumber(KeyStore.Width, 0) : 0);
-    public Scale = () => this.GetNumber(KeyStore.Scale, 1)
+    public Scale = () => this.GetNumber(KeyStore.Scale, 1);
 
     @computed
     public get Title(): string {
         let title = this.Get(KeyStore.Title, true);
-        if (title)
-            if (title !== FieldWaiting && title instanceof TextField)
+        if (title) {
+            if (title !== FieldWaiting && title instanceof TextField) {
                 return title.Data;
+            }
             else return "-waiting-";
+        }
         let parTitle = this.GetT(KeyStore.Title, TextField);
-        if (parTitle)
+        if (parTitle) {
             if (parTitle !== FieldWaiting) return parTitle.Data + ".alias";
             else return "-waiting-.alias";
+        }
         return "-untitled-";
     }
 
@@ -398,11 +401,13 @@ export class Document extends Field {
                         }
                     }
                     else
-                        if (field instanceof Document)  // ... TODO bcz: should we copy documents or reference them
-                            copy.Set(key!, field)
-                        else if (field)
-                            copy.Set(key!, field.Copy())
-                })
+                        if (field instanceof Document) {  // ... TODO bcz: should we copy documents or reference them
+                            copy.Set(key!, field);
+                        }
+                        else if (field) {
+                            copy.Set(key!, field.Copy());
+ }
+                });
             }
         });
         return copy;
diff --git a/src/fields/DocumentReference.ts b/src/fields/DocumentReference.ts
index 9d3c209b4..6c0c1ef82 100644
--- a/src/fields/DocumentReference.ts
+++ b/src/fields/DocumentReference.ts
@@ -52,6 +52,6 @@ export class DocumentReference extends Field {
             type: Types.DocumentReference,
             data: this.document.Id,
             _id: this.Id
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/Field.ts b/src/fields/Field.ts
index 0d0e56f77..d9db23b9e 100644
--- a/src/fields/Field.ts
+++ b/src/fields/Field.ts
@@ -65,5 +65,5 @@ export abstract class Field {
 
     abstract Copy(): Field;
 
-    abstract ToJson(): { _id: string, type: Types, data: any }
+    abstract ToJson(): { _id: string, type: Types, data: any };
 }
\ No newline at end of file
diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts
index 7cbdf7e58..65665cf7a 100644
--- a/src/fields/HtmlField.ts
+++ b/src/fields/HtmlField.ts
@@ -20,6 +20,6 @@ export class HtmlField extends BasicField<string> {
             type: Types.Html,
             data: this.Data,
             _id: this.Id,
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts
index ef616b2ad..dd843026f 100644
--- a/src/fields/ImageField.ts
+++ b/src/fields/ImageField.ts
@@ -24,6 +24,6 @@ export class ImageField extends BasicField<URL> {
             type: Types.Image,
             data: this.Data.href,
             _id: this.Id
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 2a4ed18e7..ab706ee30 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -36,7 +36,7 @@ export class InkField extends BasicField<StrokeMap> {
             type: Types.Ink,
             data: this.Data,
             _id: this.Id,
-        }
+        };
     }
 
     UpdateFromServer(data: any) {
diff --git a/src/fields/Key.ts b/src/fields/Key.ts
index 00d78d516..c7f806b88 100644
--- a/src/fields/Key.ts
+++ b/src/fields/Key.ts
@@ -1,4 +1,4 @@
-import { Field, FieldId } from "./Field"
+import { Field, FieldId } from "./Field";
 import { Utils } from "../Utils";
 import { observable } from "mobx";
 import { Types } from "../server/Message";
@@ -16,7 +16,7 @@ export class Key extends Field {
 
         this.name = name;
         if (save) {
-            Server.UpdateField(this)
+            Server.UpdateField(this);
         }
     }
 
@@ -45,6 +45,6 @@ export class Key extends Field {
             type: Types.Key,
             data: this.name,
             _id: this.Id
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index e24e9c8ec..425408273 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -56,8 +56,9 @@ export namespace KeyStore {
     ];
     export function KeyLookup(keyid: string) {
         for (const key of KeyList) {
-            if (key.Id === keyid)
+            if (key.Id === keyid) {
                 return key;
+            }
         }
         return undefined;
     }
diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts
index 815a3df73..b6eab5f86 100644
--- a/src/fields/ListField.ts
+++ b/src/fields/ListField.ts
@@ -7,7 +7,7 @@ import { Field, FieldId } from "./Field";
 import { FieldMap } from "../client/SocketStub";
 
 export class ListField<T extends Field> extends BasicField<T[]> {
-    private _proxies: string[] = []
+    private _proxies: string[] = [];
     constructor(data: T[] = [], id?: FieldId, save: boolean = true) {
         super(data, save, id);
         this.updateProxies();
@@ -22,23 +22,24 @@ export class ListField<T extends Field> extends BasicField<T[]> {
     private observeDisposer: Lambda | undefined;
     private observeList(): void {
         if (this.observeDisposer) {
-            this.observeDisposer()
+            this.observeDisposer();
         }
         this.observeDisposer = observe(this.Data as IObservableArray<T>, (change: IArrayChange<T> | IArraySplice<T>) => {
-            this.updateProxies()
+            this.updateProxies();
             if (change.type === "splice") {
                 UndoManager.AddEvent({
                     undo: () => this.Data.splice(change.index, change.addedCount, ...change.removed),
                     redo: () => this.Data.splice(change.index, change.removedCount, ...change.added)
-                })
+                });
             } else {
                 UndoManager.AddEvent({
                     undo: () => this.Data[change.index] = change.oldValue,
                     redo: () => this.Data[change.index] = change.newValue
-                })
+                });
             }
-            if (!this._processingServerUpdate)
+            if (!this._processingServerUpdate) {
                 Server.UpdateField(this);
+            }
         });
     }
 
@@ -78,19 +79,23 @@ export class ListField<T extends Field> extends BasicField<T[]> {
                 var proxies = this._proxies.map(p => p);
                 var added = this.data.length < this._proxies.length;
                 var deleted = this.data.length > this._proxies.length;
-                for (let i = 0; i < dataids.length && added; i++)
+                for (let i = 0; i < dataids.length && added; i++) {
                     added = proxies.indexOf(dataids[i]) !== -1;
-                for (let i = 0; i < this._proxies.length && deleted; i++)
+                }
+                for (let i = 0; i < this._proxies.length && deleted; i++) {
                     deleted = dataids.indexOf(proxies[i]) !== -1;
+                }
 
                 this._processingServerUpdate = true;
                 for (let i = 0; i < proxies.length && added; i++) {
-                    if (dataids.indexOf(proxies[i]) === -1)
+                    if (dataids.indexOf(proxies[i]) === -1) {
                         this.Data.splice(i, 0, fields[proxies[i]] as T);
+                    }
                 }
                 for (let i = dataids.length - 1; i >= 0 && deleted; i--) {
-                    if (proxies.indexOf(dataids[i]) === -1)
+                    if (proxies.indexOf(dataids[i]) === -1) {
                         this.Data.splice(i, 1);
+                    }
                 }
                 if (!added && !deleted) {// otherwise, just rebuild the whole list
                     this.setData(proxies.map(id => fields[id] as T));
@@ -98,7 +103,7 @@ export class ListField<T extends Field> extends BasicField<T[]> {
                 this._processingServerUpdate = false;
             }
             callback(this);
-        }))
+        }));
     }
 
     ToScriptString(): string {
@@ -114,12 +119,12 @@ export class ListField<T extends Field> extends BasicField<T[]> {
             type: Types.List,
             data: this._proxies || [],
             _id: this.Id
-        }
+        };
     }
 
     static FromJson(id: string, ids: string[]): ListField<Field> {
         let list = new ListField([], id, false);
         list._proxies = ids;
-        return list
+        return list;
     }
 }
\ No newline at end of file
diff --git a/src/fields/NumberField.ts b/src/fields/NumberField.ts
index e0c8648de..45b920e31 100644
--- a/src/fields/NumberField.ts
+++ b/src/fields/NumberField.ts
@@ -1,4 +1,4 @@
-import { BasicField } from "./BasicField"
+import { BasicField } from "./BasicField";
 import { Types } from "../server/Message";
 import { FieldId } from "./Field";
 
@@ -20,6 +20,6 @@ export class NumberField extends BasicField<number> {
             _id: this.Id,
             type: Types.Number,
             data: this.Data
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/PDFField.ts b/src/fields/PDFField.ts
index 436c1cf2b..65e179894 100644
--- a/src/fields/PDFField.ts
+++ b/src/fields/PDFField.ts
@@ -1,6 +1,6 @@
 import { BasicField } from "./BasicField";
 import { Field, FieldId } from "./Field";
-import { observable } from "mobx"
+import { observable } from "mobx";
 import { Types } from "../server/Message";
 
 
@@ -27,7 +27,7 @@ export class PDFField extends BasicField<URL> {
             type: Types.PDF,
             data: this.Data.href,
             _id: this.Id
-        }
+        };
     }
 
     @observable
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index 5efb43314..6f7b3074a 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -20,7 +20,7 @@ export class RichTextField extends BasicField<string> {
             type: Types.RichText,
             data: this.Data,
             _id: this.Id
-        }
+        };
     }
 
 }
\ No newline at end of file
diff --git a/src/fields/TextField.ts b/src/fields/TextField.ts
index 71d8ea310..69d26f42f 100644
--- a/src/fields/TextField.ts
+++ b/src/fields/TextField.ts
@@ -1,4 +1,4 @@
-import { BasicField } from "./BasicField"
+import { BasicField } from "./BasicField";
 import { FieldId } from "./Field";
 import { Types } from "../server/Message";
 
@@ -20,6 +20,6 @@ export class TextField extends BasicField<string> {
             type: Types.Text,
             data: this.Data,
             _id: this.Id
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/TupleField.ts b/src/fields/TupleField.ts
index e2162c751..ad0f6f350 100644
--- a/src/fields/TupleField.ts
+++ b/src/fields/TupleField.ts
@@ -21,7 +21,7 @@ export class TupleField<T, U> extends BasicField<[T, U]> {
                 UndoManager.AddEvent({
                     undo: () => this.Data[change.index] = change.oldValue,
                     redo: () => this.Data[change.index] = change.newValue
-                })
+                });
                 Server.UpdateField(this);
             } else {
                 throw new Error("Why are you messing with the length of a tuple, huh?");
@@ -31,7 +31,7 @@ export class TupleField<T, U> extends BasicField<[T, U]> {
 
     protected setData(value: [T, U]) {
         if (this.observeDisposer) {
-            this.observeDisposer()
+            this.observeDisposer();
         }
         this.data = observable(value) as (T | U)[] as [T, U];
         this.observeTuple();
@@ -54,6 +54,6 @@ export class TupleField<T, U> extends BasicField<[T, U]> {
             type: Types.Tuple,
             data: this.Data,
             _id: this.Id
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/VideoField.ts b/src/fields/VideoField.ts
index 992cc1641..d7cd7e968 100644
--- a/src/fields/VideoField.ts
+++ b/src/fields/VideoField.ts
@@ -24,7 +24,7 @@ export class VideoField extends BasicField<URL> {
             type: Types.Video,
             data: this.Data.href,
             _id: this.Id
-        }
+        };
     }
 
 }
\ No newline at end of file
diff --git a/src/fields/WebField.ts b/src/fields/WebField.ts
index 0cbcc6d33..6023e9e6b 100644
--- a/src/fields/WebField.ts
+++ b/src/fields/WebField.ts
@@ -24,7 +24,7 @@ export class WebField extends BasicField<URL> {
             type: Types.Web,
             data: this.Data.href,
             _id: this.Id
-        }
+        };
     }
 
 }
\ No newline at end of file
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index c3684a0eb..ec89a1194 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -22,7 +22,7 @@ import { Opt } from '../fields/Field';
 // }
 
 const onFileLoad = async (file: any) => {
-    let imgPrev = document.getElementById("img_preview")
+    let imgPrev = document.getElementById("img_preview");
     if (imgPrev) {
         let files: File[] = file.target.files;
         if (files.length !== 0) {
@@ -30,15 +30,15 @@ const onFileLoad = async (file: any) => {
             let formData = new FormData();
             formData.append("file", files[0]);
 
-            const upload = window.location.origin + "/upload"
+            const upload = window.location.origin + "/upload";
             const res = await fetch(upload, {
                 method: 'POST',
                 body: formData
             });
             const json = await res.json();
             json.map(async (file: any) => {
-                let path = window.location.origin + file
-                var doc: Document = Documents.ImageDocument(path, { nativeWidth: 200, width: 200 })
+                let path = window.location.origin + file;
+                var doc: Document = Documents.ImageDocument(path, { nativeWidth: 200, width: 200 });
 
                 const res = await rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId));
                 if (!res) {
@@ -47,12 +47,12 @@ const onFileLoad = async (file: any) => {
                 const field = await Server.GetField(res);
                 let pending: Opt<Document>;
                 if (field instanceof Document) {
-                    pending = await field.GetTAsync(KeyStore.OptionalRightCollection, Document)
+                    pending = await field.GetTAsync(KeyStore.OptionalRightCollection, Document);
                 }
                 if (pending) {
                     pending.GetOrCreateAsync(KeyStore.Data, ListField, list => {
                         list.Data.push(doc);
-                    })
+                    });
                 }
             });
 
@@ -61,7 +61,7 @@ const onFileLoad = async (file: any) => {
             //imgPrev.setAttribute("src", window.location.origin + files[0].name)
         }
     }
-}
+};
 
 ReactDOM.render((
     <div className="imgupload_cont">
diff --git a/src/server/Client.ts b/src/server/Client.ts
index 6b8841658..02402a5a0 100644
--- a/src/server/Client.ts
+++ b/src/server/Client.ts
@@ -2,14 +2,14 @@ import { computed } from "mobx";
 
 export class Client {
     constructor(guid: string) {
-        this.guid = guid
+        this.guid = guid;
     }
 
     private guid: string;
 
     @computed
     public get GUID(): string {
-        return this.guid
+        return this.guid;
     }
 
 }
\ No newline at end of file
diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts
index b6fe15655..d42741410 100644
--- a/src/server/authentication/config/passport.ts
+++ b/src/server/authentication/config/passport.ts
@@ -1,4 +1,4 @@
-import * as passport from 'passport'
+import * as passport from 'passport';
 import * as passportLocal from 'passport-local';
 import * as mongodb from 'mongodb';
 import * as _ from "lodash";
@@ -22,7 +22,7 @@ passport.deserializeUser<any, any>((id, done) => {
 passport.use(new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => {
     User.findOne({ email: email.toLowerCase() }, (error: any, user: any) => {
         if (error) return done(error);
-        if (!user) return done(undefined, false, { message: "Invalid email or password" }) // invalid email
+        if (!user) return done(undefined, false, { message: "Invalid email or password" }); // invalid email
         user.comparePassword(password, (error: Error, isMatch: boolean) => {
             if (error) return done(error);
             if (!isMatch) return done(undefined, false, { message: "Invalid email or password" }); // invalid password
@@ -37,7 +37,7 @@ export let isAuthenticated = (req: Request, res: Response, next: NextFunction) =
         return next();
     }
     return res.redirect(RouteStore.login);
-}
+};
 
 export let isAuthorized = (req: Request, res: Response, next: NextFunction) => {
     const provider = req.path.split("/").slice(-1)[0];
diff --git a/src/server/authentication/controllers/WorkspacesMenu.tsx b/src/server/authentication/controllers/WorkspacesMenu.tsx
index 835432c8e..b08c1aebe 100644
--- a/src/server/authentication/controllers/WorkspacesMenu.tsx
+++ b/src/server/authentication/controllers/WorkspacesMenu.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 import { observable, action, configure, reaction, computed, ObservableMap, runInAction } from 'mobx';
 import { observer } from "mobx-react";
-import './WorkspacesMenu.css'
+import './WorkspacesMenu.css';
 import { Document } from '../../../fields/Document';
 import { EditableView } from '../../../client/views/EditableView';
 import { KeyStore } from '../../../fields/KeyStore';
diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/controllers/user_controller.ts
index 2bbb334b5..1dacdf3fa 100644
--- a/src/server/authentication/controllers/user_controller.ts
+++ b/src/server/authentication/controllers/user_controller.ts
@@ -109,12 +109,12 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
     }
 
     passport.authenticate("local", (err: Error, user: DashUserModel, info: IVerifyOptions) => {
-        if (err) { next(err); return }
+        if (err) { next(err); return; }
         if (!user) {
             return res.redirect(RouteStore.signup);
         }
         req.logIn(user, (err) => {
-            if (err) { next(err); return }
+            if (err) { next(err); return; }
             res.redirect(RouteStore.home);
         });
     })(req, res, next);
@@ -132,14 +132,14 @@ export let getLogout = (req: Request, res: Response) => {
         sess.destroy((err) => { if (err) { console.log(err); } });
     }
     res.redirect(RouteStore.login);
-}
+};
 
 export let getForgot = function (req: Request, res: Response) {
     res.render("forgot.pug", {
         title: "Recover Password",
         user: req.user,
     });
-}
+};
 
 export let postForgot = function (req: Request, res: Response, next: NextFunction) {
     const email = req.body.email;
@@ -152,14 +152,14 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
                     return;
                 }
                 done(null, buffer.toString('hex'));
-            })
+            });
         },
         function (token: string, done: any) {
             User.findOne({ email }, function (err, user: DashUserModel) {
                 if (!user) {
                     // NO ACCOUNT WITH SUBMITTED EMAIL
                     res.redirect(RouteStore.forgot);
-                    return
+                    return;
                 }
                 user.passwordResetToken = token;
                 user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR
@@ -193,8 +193,8 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
     ], function (err) {
         if (err) return next(err);
         res.redirect(RouteStore.forgot);
-    })
-}
+    });
+};
 
 export let getReset = function (req: Request, res: Response) {
     User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) {
@@ -206,7 +206,7 @@ export let getReset = function (req: Request, res: Response) {
             user: req.user,
         });
     });
-}
+};
 
 export let postReset = function (req: Request, res: Response) {
     async.waterfall([
@@ -263,4 +263,4 @@ export let postReset = function (req: Request, res: Response) {
     ], function (err) {
         res.redirect(RouteStore.login);
     });
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 0ac85b446..13eddafbf 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -70,7 +70,7 @@ export class CurrentUserUtils {
         let doc = new Document(id);
 
         doc.Set(KeyStore.Workspaces, new ListField<Document>());
-        doc.Set(KeyStore.OptionalRightCollection, Documents.SchemaDocument([], { title: "Pending documents" }))
+        doc.Set(KeyStore.OptionalRightCollection, Documents.SchemaDocument([], { title: "Pending documents" }));
         return doc;
     }
 
@@ -81,7 +81,7 @@ export class CurrentUserUtils {
                 CurrentUserUtils.curr_id = obj.id as string;
                 CurrentUserUtils.curr_email = obj.email as string;
             } else {
-                throw new Error("There should be a user! Why does Dash think there isn't one?")
+                throw new Error("There should be a user! Why does Dash think there isn't one?");
             }
         });
         let userDocPromise = rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId)).then(id => {
@@ -92,9 +92,9 @@ export class CurrentUserUtils {
                     } else {
                         this.user_document = this.createUserDocument(id);
                     }
-                })
+                });
             } else {
-                throw new Error("There should be a user id! Why does Dash think there isn't one?")
+                throw new Error("There should be a user id! Why does Dash think there isn't one?");
             }
         });
         return Promise.all([userPromise, userDocPromise]);
diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts
index 81580aad5..1c6926517 100644
--- a/src/server/authentication/models/user_model.ts
+++ b/src/server/authentication/models/user_model.ts
@@ -2,7 +2,7 @@
 import * as bcrypt from "bcrypt-nodejs";
 //@ts-ignore
 import * as mongoose from "mongoose";
-var url = 'mongodb://localhost:27017/Dash'
+var url = 'mongodb://localhost:27017/Dash';
 
 mongoose.connect(url, { useNewUrlParser: true });
 
diff --git a/src/server/database.ts b/src/server/database.ts
index 415acc09a..0bc806253 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -1,15 +1,15 @@
 import * as mongodb from 'mongodb';
 
 export class Database {
-    public static Instance = new Database()
+    public static Instance = new Database();
     private MongoClient = mongodb.MongoClient;
     private url = 'mongodb://localhost:27017/Dash';
     private db?: mongodb.Db;
 
     constructor() {
         this.MongoClient.connect(this.url, (err, client) => {
-            this.db = client.db()
-        })
+            this.db = client.db();
+        });
     }
 
     private currentWrites: { [_id: string]: Promise<void> } = {};
@@ -30,19 +30,19 @@ export class Database {
                     //     console.log(JSON.stringify(res.result));
                     // }
                     if (this.currentWrites[id] === promise) {
-                        delete this.currentWrites[id]
+                        delete this.currentWrites[id];
                     }
                     if (resolve) {
                         resolve();
                     }
                     callback();
                 });
-            }
+            };
             if (prom) {
                 const newProm: Promise<void> = prom.then(() => run(newProm));
                 this.currentWrites[id] = newProm;
             } else {
-                const newProm: Promise<void> = new Promise<void>(res => run(newProm, res))
+                const newProm: Promise<void> = new Promise<void>(res => run(newProm, res));
                 this.currentWrites[id] = newProm;
             }
         }
@@ -61,7 +61,7 @@ export class Database {
                 let collection = this.db.collection(collectionName);
                 collection.deleteMany({}, res);
             }
-        })
+        });
     }
 
     public insert(kvpairs: any) {
@@ -70,7 +70,7 @@ export class Database {
             collection.insertOne(kvpairs, (err: any, res: any) => {
                 if (err) {
                     // console.log(err)
-                    return
+                    return;
                 }
             });
         }
@@ -81,30 +81,30 @@ export class Database {
         if (this.db) {
             let collection = this.db.collection('documents');
             collection.findOne({ _id: id }, (err: any, res: any) => {
-                result = res
+                result = res;
                 if (!result) {
-                    fn(undefined)
+                    fn(undefined);
                 }
-                fn(result)
-            })
-        };
+                fn(result);
+            });
+        }
     }
 
     public getDocuments(ids: string[], fn: (res: any) => void) {
         if (this.db) {
             let collection = this.db.collection('documents');
-            let cursor = collection.find({ _id: { "$in": ids } })
+            let cursor = collection.find({ _id: { "$in": ids } });
             cursor.toArray((err, docs) => {
                 if (err) {
                     console.log(err.message);
                     console.log(err.errmsg);
                 }
                 fn(docs);
-            })
-        };
+            });
+        }
     }
 
     public print() {
-        console.log("db says hi!")
+        console.log("db says hi!");
     }
 }
diff --git a/src/server/index.ts b/src/server/index.ts
index f60e6e293..b9c7448b4 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,10 +1,10 @@
-import * as express from 'express'
-const app = express()
-import * as webpack from 'webpack'
+import * as express from 'express';
+const app = express();
+import * as webpack from 'webpack';
 import * as wdm from 'webpack-dev-middleware';
 import * as whm from 'webpack-hot-middleware';
-import * as path from 'path'
-import * as formidable from 'formidable'
+import * as path from 'path';
+import * as formidable from 'formidable';
 import * as passport from 'passport';
 import { MessageStore, Transferable } from "./Message";
 import { Client } from './Client';
@@ -13,7 +13,7 @@ import { Utils } from '../Utils';
 import { ObservableMap } from 'mobx';
 import { FieldId, Field } from '../fields/Field';
 import { Database } from './database';
-import * as io from 'socket.io'
+import * as io from 'socket.io';
 import { getLogin, postLogin, getSignup, postSignup, getLogout, postReset, getForgot, postForgot, getReset } from './authentication/controllers/user_controller';
 const config = require('../../webpack.config');
 const compiler = webpack(config);
@@ -31,19 +31,19 @@ const MongoStore = require('connect-mongo')(session);
 const mongoose = require('mongoose');
 import { DashUserModel } from './authentication/models/user_model';
 import * as fs from 'fs';
-import * as request from 'request'
+import * as request from 'request';
 import { RouteStore } from './RouteStore';
-import { exec } from 'child_process'
+import { exec } from 'child_process';
 
 const download = (url: string, dest: fs.PathLike) => {
     request.get(url).pipe(fs.createWriteStream(dest));
-}
+};
 
 const mongoUrl = 'mongodb://localhost:27017/Dash';
-mongoose.connect(mongoUrl)
+mongoose.connect(mongoUrl);
 mongoose.connection.on('connected', function () {
     console.log("connected");
-})
+});
 
 // SESSION MANAGEMENT AND AUTHENTICATION MIDDLEWARE
 // ORDER OF IMPORTS MATTERS
@@ -73,7 +73,7 @@ app.use((req, res, next) => {
 
 app.get("/hello", (req, res) => {
     res.send("<p>Hello</p>");
-})
+});
 
 enum Method {
     GET,
@@ -98,7 +98,7 @@ function addSecureRoute(method: Method,
         const dashUser: DashUserModel = req.user;
         if (!dashUser) return onRejection(res);
         handler(dashUser, res, req);
-    }
+    };
     subscribers.forEach(route => {
         switch (method) {
             case Method.GET:
@@ -116,7 +116,7 @@ function addSecureRoute(method: Method,
 let FieldStore: ObservableMap<FieldId, Field> = new ObservableMap();
 
 app.use(express.static(__dirname + RouteStore.public));
-app.use(RouteStore.images, express.static(__dirname + RouteStore.public))
+app.use(RouteStore.images, express.static(__dirname + RouteStore.public));
 
 app.get("/pull", (req, res) => {
     exec('"C:\\Program Files\\Git\\git-bash.exe" -c "git pull"', (err, stdout, stderr) => {
@@ -125,7 +125,7 @@ app.get("/pull", (req, res) => {
             return;
         }
         res.redirect("/");
-    })
+    });
 });
 
 // GETTERS
@@ -178,13 +178,13 @@ addSecureRoute(
 addSecureRoute(
     Method.POST,
     (user, res, req) => {
-        let form = new formidable.IncomingForm()
-        form.uploadDir = __dirname + "/public/files/"
-        form.keepExtensions = true
+        let form = new formidable.IncomingForm();
+        form.uploadDir = __dirname + "/public/files/";
+        form.keepExtensions = true;
         // let path = req.body.path;
-        console.log("upload")
+        console.log("upload");
         form.parse(req, (err, fields, files) => {
-            console.log("parsing")
+            console.log("parsing");
             let names: any[] = [];
             for (const name in files) {
                 let file = files[name];
@@ -211,8 +211,8 @@ app.post(RouteStore.login, postLogin);
 app.get(RouteStore.logout, getLogout);
 
 // FORGOT PASSWORD EMAIL HANDLING
-app.get(RouteStore.forgot, getForgot)
-app.post(RouteStore.forgot, postForgot)
+app.get(RouteStore.forgot, getForgot);
+app.post(RouteStore.forgot, postForgot);
 
 // RESET PASSWORD EMAIL HANDLING
 app.get(RouteStore.reset, getReset);
@@ -232,32 +232,32 @@ app.get(RouteStore.deleteAll, (req, res) => {
 
 app.use(wdm(compiler, {
     publicPath: config.output.publicPath
-}))
+}));
 
-app.use(whm(compiler))
+app.use(whm(compiler));
 
 // start the Express server
 app.listen(port, () => {
     console.log(`server started at http://localhost:${port}`);
-})
+});
 
 const server = io();
 interface Map {
     [key: string]: Client;
 }
-let clients: Map = {}
+let clients: Map = {};
 
 server.on("connection", function (socket: Socket) {
-    console.log("a user has connected")
+    console.log("a user has connected");
 
-    Utils.Emit(socket, MessageStore.Foo, "handshooken")
+    Utils.Emit(socket, MessageStore.Foo, "handshooken");
 
-    Utils.AddServerHandler(socket, MessageStore.Bar, barReceived)
-    Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args))
-    Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField)
-    Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields)
-    Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields)
-})
+    Utils.AddServerHandler(socket, MessageStore.Bar, barReceived);
+    Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args));
+    Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
+    Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
+    Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields);
+});
 
 function deleteFields() {
     return Database.Instance.deleteAll();
@@ -276,12 +276,12 @@ function barReceived(guid: String) {
 function getField([id, callback]: [string, (result: any) => void]) {
     Database.Instance.getDocument(id, (result: any) => {
         if (result) {
-            callback(result)
+            callback(result);
         }
         else {
-            callback(undefined)
+            callback(undefined);
         }
-    })
+    });
 }
 
 function getFields([ids, callback]: [string[], (result: any) => void]) {
@@ -291,7 +291,7 @@ function getFields([ids, callback]: [string[], (result: any) => void]) {
 function setField(socket: Socket, newValue: Transferable) {
     Database.Instance.update(newValue._id, newValue, () => {
         socket.broadcast.emit(MessageStore.SetField.Message, newValue);
-    })
+    });
 }
 
 server.listen(serverPort);
diff --git a/test/test.ts b/test/test.ts
index db24cae5f..16cace026 100644
--- a/test/test.ts
+++ b/test/test.ts
@@ -1,6 +1,6 @@
 import { NumberField } from "../src/fields/NumberField";
 import { expect } from 'chai';
-import 'mocha'
+import 'mocha';
 import { Key } from "../src/fields/Key";
 import { Document } from "../src/fields/Document";
 import { autorun, reaction } from "mobx";
@@ -17,7 +17,7 @@ describe('Number Controller', () => {
     it('Should update', () => {
         const numController = new NumberField(15);
         let ran = false;
-        reaction(() => numController.Data, (data) => { ran = true; })
+        reaction(() => numController.Data, (data) => { ran = true; });
         expect(ran).to.equal(false);
         numController.Data = 5;
         expect(ran).to.equal(true);
@@ -42,7 +42,7 @@ describe("Document", () => {
         let key = new Key("Test");
         let key2 = new Key("Test2");
         let ran = false;
-        reaction(() => doc.Get(key), (field) => { ran = true });
+        reaction(() => doc.Get(key), (field) => { ran = true; });
         expect(ran).to.equal(false);
 
         doc.Set(key2, new NumberField(4));
diff --git a/tslint.json b/tslint.json
index 54876916e..aa4dee4e5 100644
--- a/tslint.json
+++ b/tslint.json
@@ -27,14 +27,14 @@
         "arrow-return-shorthand": true,
         // "object-literal-shorthand": true,
         // "object-literal-sort-keys": true,
-        // "semicolon": [
-        //     true,
-        //     "always"
-        // ],
-        // "curly": [
-        //     true,
-        //     "ignore-same-line"
-        // ],
+        "semicolon": [
+            true,
+            "always"
+        ],
+        "curly": [
+            true,
+            "ignore-same-line"
+        ],
         // "quotemark": [
         //     true,
         //     "double",
-- 
cgit v1.2.3-70-g09d2


From fd8e6bf2cacf8bed7c3363eeb777c731741266d5 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 11 Apr 2019 20:19:57 -0400
Subject: made COLLECTION_BORDER_WIDTH a CSS variable accessible from code. 
 changed global css variable file name

---
 src/client/util/DragManager.ts                     |  5 ++--
 src/client/util/TooltipTextMenu.scss               |  2 +-
 src/client/views/ContextMenu.scss                  |  2 +-
 src/client/views/DocumentDecorations.scss          |  2 +-
 src/client/views/InkingCanvas.scss                 |  2 +-
 src/client/views/InkingControl.scss                |  2 +-
 src/client/views/Main.scss                         |  2 +-
 src/client/views/_global_variables.scss            | 28 -------------------
 src/client/views/_global_variables.scss.d.ts       |  7 -----
 src/client/views/_global_variables.ts              |  8 ------
 .../views/collections/CollectionBaseView.tsx       |  1 -
 .../views/collections/CollectionDockingView.scss   |  6 +++++
 .../views/collections/CollectionDockingView.tsx    |  9 +------
 .../views/collections/CollectionSchemaView.scss    |  6 +++--
 .../views/collections/CollectionSchemaView.tsx     | 10 ++++---
 .../views/collections/CollectionTreeView.scss      |  6 +++--
 .../views/collections/CollectionTreeView.tsx       |  4 +--
 .../CollectionFreeFormRemoteCursors.scss           |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.scss | 10 ++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 31 ++++++++++++----------
 src/client/views/globalCssVariables.scss           | 29 ++++++++++++++++++++
 src/client/views/globalCssVariables.scss.d.ts      |  8 ++++++
 src/client/views/nodes/DocumentView.scss           |  2 +-
 src/client/views/nodes/FormattedTextBox.scss       |  2 +-
 src/client/views/nodes/KeyValueBox.scss            |  2 +-
 src/client/views/nodes/KeyValuePair.scss           |  2 +-
 src/client/views/nodes/LinkBox.scss                |  2 +-
 src/client/views/nodes/LinkEditor.scss             |  2 +-
 28 files changed, 99 insertions(+), 95 deletions(-)
 delete mode 100644 src/client/views/_global_variables.scss
 delete mode 100644 src/client/views/_global_variables.scss.d.ts
 delete mode 100644 src/client/views/_global_variables.ts
 create mode 100644 src/client/views/globalCssVariables.scss
 create mode 100644 src/client/views/globalCssVariables.scss.d.ts

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 333c474c1..3d8bbb49c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -5,8 +5,7 @@ import { CollectionDockingView } from "../views/collections/CollectionDockingVie
 import { DocumentDecorations } from "../views/DocumentDecorations";
 import { Main } from "../views/Main";
 import { DocumentView } from "../views/nodes/DocumentView";
-// import globalStyles from "../views/_global_variables";
-import * as globalStyles from "../views/_global_variables.scss"; // bcz: why doesn't this work?
+import * as globalCssVariables from "../views/globalCssVariables.scss";
 
 export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction, copyOnDrop: boolean = false) {
     let onRowMove = action((e: PointerEvent): void => {
@@ -178,7 +177,7 @@ export namespace DragManager {
             dragElement.style.bottom = "";
             dragElement.style.left = "0";
             dragElement.style.transformOrigin = "0 0";
-            dragElement.style.zIndex = globalStyles.contextMenuZindex;// "1000";
+            dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "1000";
             dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
             dragElement.style.width = `${rect.width / scaleX}px`;
             dragElement.style.height = `${rect.height / scaleY}px`;
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index ea580d104..7deea3be6 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -1,4 +1,4 @@
-@import "../views/global_variables";
+@import "../views/globalCssVariables";
 
 .tooltipMenu {
     position: absolute;
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 5acf598cf..fe884ca85 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -1,4 +1,4 @@
-@import "global_variables";
+@import "globalCssVariables";
 .contextMenu-cont {
   position: absolute;
   display: flex;
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 321bda384..ce84b6106 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,4 +1,4 @@
-@import "global_variables";
+@import "globalCssVariables";
 
 .documentDecorations {
     position: absolute;
diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
index 42ae38c73..2c550051c 100644
--- a/src/client/views/InkingCanvas.scss
+++ b/src/client/views/InkingCanvas.scss
@@ -1,4 +1,4 @@
-@import "global_variables";
+@import "globalCssVariables";
 
 .inkingCanvas {
     opacity:0.99;
diff --git a/src/client/views/InkingControl.scss b/src/client/views/InkingControl.scss
index 0d8fd8784..ba4ec41af 100644
--- a/src/client/views/InkingControl.scss
+++ b/src/client/views/InkingControl.scss
@@ -1,4 +1,4 @@
-@import "global_variables";
+@import "globalCssVariables";
 .inking-control {
     position: absolute;
     left: 70px;
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 7329b8eb6..f3af26f37 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -1,4 +1,4 @@
-@import "global_variables";
+@import "globalCssVariables";
 @import "nodeModuleOverrides";
 html,
 body {
diff --git a/src/client/views/_global_variables.scss b/src/client/views/_global_variables.scss
deleted file mode 100644
index cd6af2dac..000000000
--- a/src/client/views/_global_variables.scss
+++ /dev/null
@@ -1,28 +0,0 @@
-@import url("https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Crimson+Text:400,400i,700");
-// colors
-$light-color: #fcfbf7;
-$light-color-secondary: rgb(241, 239, 235);
-$main-accent: #61aaa3;
-// $alt-accent: #cdd5ec;
-// $alt-accent: #cdeceb;
-$alt-accent: #59dff7;
-$lighter-alt-accent: rgb(207, 220, 240);
-$intermediate-color: #9c9396;
-$dark-color: #121721;
-// fonts
-$sans-serif: "Noto Sans", sans-serif;
-// $sans-serif: "Roboto Slab", sans-serif;
-$serif: "Crimson Text", serif;
-// misc values
-$border-radius: 0.3em;
-//
-
-                            // dragged items
-$contextMenu-zindex: 1000;  // context menu shows up over everything
-$mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
-$docDecorations-zindex: 998; // then doc decorations appear over everything else
-$remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
-
-:export  {
-    contextMenuZindex: $contextMenu-zindex;
-}
\ No newline at end of file
diff --git a/src/client/views/_global_variables.scss.d.ts b/src/client/views/_global_variables.scss.d.ts
deleted file mode 100644
index ebee7dbce..000000000
--- a/src/client/views/_global_variables.scss.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-
-interface IGlobalScss {
-    contextMenuZindex: string;  // context menu shows up over everything
-}
-declare const globalStyleVariables: IGlobalScss;
-
-export = globalStyleVariables;
\ No newline at end of file
diff --git a/src/client/views/_global_variables.ts b/src/client/views/_global_variables.ts
deleted file mode 100644
index caedc7aad..000000000
--- a/src/client/views/_global_variables.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import * as globalStyleVariables from "../views/_global_variables.scss";
-
-export interface IGlobalScss {
-    contextMenuZindex: string;  // context menu shows up over everything
-}
-let globalStyles = globalStyleVariables as any as IGlobalScss;
-
-export default globalStyles;
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index ac320eda3..0ace700ce 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -34,7 +34,6 @@ export interface CollectionViewProps extends FieldViewProps {
     contentRef?: React.Ref<HTMLDivElement>;
 }
 
-export const COLLECTION_BORDER_WIDTH = 1;
 
 @observer
 export class CollectionBaseView extends React.Component<CollectionViewProps> {
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 583d50c5b..13dd9faa8 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,8 +1,14 @@
+@import "../../views/globalCssVariables.scss";
+
 .collectiondockingview-content {
     height: 100%;
 }
 
 .collectiondockingview-container {
+    width: 100%;
+    height: 100%;
+    border-style: solid;
+    border-width: $COLLECTION_BORDER_WIDTH;
     position: absolute;
     top: 0;
     left: 0;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 212cf8a69..05470f405 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -13,7 +13,6 @@ import { Server } from "../../Server";
 import { undoBatch } from "../../util/UndoManager";
 import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionDockingView.scss";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView";
 import React = require("react");
 import { SubCollectionViewProps } from "./CollectionSubView";
 import { ServerUtils } from "../../../server/ServerUtil";
@@ -271,13 +270,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     render() {
         return (
             <div className="collectiondockingview-container" id="menuContainer"
-                onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef}
-                style={{
-                    width: "100%",
-                    height: "100%",
-                    borderStyle: "solid",
-                    borderWidth: `${COLLECTION_BORDER_WIDTH}px`,
-                }} />
+                onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
         );
     }
 }
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index c3a2e88ac..40e49bb5f 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -1,4 +1,4 @@
-@import "../global_variables";
+@import "../globalCssVariables";
 
 //options menu styling
 #schemaOptionsMenuBtn {
@@ -53,7 +53,9 @@
   
 
 .collectionSchemaView-container {
-    border: 1px solid $intermediate-color;
+    border-width: $COLLECTION_BORDER_WIDTH;
+    border-color : $intermediate-color;
+    border-style: solid;
     border-radius: $border-radius;
     box-sizing: border-box;
     position: absolute;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index f1b3e1b8f..0cb88eab0 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -25,7 +25,7 @@ import "./CollectionSchemaView.scss";
 import { CollectionView } from "./CollectionView";
 import { CollectionSubView } from "./CollectionSubView";
 import { TextField } from "../../../fields/TextField";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView";
+import * as globalCssVariables from "../../views/globalCssVariables.scss";
 import { emptyFunction, returnFalse } from "../../../Utils";
 
 
@@ -245,11 +245,13 @@ export class CollectionSchemaView extends CollectionSubView {
         this._contentScaling = r.entry.width / selected!.GetNumber(KeyStore.NativeWidth, r.entry.width);
     }
 
+    @computed
+    get borderWidth() { return globalCssVariables.COLLECTION_BORDER_WIDTH; }
     getContentScaling = (): number => this._contentScaling;
     getPanelWidth = (): number => this._panelWidth;
     getPanelHeight = (): number => this._panelHeight;
-    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling);
-    getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling);
+    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX, - this.borderWidth).scale(1 / this._contentScaling);
+    getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - this.borderWidth).scale(1 / this._contentScaling);
 
     focusDocument = (doc: Document) => { };
 
@@ -349,7 +351,7 @@ export class CollectionSchemaView extends CollectionSubView {
         </Flyout>);
 
         return (
-            <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} >
+            <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont}>
                 <div className="collectionSchemaView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
                     <Measure onResize={this.setTableDimensions}>
                         {({ measureRef }) =>
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index f2affbf55..973eead97 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,7 +1,9 @@
-@import "../global_variables";
+@import "../globalCssVariables";
 
 .collectionTreeView-dropTarget {
-    border: 0px solid transparent;
+    border-width: $COLLECTION_BORDER_WIDTH;
+    border-color: transparent;
+    border-style: solid;
     border-radius: $border-radius;
     box-sizing: border-box;
     height: 100%;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 659cff9fe..20ec16822 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -11,9 +11,9 @@ import { setupDrag, DragManager } from "../../util/DragManager";
 import { EditableView } from "../EditableView";
 import "./CollectionTreeView.scss";
 import { CollectionView } from "./CollectionView";
+import * as globalCssVariables from "../../views/globalCssVariables.scss";
 import { CollectionSubView } from "./CollectionSubView";
 import React = require("react");
-import { COLLECTION_BORDER_WIDTH } from './CollectionBaseView';
 import { props } from 'bluebird';
 
 
@@ -139,7 +139,7 @@ export class CollectionTreeView extends CollectionSubView {
             );
 
         return (
-            <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
+            <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
                 <div className="coll-title">
                     <EditableView
                         contents={this.props.Document.Title}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
index c38787802..c5b8fc5e8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
@@ -1,4 +1,4 @@
-@import "global_variables";
+@import "globalCssVariables";
 
 .collectionFreeFormRemoteCursors-cont {
     
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 81f2146e4..f3ac4a383 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,4 +1,4 @@
-@import "../../global_variables";
+@import "../../globalCssVariables";
 .collectionfreeformview-measure {
     position: absolute;
     top: 0;
@@ -28,8 +28,10 @@
     // background-size: 30px 30px;
   // }
 
+  border-width: $COLLECTION_BORDER_WIDTH;
   box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
-  border: 0px solid $light-color-secondary;
+  border-color:  $light-color-secondary;
+  border-style: solid; 
   border-radius: $border-radius;
   box-sizing: border-box;
   position: absolute;
@@ -49,7 +51,9 @@
   }
  
   opacity: 0.99;
-  border: 0px solid transparent;
+  border-width: 0;
+  border-color: transparent;
+  border-style: solid; 
   border-radius: $border-radius;
   box-sizing: border-box;
   position:absolute;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 01ebbe0e1..e38e71a68 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,31 +1,31 @@
-import { action, computed, observable, trace, ObservableSet, runInAction } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
+import Measure from "react-measure";
 import { Document } from "../../../../fields/Document";
 import { FieldWaiting } from "../../../../fields/Field";
 import { KeyStore } from "../../../../fields/KeyStore";
+import { NumberField } from "../../../../fields/NumberField";
 import { TextField } from "../../../../fields/TextField";
+import { emptyFunction, returnFalse } from "../../../../Utils";
+import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager } from "../../../util/DragManager";
+import { SelectionManager } from "../../../util/SelectionManager";
 import { Transform } from "../../../util/Transform";
 import { undoBatch } from "../../../util/UndoManager";
+import * as globalCssVariables from "../../../views/globalCssVariables.scss";
 import { InkingCanvas } from "../../InkingCanvas";
+import { Main } from "../../Main";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentContentsView } from "../../nodes/DocumentContentsView";
 import { DocumentViewProps } from "../../nodes/DocumentView";
-import { COLLECTION_BORDER_WIDTH } from "../CollectionBaseView";
 import { CollectionSubView } from "../CollectionSubView";
 import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
+import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
+import { PreviewCursor } from "./PreviewCursor";
 import React = require("react");
 import v5 = require("uuid/v5");
-import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
-import { PreviewCursor } from "./PreviewCursor";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { NumberField } from "../../../../fields/NumberField";
-import { Main } from "../../Main";
-import Measure from "react-measure";
-import { returnFalse, emptyFunction } from "../../../../Utils";
 
 @observer
 export class CollectionFreeFormView extends CollectionSubView {
@@ -297,8 +297,12 @@ export class CollectionFreeFormView extends CollectionSubView {
                 layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
     }
 
-    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform());
-    getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH);
+    @computed
+    get borderWidth() {
+        return this.isAnnotationOverlay ? 0 : globalCssVariables.COLLECTION_BORDER_WIDTH;
+    }
+    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform());
+    getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
     getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.scale).translate(this.panX, this.panY);
     noScaling = () => 1;
     childViews = () => this.views;
@@ -317,8 +321,7 @@ export class CollectionFreeFormView extends CollectionSubView {
                     <div className={`collectionfreeformview-measure`} ref={measureRef}>
                         <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
                             onPointerDown={this.onPointerDown} onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))}
-                            onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel}
-                            style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} ref={this.createDropTarget}>
+                            onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel} ref={this.createDropTarget}>
                             <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
                                 addDocument={this.addDocument} removeDocument={this.props.removeDocument}
                                 getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
new file mode 100644
index 000000000..5c8e9c8fc
--- /dev/null
+++ b/src/client/views/globalCssVariables.scss
@@ -0,0 +1,29 @@
+@import url("https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Crimson+Text:400,400i,700");
+// colors
+$light-color: #fcfbf7;
+$light-color-secondary: rgb(241, 239, 235);
+$main-accent: #61aaa3;
+// $alt-accent: #cdd5ec;
+// $alt-accent: #cdeceb;
+$alt-accent: #59dff7;
+$lighter-alt-accent: rgb(207, 220, 240);
+$intermediate-color: #9c9396;
+$dark-color: #121721;
+// fonts
+$sans-serif: "Noto Sans", sans-serif;
+// $sans-serif: "Roboto Slab", sans-serif;
+$serif: "Crimson Text", serif;
+// misc values
+$border-radius: 0.3em;
+//
+
+                            // dragged items
+$contextMenu-zindex: 1000;  // context menu shows up over everything
+$mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
+$docDecorations-zindex: 998; // then doc decorations appear over everything else
+$remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
+$COLLECTION_BORDER_WIDTH: 1;
+:export  {
+    contextMenuZindex: $contextMenu-zindex;
+    COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH;
+}
\ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts
new file mode 100644
index 000000000..e874b815d
--- /dev/null
+++ b/src/client/views/globalCssVariables.scss.d.ts
@@ -0,0 +1,8 @@
+
+interface IGlobalScss {
+    contextMenuZindex: string;  // context menu shows up over everything
+    COLLECTION_BORDER_WIDTH: number;
+}
+declare const globalCssVariables: IGlobalScss;
+
+export = globalCssVariables;
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 5126e69f9..039d1bcc6 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,4 +1,4 @@
-@import "../global_variables";
+@import "../globalCssVariables";
 
 .documentView-node {
   position: absolute;
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index d2ba52cf9..3978c3d38 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -1,4 +1,4 @@
-@import "../global_variables";
+@import "../globalCssVariables";
 .ProseMirror {
   width: 100%;
   height: auto;
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index 63ae75424..f60edc675 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -1,4 +1,4 @@
-@import "../global_variables";
+@import "../globalCssVariables";
 .keyValueBox-cont {
     overflow-y: scroll;
     height: 100%;
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index 64e871e1c..0bb92e5a3 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -1,4 +1,4 @@
-@import "../global_variables";
+@import "../globalCssVariables";
 
 .container{
     display: flex;                 
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
index 5d5f782d2..8bc70b48f 100644
--- a/src/client/views/nodes/LinkBox.scss
+++ b/src/client/views/nodes/LinkBox.scss
@@ -1,4 +1,4 @@
-@import "../global_variables";
+@import "../globalCssVariables";
 .link-container {
     width: 100%;
     height: 35px;
diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss
index fb0c69cff..ea2e7289c 100644
--- a/src/client/views/nodes/LinkEditor.scss
+++ b/src/client/views/nodes/LinkEditor.scss
@@ -1,4 +1,4 @@
-@import "../global_variables";
+@import "../globalCssVariables";
 .edit-container {
     width: 100%;
     height: auto;
-- 
cgit v1.2.3-70-g09d2


From 0c9d3637bde9825efe93c55afce8270b2a2e979c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 12 Apr 2019 11:18:23 -0400
Subject: cleaned up and enabled linking to a top-level document

---
 src/client/util/DragManager.ts                     | 29 ++++++++++++++++++++-
 src/client/views/DocumentDecorations.tsx           | 27 ++-----------------
 src/client/views/Main.tsx                          |  4 +--
 .../views/collections/CollectionDockingView.tsx    | 30 +++-------------------
 .../views/collections/CollectionSchemaView.tsx     |  4 +--
 .../views/collections/CollectionTreeView.tsx       |  4 +--
 src/client/views/nodes/DocumentView.tsx            |  3 ---
 7 files changed, 39 insertions(+), 62 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 2562ba18a..6a7047725 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -6,8 +6,10 @@ import { DocumentDecorations } from "../views/DocumentDecorations";
 import { Main } from "../views/Main";
 import { DocumentView } from "../views/nodes/DocumentView";
 import * as globalCssVariables from "../views/globalCssVariables.scss";
+import { KeyStore } from "../../fields/KeyStore";
+import { FieldWaiting } from "../../fields/Field";
 
-export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction, copyOnDrop: boolean = false) {
+export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction, copyOnDrop: boolean = false) {
     let onRowMove = action((e: PointerEvent): void => {
         e.stopPropagation();
         e.preventDefault();
@@ -39,6 +41,31 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
     return onItemDown;
 }
 
+export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Document) {
+    let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document);
+    let draggedDocs = (srcTarg && srcTarg !== FieldWaiting) ?
+        srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc =>
+            (linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : [];
+    let draggedFromDocs = (srcTarg && srcTarg !== FieldWaiting) ?
+        srcTarg.GetList(KeyStore.LinkedFromDocs, [] as Document[]).map(linkDoc =>
+            (linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : [];
+    draggedDocs.push(...draggedFromDocs);
+    if (draggedDocs.length) {
+        let moddrag = [] as Document[];
+        for (const draggedDoc of draggedDocs) {
+            let doc = await draggedDoc.GetTAsync(KeyStore.AnnotationOn, Document);
+            if (doc) moddrag.push(doc);
+        }
+        let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
+        DragManager.StartDocumentDrag([dragEle], dragData, x, y, {
+            handlers: {
+                dragComplete: action(emptyFunction),
+            },
+            hideSource: false
+        });
+    }
+}
+
 export namespace DragManager {
     export function Root() {
         const root = document.getElementById("root");
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 69623b8f0..03b16fb0b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -7,7 +7,7 @@ import { ListField } from "../../fields/ListField";
 import { NumberField } from "../../fields/NumberField";
 import { Document } from "../../fields/Document";
 import { TextField } from "../../fields/TextField";
-import { DragManager } from "../util/DragManager";
+import { DragManager, DragLinksAsDocuments } from "../util/DragManager";
 import { SelectionManager } from "../util/SelectionManager";
 import { CollectionView } from "./collections/CollectionView";
 import './DocumentDecorations.scss';
@@ -246,34 +246,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             document.removeEventListener("pointermove", this.onLinkButtonMoved);
             document.removeEventListener("pointerup", this.onLinkButtonUp);
 
-            let sourceDoc = SelectionManager.SelectedDocuments()[0].props.Document;
-            let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document);
-            let draggedDocs = (srcTarg && srcTarg !== FieldWaiting) ?
-                srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc =>
-                    (linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : [];
-            let draggedFromDocs = (srcTarg && srcTarg !== FieldWaiting) ?
-                srcTarg.GetList(KeyStore.LinkedFromDocs, [] as Document[]).map(linkDoc =>
-                    (linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : [];
-            draggedDocs.push(...draggedFromDocs);
-            if (draggedDocs.length) {
-                let moddrag = [] as Document[];
-                for (const draggedDoc of draggedDocs) {
-                    let doc = await draggedDoc.GetTAsync(KeyStore.AnnotationOn, Document);
-                    if (doc) moddrag.push(doc);
-                }
-                let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
-                DragManager.StartDocumentDrag([this._linkButton.current], dragData, e.x, e.y, {
-                    handlers: {
-                        dragComplete: action(emptyFunction),
-                    },
-                    hideSource: false
-                });
-            }
+            DragLinksAsDocuments(this._linkButton.current, e.x, e.y, SelectionManager.SelectedDocuments()[0].props.Document);
         }
         e.stopPropagation();
     }
 
-
     onPointerMove = (e: PointerEvent): void => {
         e.stopPropagation();
         e.preventDefault();
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index c4c4a6bf9..60f7098fb 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -28,7 +28,7 @@ import '../northstar/model/ModelExtensions';
 import { HistogramOperation } from '../northstar/operations/HistogramOperation';
 import '../northstar/utils/Extensions';
 import { Server } from '../Server';
-import { setupDrag, DragManager } from '../util/DragManager';
+import { SetupDrag, DragManager } from '../util/DragManager';
 import { Transform } from '../util/Transform';
 import { UndoManager } from '../util/UndoManager';
 import { CollectionDockingView } from './collections/CollectionDockingView';
@@ -344,7 +344,7 @@ export class Main extends React.Component {
                 <ul id="add-options-list">
                     {btns.map(btn =>
                         <li key={btn[1]} ><div ref={btn[0]}>
-                            <button className="round-button add-button" title={btn[2]} onPointerDown={setupDrag(btn[0], btn[3])}>
+                            <button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}>
                                 <FontAwesomeIcon icon={btn[1]} size="sm" />
                             </button>
                         </div></li>)}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f2f41e821..e54c25474 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -16,7 +16,7 @@ import "./CollectionDockingView.scss";
 import React = require("react");
 import { SubCollectionViewProps } from "./CollectionSubView";
 import { ServerUtils } from "../../../server/ServerUtil";
-import { DragManager } from "../../util/DragManager";
+import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
 import { TextField } from "../../../fields/TextField";
 import { ListField } from "../../../fields/ListField";
 
@@ -201,32 +201,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             let y = e.clientY;
             let docid = (e.target as any).DashDocId;
             let tab = (e.target as any).parentElement as HTMLElement;
-            Server.GetField(docid, action(async (sourceDoc: Opt<Field>) => {
-                if (sourceDoc instanceof Document) {
-                    let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document);
-                    let draggedDocs = (srcTarg && srcTarg !== FieldWaiting) ?
-                        srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc =>
-                            (linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : [];
-                    let draggedFromDocs = (srcTarg && srcTarg !== FieldWaiting) ?
-                        srcTarg.GetList(KeyStore.LinkedFromDocs, [] as Document[]).map(linkDoc =>
-                            (linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : [];
-                    draggedDocs.push(...draggedFromDocs);
-                    if (draggedDocs.length) {
-                        let moddrag = [] as Document[];
-                        for (const draggedDoc of draggedDocs) {
-                            let doc = await draggedDoc.GetTAsync(KeyStore.AnnotationOn, Document);
-                            if (doc) moddrag.push(doc);
-                        }
-                        let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
-                        DragManager.StartDocumentDrag([tab], dragData, x, y, {
-                            handlers: {
-                                dragComplete: action(emptyFunction),
-                            },
-                            hideSource: false
-                        });
-                    }
-                }
-            }));
+            Server.GetField(docid, action(async (sourceDoc: Opt<Field>) =>
+                (sourceDoc instanceof Document) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
         } else
             if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) {
                 e.stopPropagation();
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 0cb88eab0..f53b2a76e 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -13,7 +13,7 @@ import { Key } from "../../../fields/Key";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
 import { Server } from "../../Server";
-import { setupDrag } from "../../util/DragManager";
+import { SetupDrag } from "../../util/DragManager";
 import { CompileScript, ToField } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
 import { anchorPoints, Flyout } from "../DocumentDecorations";
@@ -90,7 +90,7 @@ export class CollectionSchemaView extends CollectionSubView {
             <FieldView {...props} />
         );
         let reference = React.createRef<HTMLDivElement>();
-        let onItemDown = setupDrag(reference, () => props.Document, this.props.moveDocument);
+        let onItemDown = SetupDrag(reference, () => props.Document, this.props.moveDocument);
         let applyToDoc = (doc: Document, run: (args?: { [name: string]: any }) => any) => {
             const res = run({ this: doc });
             if (!res.success) return false;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 20ec16822..51a02fc25 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -7,7 +7,7 @@ import { Document } from "../../../fields/Document";
 import { FieldWaiting } from "../../../fields/Field";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
-import { setupDrag, DragManager } from "../../util/DragManager";
+import { SetupDrag, DragManager } from "../../util/DragManager";
 import { EditableView } from "../EditableView";
 import "./CollectionTreeView.scss";
 import { CollectionView } from "./CollectionView";
@@ -77,7 +77,7 @@ class TreeView extends React.Component<TreeViewProps> {
      */
     renderTitle() {
         let reference = React.createRef<HTMLDivElement>();
-        let onItemDown = setupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag);
+        let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag);
         let editableView = (titleString: string) =>
             (<EditableView
                 display={"inline"}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 649104d6c..06182d1a6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -243,9 +243,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         if (de.data instanceof DragManager.LinkDragData) {
             let sourceDoc: Document = de.data.linkSourceDocument;
             let destDoc: Document = this.props.Document;
-            if (this.props.isTopMost) {
-                return;
-            }
             let linkDoc: Document = new Document();
 
             destDoc.GetTAsync(KeyStore.Prototype, Document).then(protoDest =>
-- 
cgit v1.2.3-70-g09d2


From 95dff75290edc9b13e9741eefe2bc5851a60dde6 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 18 Apr 2019 13:51:25 -0400
Subject: fixed workspace switching exception.  started to refresh minimize
 functionality.

---
 src/client/util/DragManager.ts                     |  2 +-
 .../views/collections/CollectionDockingView.tsx    | 11 +++++----
 .../views/collections/CollectionTreeView.tsx       |  9 +++-----
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 23 +++++++++++--------
 .../collections/collectionFreeForm/MarqueeView.tsx | 10 +++++----
 src/client/views/globalCssVariables.scss           |  2 ++
 src/client/views/globalCssVariables.scss.d.ts      |  1 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 16 +++++++++----
 src/client/views/nodes/DocumentView.scss           | 13 +++++------
 src/client/views/nodes/DocumentView.tsx            | 26 +++++++++++++++++-----
 src/fields/KeyStore.ts                             |  4 +++-
 11 files changed, 76 insertions(+), 41 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 64ea4342e..426c9fc3d 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -250,7 +250,7 @@ export namespace DragManager {
                 dragData.aliasOnDrop = e.ctrlKey || e.altKey;
             }
             if (e.shiftKey) {
-                abortDrag();
+                AbortDrag();
                 CollectionDockingView.Instance.StartOtherDrag(docs, {
                     pageX: e.pageX,
                     pageY: e.pageY,
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 39e27b601..e4c647635 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -19,7 +19,7 @@ import { ServerUtils } from "../../../server/ServerUtil";
 import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
 import { TextField } from "../../../fields/TextField";
 import { ListField } from "../../../fields/ListField";
-import { thisExpression } from "babel-types";
+import { Transform } from '../../util/Transform'
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -336,9 +336,12 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
 
     ScreenToLocalTransform = () => {
-        let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!.children[0].firstChild as HTMLElement);
-        scale = Utils.GetScreenTransform(this._mainCont.current!).scale;
-        return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this.contentScaling());
+        if (this._mainCont.current && this._mainCont.current.children) {
+            let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!.children[0].firstChild as HTMLElement);
+            scale = Utils.GetScreenTransform(this._mainCont.current!).scale;
+            return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this.contentScaling());
+        }
+        return Transform.Identity();
     }
     get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 51a02fc25..e0387f4b4 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,20 +1,17 @@
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
 import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, trace } from "mobx";
+import { action, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../fields/Document";
 import { FieldWaiting } from "../../../fields/Field";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
-import { SetupDrag, DragManager } from "../../util/DragManager";
+import { DragManager, SetupDrag } from "../../util/DragManager";
 import { EditableView } from "../EditableView";
-import "./CollectionTreeView.scss";
-import { CollectionView } from "./CollectionView";
-import * as globalCssVariables from "../../views/globalCssVariables.scss";
 import { CollectionSubView } from "./CollectionSubView";
+import "./CollectionTreeView.scss";
 import React = require("react");
-import { props } from 'bluebird';
 
 
 export interface TreeViewProps {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 159db6279..af6819cd4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -71,20 +71,25 @@ export class CollectionFreeFormView extends CollectionSubView {
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) {
+            console.log("DROP Aat " + de.x + " off " + de.data.xOffset);
             const [x, y] = this.getTransform().transformPoint(de.x - de.data.xOffset, de.y - de.data.yOffset);
             if (de.data.droppedDocuments.length) {
-                let dropX = de.data.droppedDocuments[0].GetNumber(KeyStore.X, 0);
-                let dropY = de.data.droppedDocuments[0].GetNumber(KeyStore.Y, 0);
+                let dragDoc = de.data.droppedDocuments[0];
+                let dropX = dragDoc.GetNumber(KeyStore.X, 0);
+                let dropY = dragDoc.GetNumber(KeyStore.Y, 0);
                 de.data.droppedDocuments.map(d => {
+                    let minimized = d.GetBoolean(KeyStore.Minimized, false);
                     d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0) - dropX));
                     d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0) - dropY));
-                    if (!d.GetNumber(KeyStore.Width, 0)) {
-                        d.SetNumber(KeyStore.Width, 300);
-                    }
-                    if (!d.GetNumber(KeyStore.Height, 0)) {
-                        let nw = d.GetNumber(KeyStore.NativeWidth, 0);
-                        let nh = d.GetNumber(KeyStore.NativeHeight, 0);
-                        d.SetNumber(KeyStore.Height, nw && nh ? nh / nw * d.Width() : 300);
+                    if (!minimized) {
+                        if (!d.GetNumber(KeyStore.Width, 0)) {
+                            d.SetNumber(KeyStore.Width, 300);
+                        }
+                        if (!d.GetNumber(KeyStore.Height, 0)) {
+                            let nw = d.GetNumber(KeyStore.NativeWidth, 0);
+                            let nh = d.GetNumber(KeyStore.NativeHeight, 0);
+                            d.SetNumber(KeyStore.Height, nw && nh ? nh / nw * d.Width() : 300);
+                        }
                     }
                     this.bringToFront(d);
                 });
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 65f461b27..8b94374fa 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -11,6 +11,7 @@ import { undoBatch } from "../../../util/UndoManager";
 import { InkingCanvas } from "../../InkingCanvas";
 import { PreviewCursor } from "../../PreviewCursor";
 import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import { MINIMIZED_ICON_SIZE } from '../../../views/globalCssVariables.scss'
 import "./MarqueeView.scss";
 import React = require("react");
 
@@ -207,11 +208,12 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         let selRect = this.Bounds;
         let selection: Document[] = [];
         this.props.activeDocuments().map(doc => {
+            let minimized = doc.GetBoolean(KeyStore.Minimized, false);
             var z = doc.GetNumber(KeyStore.Zoom, 1);
-            var x = doc.GetNumber(KeyStore.X, 0);
-            var y = doc.GetNumber(KeyStore.Y, 0);
-            var w = doc.Width() / z;
-            var h = doc.Height() / z;
+            var x = doc.GetNumber(KeyStore.X, 0) + (minimized ? doc.GetNumber(KeyStore.MinimizedX, 0) : 0);
+            var y = doc.GetNumber(KeyStore.Y, 0) + (minimized ? doc.GetNumber(KeyStore.MinimizedY, 0) : 0);
+            var w = minimized ? MINIMIZED_ICON_SIZE : doc.Width() / z;
+            var h = minimized ? MINIMIZED_ICON_SIZE : doc.Height() / z;
             if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
                 selection.push(doc);
             }
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 5c8e9c8fc..f154f8158 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -23,7 +23,9 @@ $mainTextInput-zindex: 999; // then text input overlay so that it's context menu
 $docDecorations-zindex: 998; // then doc decorations appear over everything else
 $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
 $COLLECTION_BORDER_WIDTH: 1;
+$MINIMIZED_ICON_SIZE:25;
 :export  {
     contextMenuZindex: $contextMenu-zindex;
     COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH;
+    MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
 }
\ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts
index ed8573f41..cc77d987a 100644
--- a/src/client/views/globalCssVariables.scss.d.ts
+++ b/src/client/views/globalCssVariables.scss.d.ts
@@ -2,6 +2,7 @@
 interface IGlobalScss {
     contextMenuZindex: string;  // context menu shows up over everything
     COLLECTION_BORDER_WIDTH: string;
+    MINIMIZED_ICON_SIZE: string;
 }
 declare const globalCssVariables: IGlobalScss;
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 97d53a47e..a3689414d 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -17,7 +17,7 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
 
     @computed
     get transform(): string {
-        return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px) scale(${this.zoom}, ${this.zoom}) `;
+        return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}, ${this.zoom}) `;
     }
 
     @computed get zoom(): number { return 1 / this.props.Document.GetNumber(KeyStore.Zoom, 1); }
@@ -45,14 +45,20 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
         this.props.Document.SetData(KeyStore.ZIndex, h, NumberField);
     }
 
+    get X() {
+        return this.props.Document.GetNumber(KeyStore.X, 0) + (this.isMinimized ? this.props.Document.GetNumber(KeyStore.MinimizedX, 0) : 0);
+    }
+    get Y() {
+        return this.props.Document.GetNumber(KeyStore.Y, 0) + (this.isMinimized ? this.props.Document.GetNumber(KeyStore.MinimizedY, 0) : 0);
+    }
     getTransform = (): Transform =>
         this.props.ScreenToLocalTransform()
-            .translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0))
+            .translate(-this.X, -this.Y)
             .scale(1 / this.contentScaling()).scale(1 / this.zoom)
 
     contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
-    panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth();
-    panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight();
+    panelWidth = () => this.isMinimized ? 10 : this.props.PanelWidth();
+    panelHeight = () => this.isMinimized ? 10 : this.props.PanelHeight();
 
     @computed
     get docView() {
@@ -64,6 +70,8 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
         />;
     }
 
+    get isMinimized() { return this.props.Document.GetBoolean(KeyStore.Minimized, false); }
+
     render() {
         let zoomFade = 1;
         //var zoom = doc.GetNumber(KeyStore.Zoom, 1);
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 690ee50e8..85c305b5a 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -13,7 +13,6 @@
   }
 
   .top {
-    background: #232323;
     height: 20px;
     cursor: pointer;
   }
@@ -32,13 +31,13 @@
 .documentView-node-topmost {
     background: white;
 }
-
 .minimized-box {
-  height: 10px;
-  width: 10px;
-  border-radius: 2px;
-  background: $dark-color;
-  transform-origin: left top;
+    position: absolute;
+    left:0;
+    top:0;
+    width:$MINIMIZED_ICON_SIZE;
+    height:$MINIMIZED_ICON_SIZE;
+    transform-origin: left top;
 }
 
 .minimized-box:hover {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d74f9fc57..af52e44e1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,12 +1,13 @@
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretUp, faObjectGroup, faStickyNote, faFilePdf, faFilm, faImage } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { BooleanField } from "../../../fields/BooleanField";
 import { Document } from "../../../fields/Document";
-import { Field, FieldWaiting, Opt } from "../../../fields/Field";
+import { Field, Opt } from "../../../fields/Field";
 import { Key } from "../../../fields/Key";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
-import { TextField } from "../../../fields/TextField";
 import { ServerUtils } from "../../../server/ServerUtil";
 import { emptyFunction, Utils } from "../../../Utils";
 import { Documents } from "../../documents/Documents";
@@ -19,12 +20,19 @@ import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
+import { MINIMIZED_ICON_SIZE } from "../../views/globalCssVariables.scss";
 import { ContextMenu } from "../ContextMenu";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
 
 
+library.add(faCaretUp);
+library.add(faObjectGroup);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     Document: Document;
@@ -216,6 +224,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     @action
     public minimize = (): void => {
         this.props.Document.SetBoolean(KeyStore.Minimized, true);
+        this.props.Document.SetNumber(KeyStore.MinimizedX, 0);
+        this.props.Document.SetNumber(KeyStore.MinimizedY, 0);
         SelectionManager.DeselectAll();
     }
 
@@ -314,8 +324,14 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%";
 
         if (this.isMinimized()) {
-            return <div className="minimized-box" ref={this._mainCont} onClick={this.expand} onDrop={this.onDrop}
-                style={{ transform: `scale(${scaling} , ${scaling})` }} onPointerDown={this.onPointerDown} />;
+            let button = this.layout.indexOf("PDFBox") !== -1 ? faFilePdf :
+                this.layout.indexOf("ImageBox") !== -1 ? faImage :
+                    this.layout.indexOf("Formatted") !== -1 ? faStickyNote :
+                        this.layout.indexOf("Collection") !== -1 ? faObjectGroup :
+                            faCaretUp;
+            return <div className="minimized-box" ref={this._mainCont} onClick={this.expand} onDrop={this.onDrop} onPointerDown={this.onPointerDown} >
+                <FontAwesomeIcon icon={button} style={{ width: MINIMIZED_ICON_SIZE, height: MINIMIZED_ICON_SIZE }} className="documentView-minimizedIcon" />
+            </div>
         }
         return (
             <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 16a909eb8..ff2f31003 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -4,6 +4,8 @@ export namespace KeyStore {
     export const Prototype = new Key("Prototype");
     export const X = new Key("X");
     export const Y = new Key("Y");
+    export const MinimizedX = new Key("MinimizedX");
+    export const MinimizedY = new Key("MinimizedY");
     export const Page = new Key("Page");
     export const Title = new Key("Title");
     export const Author = new Key("Author");
@@ -48,7 +50,7 @@ export namespace KeyStore {
     export const Minimized = new Key("Minimized");
     export const CopyDraggedItems = new Key("CopyDraggedItems");
 
-    export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight,
+    export const KeyList: Key[] = [Prototype, X, Y, MinimizedX, MinimizedY, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight,
         Width, Height, ZIndex, Zoom, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys,
         LayoutFields, ColumnsKey, SchemaSplitPercentage, Caption, ActiveWorkspace, DocumentText, BrushingDocs, LinkedToDocs, LinkedFromDocs,
         LinkDescription, LinkTags, Thumbnail, ThumbnailPage, CurPage, AnnotationOn, NumPages, Ink, Cursors, OptionalRightCollection,
-- 
cgit v1.2.3-70-g09d2


From 575d8b339cdf74cdcb8d46dcd6e627aec2dda636 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 24 Apr 2019 23:17:44 -0400
Subject: added borderRadius and fixed a bunch of broken props along the way.

---
 src/client/documents/Documents.ts                          |  2 ++
 src/client/views/Main.scss                                 |  3 ++-
 src/client/views/Main.tsx                                  |  3 ++-
 src/client/views/MainOverlayTextBox.tsx                    |  4 ++--
 src/client/views/collections/CollectionBaseView.tsx        |  7 ++++---
 src/client/views/collections/CollectionDockingView.tsx     |  3 ++-
 src/client/views/collections/CollectionSchemaView.tsx      |  6 +++++-
 src/client/views/collections/CollectionSubView.tsx         |  1 +
 src/client/views/collections/CollectionTreeView.tsx        |  5 ++++-
 .../collectionFreeForm/CollectionFreeFormView.scss         |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx          | 14 +++++++++++---
 .../views/collections/collectionFreeForm/MarqueeView.tsx   |  7 ++++---
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx  | 11 +++++++++++
 src/client/views/nodes/DocumentView.tsx                    |  2 ++
 src/client/views/nodes/FieldView.tsx                       |  7 ++++++-
 src/client/views/nodes/FormattedTextBox.scss               |  3 +--
 src/client/views/nodes/FormattedTextBox.tsx                |  5 ++++-
 src/client/views/nodes/ImageBox.scss                       |  3 ---
 src/client/views/nodes/ImageBox.tsx                        |  2 +-
 src/fields/KeyStore.ts                                     |  3 ++-
 20 files changed, 67 insertions(+), 25 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0e6661819..879b114b6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -55,6 +55,7 @@ export interface DocumentOptions {
     backgroundColor?: string;
     copyDraggedItems?: boolean;
     documentText?: string;
+    borderRounding?: number;
 }
 
 export namespace Documents {
@@ -108,6 +109,7 @@ export namespace Documents {
         if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
         if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
         if (options.copyDraggedItems !== undefined) { doc.SetBoolean(KeyStore.CopyDraggedItems, options.copyDraggedItems); }
+        if (options.borderRounding !== undefined) { doc.SetNumber(KeyStore.BorderRounding, options.borderRounding); }
         return doc;
     }
 
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 4373534b2..3edc79d39 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -42,7 +42,8 @@ h1 {
 }
 
 .jsx-parser {
-    width:100%
+    width:100%;
+    pointer-events: none;
 }
 
 p {
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 934616aca..057f68ed9 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -17,7 +17,7 @@ import { CurrentUserUtils } from '../../server/authentication/models/current_use
 import { MessageStore } from '../../server/Message';
 import { RouteStore } from '../../server/RouteStore';
 import { ServerUtils } from '../../server/ServerUtil';
-import { emptyDocFunction, emptyFunction, returnTrue, Utils, returnOne } from '../../Utils';
+import { emptyDocFunction, emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils';
 import { Documents } from '../documents/Documents';
 import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
 import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
@@ -197,6 +197,7 @@ export class Main extends React.Component {
                             isTopMost={true}
                             selectOnLoad={false}
                             focus={emptyDocFunction}
+                            borderRounding={returnZero}
                             parentActive={returnTrue}
                             whenActiveChanged={emptyFunction}
                             ContainingCollectionView={undefined} />}
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 9aaaad084..f98f6e8a5 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
 import { Document } from '../../fields/Document';
 import { Key } from '../../fields/Key';
 import { KeyStore } from '../../fields/KeyStore';
-import { emptyDocFunction, emptyFunction, returnTrue } from '../../Utils';
+import { emptyDocFunction, emptyFunction, returnTrue, returnZero } from '../../Utils';
 import '../northstar/model/ModelExtensions';
 import '../northstar/utils/Extensions';
 import { DragManager } from '../util/DragManager';
@@ -97,7 +97,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                     style={{ width: `${this.TextDoc.Width()}px`, height: `${this.TextDoc.Height()}px` }}>
                     <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={this.TextDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
                         selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
-                        ScreenToLocalTransform={this._textXf} focus={emptyDocFunction} />
+                        ScreenToLocalTransform={this._textXf} borderRounding={returnZero} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyDocFunction} />
                 </div>
             </ div>;
         }
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 097236c1d..e65770137 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -2,13 +2,12 @@ import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Document } from '../../../fields/Document';
-import { Field, FieldValue, FieldWaiting } from '../../../fields/Field';
+import { FieldValue, FieldWaiting } from '../../../fields/Field';
 import { KeyStore } from '../../../fields/KeyStore';
 import { ListField } from '../../../fields/ListField';
 import { NumberField } from '../../../fields/NumberField';
 import { ContextMenu } from '../ContextMenu';
 import { FieldViewProps } from '../nodes/FieldView';
-import { TextField } from '../../../fields/TextField';
 
 export enum CollectionViewType {
     Invalid,
@@ -190,7 +189,9 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         };
         const viewtype = this.collectionViewType;
         return (
-            <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
+            <div className={this.props.className || "collectionView-cont"}
+                style={{ borderRadius: `${this.props.borderRounding()}px`, pointerEvents: "all" }}
+                onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
                 {viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
             </div>
         );
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 84159635a..eb94a4e6d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -8,7 +8,7 @@ import { Document } from "../../../fields/Document";
 import { KeyStore } from "../../../fields/KeyStore";
 import Measure from "react-measure";
 import { FieldId, Opt, Field, FieldWaiting } from "../../../fields/Field";
-import { Utils, returnTrue, emptyFunction, emptyDocFunction, returnOne } from "../../../Utils";
+import { Utils, returnTrue, emptyFunction, emptyDocFunction, returnOne, returnZero } from "../../../Utils";
 import { Server } from "../../Server";
 import { undoBatch } from "../../util/UndoManager";
 import { DocumentView } from "../nodes/DocumentView";
@@ -368,6 +368,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                     parentActive={returnTrue}
                     whenActiveChanged={emptyFunction}
                     focus={emptyDocFunction}
+                    borderRounding={returnZero}
                     ContainingCollectionView={undefined} />
             </div>);
     }
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index cb7a67991..dc2910ede 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -12,7 +12,7 @@ import { Field, Opt } from "../../../fields/Field";
 import { Key } from "../../../fields/Key";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
-import { emptyDocFunction, emptyFunction, returnFalse } from "../../../Utils";
+import { emptyDocFunction, emptyFunction, returnFalse, returnZero } from "../../../Utils";
 import { Server } from "../../Server";
 import { SetupDrag } from "../../util/DragManager";
 import { CompileScript, ToField } from "../../util/Scripting";
@@ -77,6 +77,9 @@ export class CollectionSchemaView extends CollectionSubView {
             focus: emptyDocFunction,
             active: returnFalse,
             whenActiveChanged: emptyFunction,
+            borderRounding: this.props.borderRounding,
+            PanelHeight: returnZero,
+            PanelWidth: returnZero,
         };
         let contents = (
             <FieldView {...props} />
@@ -275,6 +278,7 @@ export class CollectionSchemaView extends CollectionSubView {
                         focus={emptyDocFunction}
                         parentActive={this.props.active}
                         whenActiveChanged={this.props.whenActiveChanged}
+                        borderRounding={this.props.borderRounding}
                     />
                 </div>
                 <input className="collectionSchemaView-input" value={this.previewScript} onChange={this.onPreviewScriptChange}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index f9fc7be5a..bf59bec95 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -25,6 +25,7 @@ export interface CollectionViewProps extends FieldViewProps {
     moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
     PanelWidth: () => number;
     PanelHeight: () => number;
+    borderRounding: () => number;
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e0387f4b4..b9d1f0bd9 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -136,7 +136,10 @@ export class CollectionTreeView extends CollectionSubView {
             );
 
         return (
-            <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
+            <div id="body" className="collectionTreeView-dropTarget"
+                style={{ borderRadius: `${this.props.borderRounding()}px` }}
+                onWheel={(e: React.WheelEvent) => e.stopPropagation()}
+                onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
                 <div className="coll-title">
                     <EditableView
                         contents={this.props.Document.Title}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 57706b51e..67a0e532c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -6,6 +6,7 @@
   width: 100%;
   height: 100%;
   transform-origin: left top;
+  pointer-events: none;
 }
 .collectionfreeformview-container {
   .collectionfreeformview > .jsx-parser {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7492d8744..9e2c9bf9a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -257,6 +257,7 @@ export class CollectionFreeFormView extends CollectionSubView {
             focus: this.focusDocument,
             parentActive: this.props.active,
             whenActiveChanged: this.props.active,
+            borderRounding: this.props.borderRounding,
         };
     }
 
@@ -288,11 +289,17 @@ export class CollectionFreeFormView extends CollectionSubView {
         const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
         return (
             <div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel}
+                style={{ borderRadius: `${this.props.borderRounding()}px` }}
                 onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} >
-                <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
+                {/* <svg viewBox="0 0 180 18" style={{ top: "50%", opacity: 0.05, position: "absolute" }}>
+                    <text y="15" >
+                        {this.props.Document.Title}
+                    </text>
+                </svg> */}
+                <MarqueeView container={this} borderRadius={this.props.borderRounding} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
                     addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
                     getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
-                    <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
+                    <CollectionFreeFormViewPannableContents borderRadius={this.props.borderRounding} centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
                         zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
                         <CollectionFreeFormLinksView {...this.props} key="freeformLinks">
                             <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
@@ -340,6 +347,7 @@ interface CollectionFreeFormViewPannableContentsProps {
     panX: () => number;
     panY: () => number;
     zoomScaling: () => number;
+    borderRadius: () => number;
 }
 
 @observer
@@ -350,7 +358,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         const panx = -this.props.panX();
         const pany = -this.props.panY();
         const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
-        return <div className="collectionfreeformview" style={{ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
+        return <div className="collectionfreeformview" style={{ borderRadius: `${this.props.borderRadius()}px`, transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
             {this.props.children}
         </div>;
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 898fff019..e724ab47e 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -14,7 +14,6 @@ import { CollectionFreeFormView } from "./CollectionFreeFormView";
 import "./MarqueeView.scss";
 import React = require("react");
 import { Utils } from "../../../../Utils";
-import { ListField } from "../../../../fields/ListField";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -25,6 +24,7 @@ interface MarqueeViewProps {
     selectDocuments: (docs: Document[]) => void;
     removeDocument: (doc: Document) => boolean;
     addLiveTextDocument: (doc: Document) => void;
+    borderRadius: () => number;
 }
 
 @observer
@@ -138,7 +138,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             this.cleanupInteractions(true);
             e.stopPropagation();
         }
-        if (e.key === "c" || e.key === "r") {
+        if (e.key === "c" || e.key === "r" || e.key === "e") {
             e.stopPropagation();
             let bounds = this.Bounds;
             let selected = this.marqueeSelect().map(d => {
@@ -156,6 +156,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 y: bounds.top,
                 panx: 0,
                 pany: 0,
+                borderRounding: e.key === "e" ? -1 : undefined,
                 backgroundColor: selected.length ? "white" : "",
                 scale: zoomBasis,
                 width: bounds.width * zoomBasis,
@@ -250,7 +251,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     }
 
     render() {
-        return <div className="marqueeView" onClick={this.onClick} onPointerDown={this.onPointerDown}>
+        return <div className="marqueeView" style={{ borderRadius: `${this.props.borderRadius()}px` }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
             {this.props.children}
             {!this._visible ? (null) : this.marqueeDiv}
         </div>;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 420324d40..8caf5ff31 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -65,6 +65,7 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
             ScreenToLocalTransform={this.getTransform}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
+            borderRounding={this.borderRounding}
         />;
     }
 
@@ -148,6 +149,15 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
         }
     }
 
+    borderRounding = () => {
+        let br = this.props.Document.GetNumber(KeyStore.BorderRounding, 0);
+        return br >= 0 ? br :
+            this.props.Document.GetNumber(KeyStore.NativeWidth, 0) === 0 ?
+                Math.min(this.props.PanelWidth(), this.props.PanelHeight())
+                :
+                Math.min(this.props.Document.GetNumber(KeyStore.NativeWidth, 0), this.props.Document.GetNumber(KeyStore.NativeHeight, 0));
+    }
+
     render() {
         let maximizedDoc = this.props.Document.GetT(KeyStore.MaximizedDoc, Document);
         let zoomFade = 1;
@@ -168,6 +178,7 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
                 onClick={this.onClick}
                 style={{
                     opacity: zoomFade,
+                    borderRadius: `${this.borderRounding()}px`,
                     transformOrigin: "left top",
                     transform: this.transform,
                     pointerEvents: (zoomFade < 0.09 ? "none" : "all"),
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c19b0c342..ddf99c109 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -41,6 +41,7 @@ export interface DocumentViewProps {
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     toggleMinimized: () => void;
+    borderRounding: () => number;
 }
 export interface JsxArgs extends DocumentViewProps {
     Keys: { [name: string]: Key };
@@ -277,6 +278,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
                 style={{
+                    borderRadius: `${this.props.borderRounding()}px`,
                     background: this.props.Document.GetText(KeyStore.BackgroundColor, ""),
                     width: nativeWidth, height: nativeHeight,
                     transform: `scale(${scaling}, ${scaling})`
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 93e385821..8484194b0 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -19,7 +19,7 @@ import { ListField } from "../../../fields/ListField";
 import { DocumentContentsView } from "./DocumentContentsView";
 import { Transform } from "../../util/Transform";
 import { KeyStore } from "../../../fields/KeyStore";
-import { returnFalse, emptyDocFunction, emptyFunction, returnOne } from "../../../Utils";
+import { returnFalse, emptyDocFunction, emptyFunction, returnOne, returnZero } from "../../../Utils";
 import { CollectionView } from "../collections/CollectionView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
@@ -47,6 +47,9 @@ export interface FieldViewProps {
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     focus: (doc: Document) => void;
+    PanelWidth: () => number;
+    PanelHeight: () => number;
+    borderRounding: () => number;
 }
 
 @observer
@@ -100,6 +103,8 @@ export class FieldView extends React.Component<FieldViewProps> {
                     layoutKey={KeyStore.Layout}
                     ContainingCollectionView={this.props.ContainingCollectionView}
                     parentActive={this.props.active}
+                    borderRounding={returnZero}
+                    toggleMinimized={emptyFunction}
                     whenActiveChanged={this.props.whenActiveChanged} />
             );
         }
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 5eb2bf7ce..59857bab3 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -22,11 +22,10 @@
   overflow-x: hidden;
   color: initial;
   height: 100%;
-  pointer-events: all;
+  pointer-events: none;
 }
 .formattedTextBox-cont-hidden {
     overflow: hidden;
-    pointer-events: none;
 }
 
 .menuicon {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 9cd58c73a..4b2fdbaff 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -273,7 +273,10 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
         let color = this.props.Document.GetText(KeyStore.BackgroundColor, "");
         return (
             <div className={`formattedTextBox-cont-${style}`}
-                style={{ background: color }}
+                style={{
+                    borderRadius: `${this.props.borderRounding()}px`,
+                    background: color,
+                }}
                 onKeyDown={this.onKeyPress}
                 onKeyPress={this.onKeyPress}
                 onFocus={this.onFocused}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index db9a08fba..96a724867 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -8,9 +8,6 @@
   max-height: 100%;
   pointer-events: none;
 }
-.imageBox-cont-interactive {
-    pointer-events: all;
-}
 
 .imageBox-dot {
     position:absolute;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index a16fd5007..b22accb74 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -165,7 +165,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
         let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
         let interactive = InkingControl.Instance.selectedTool ? "" : "interactive"
         return (
-            <div className={`imageBox-cont-${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+            <div className="imageBox-cont" onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
                 <img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
                 {paths.length > 1 ? this.dots(paths) : (null)}
                 {this.lightbox(paths)}
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 0a96beb50..aa18a9f33 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -49,12 +49,13 @@ export namespace KeyStore {
     export const MinimizedDoc = new Key("MinimizedDoc");
     export const MaximizedDoc = new Key("MaximizedDoc");
     export const CopyDraggedItems = new Key("CopyDraggedItems");
+    export const BorderRounding = new Key("BorderRounding");
 
     export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight,
         Width, Height, ZIndex, ZoomBasis, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys,
         LayoutFields, ColumnsKey, SchemaSplitPercentage, Caption, ActiveWorkspace, DocumentText, BrushingDocs, LinkedToDocs, LinkedFromDocs,
         LinkDescription, LinkTags, Thumbnail, ThumbnailPage, CurPage, AnnotationOn, NumPages, Ink, Cursors, OptionalRightCollection,
-        Archives, Workspaces, IsMinimized, MinimizedDoc, MaximizedDoc, CopyDraggedItems
+        Archives, Workspaces, IsMinimized, MinimizedDoc, MaximizedDoc, CopyDraggedItems, BorderRounding
     ];
     export function KeyLookup(keyid: string) {
         for (const key of KeyList) {
-- 
cgit v1.2.3-70-g09d2


From 6a62e0a16abc3bbadfb179750463e3d361f1c759 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 25 Apr 2019 09:15:22 -0400
Subject: silly me - borderRadius can be inherited

---
 src/client/views/Main.scss                                     |  1 +
 src/client/views/Main.tsx                                      |  1 -
 src/client/views/MainOverlayTextBox.tsx                        |  2 +-
 src/client/views/collections/CollectionBaseView.tsx            |  2 +-
 src/client/views/collections/CollectionDockingView.tsx         |  1 -
 src/client/views/collections/CollectionSchemaView.tsx          |  2 --
 src/client/views/collections/CollectionSubView.tsx             |  1 -
 src/client/views/collections/CollectionTreeView.tsx            |  2 +-
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx  | 10 ++++------
 .../views/collections/collectionFreeForm/MarqueeView.tsx       |  3 +--
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx      |  1 -
 src/client/views/nodes/DocumentView.tsx                        |  3 +--
 src/client/views/nodes/FieldView.tsx                           |  2 --
 src/client/views/nodes/FormattedTextBox.tsx                    |  2 +-
 14 files changed, 11 insertions(+), 22 deletions(-)

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

diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 3edc79d39..2f899ff28 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -44,6 +44,7 @@ h1 {
 .jsx-parser {
     width:100%;
     pointer-events: none;
+    border-radius: inherit;
 }
 
 p {
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 057f68ed9..c6b3f06d8 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -197,7 +197,6 @@ export class Main extends React.Component {
                             isTopMost={true}
                             selectOnLoad={false}
                             focus={emptyDocFunction}
-                            borderRounding={returnZero}
                             parentActive={returnTrue}
                             whenActiveChanged={emptyFunction}
                             ContainingCollectionView={undefined} />}
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index f98f6e8a5..be8d67925 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -97,7 +97,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                     style={{ width: `${this.TextDoc.Width()}px`, height: `${this.TextDoc.Height()}px` }}>
                     <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={this.TextDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
                         selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
-                        ScreenToLocalTransform={this._textXf} borderRounding={returnZero} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyDocFunction} />
+                        ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyDocFunction} />
                 </div>
             </ div>;
         }
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index e65770137..87b767c93 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -190,7 +190,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         const viewtype = this.collectionViewType;
         return (
             <div className={this.props.className || "collectionView-cont"}
-                style={{ borderRadius: `${this.props.borderRounding()}px`, pointerEvents: "all" }}
+                style={{ borderRadius: "inherit", pointerEvents: "all" }}
                 onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
                 {viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
             </div>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index eb94a4e6d..3267f2455 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -368,7 +368,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                     parentActive={returnTrue}
                     whenActiveChanged={emptyFunction}
                     focus={emptyDocFunction}
-                    borderRounding={returnZero}
                     ContainingCollectionView={undefined} />
             </div>);
     }
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index dc2910ede..b6d5f1bfa 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -77,7 +77,6 @@ export class CollectionSchemaView extends CollectionSubView {
             focus: emptyDocFunction,
             active: returnFalse,
             whenActiveChanged: emptyFunction,
-            borderRounding: this.props.borderRounding,
             PanelHeight: returnZero,
             PanelWidth: returnZero,
         };
@@ -278,7 +277,6 @@ export class CollectionSchemaView extends CollectionSubView {
                         focus={emptyDocFunction}
                         parentActive={this.props.active}
                         whenActiveChanged={this.props.whenActiveChanged}
-                        borderRounding={this.props.borderRounding}
                     />
                 </div>
                 <input className="collectionSchemaView-input" value={this.previewScript} onChange={this.onPreviewScriptChange}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index bf59bec95..f9fc7be5a 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -25,7 +25,6 @@ export interface CollectionViewProps extends FieldViewProps {
     moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    borderRounding: () => number;
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b9d1f0bd9..905b48db7 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -137,7 +137,7 @@ export class CollectionTreeView extends CollectionSubView {
 
         return (
             <div id="body" className="collectionTreeView-dropTarget"
-                style={{ borderRadius: `${this.props.borderRounding()}px` }}
+                style={{ borderRadius: "inherit" }}
                 onWheel={(e: React.WheelEvent) => e.stopPropagation()}
                 onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
                 <div className="coll-title">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9e2c9bf9a..56342c84c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -257,7 +257,6 @@ export class CollectionFreeFormView extends CollectionSubView {
             focus: this.focusDocument,
             parentActive: this.props.active,
             whenActiveChanged: this.props.active,
-            borderRounding: this.props.borderRounding,
         };
     }
 
@@ -289,17 +288,17 @@ export class CollectionFreeFormView extends CollectionSubView {
         const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
         return (
             <div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel}
-                style={{ borderRadius: `${this.props.borderRounding()}px` }}
+                style={{ borderRadius: "inherit" }}
                 onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} >
                 {/* <svg viewBox="0 0 180 18" style={{ top: "50%", opacity: 0.05, position: "absolute" }}>
                     <text y="15" >
                         {this.props.Document.Title}
                     </text>
                 </svg> */}
-                <MarqueeView container={this} borderRadius={this.props.borderRounding} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
+                <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
                     addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
                     getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
-                    <CollectionFreeFormViewPannableContents borderRadius={this.props.borderRounding} centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
+                    <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
                         zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
                         <CollectionFreeFormLinksView {...this.props} key="freeformLinks">
                             <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
@@ -347,7 +346,6 @@ interface CollectionFreeFormViewPannableContentsProps {
     panX: () => number;
     panY: () => number;
     zoomScaling: () => number;
-    borderRadius: () => number;
 }
 
 @observer
@@ -358,7 +356,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         const panx = -this.props.panX();
         const pany = -this.props.panY();
         const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
-        return <div className="collectionfreeformview" style={{ borderRadius: `${this.props.borderRadius()}px`, transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
+        return <div className="collectionfreeformview" style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
             {this.props.children}
         </div>;
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e724ab47e..b511a6e3d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -24,7 +24,6 @@ interface MarqueeViewProps {
     selectDocuments: (docs: Document[]) => void;
     removeDocument: (doc: Document) => boolean;
     addLiveTextDocument: (doc: Document) => void;
-    borderRadius: () => number;
 }
 
 @observer
@@ -251,7 +250,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     }
 
     render() {
-        return <div className="marqueeView" style={{ borderRadius: `${this.props.borderRadius()}px` }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
+        return <div className="marqueeView" style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
             {this.props.children}
             {!this._visible ? (null) : this.marqueeDiv}
         </div>;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 8caf5ff31..4537ce3eb 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -65,7 +65,6 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
             ScreenToLocalTransform={this.getTransform}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
-            borderRounding={this.borderRounding}
         />;
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ddf99c109..cec6e6468 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -41,7 +41,6 @@ export interface DocumentViewProps {
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     toggleMinimized: () => void;
-    borderRounding: () => number;
 }
 export interface JsxArgs extends DocumentViewProps {
     Keys: { [name: string]: Key };
@@ -278,7 +277,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
                 style={{
-                    borderRadius: `${this.props.borderRounding()}px`,
+                    borderRadius: "inherit",
                     background: this.props.Document.GetText(KeyStore.BackgroundColor, ""),
                     width: nativeWidth, height: nativeHeight,
                     transform: `scale(${scaling}, ${scaling})`
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 8484194b0..8f360754e 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -49,7 +49,6 @@ export interface FieldViewProps {
     focus: (doc: Document) => void;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    borderRounding: () => number;
 }
 
 @observer
@@ -103,7 +102,6 @@ export class FieldView extends React.Component<FieldViewProps> {
                     layoutKey={KeyStore.Layout}
                     ContainingCollectionView={this.props.ContainingCollectionView}
                     parentActive={this.props.active}
-                    borderRounding={returnZero}
                     toggleMinimized={emptyFunction}
                     whenActiveChanged={this.props.whenActiveChanged} />
             );
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 4b2fdbaff..5b17763e7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -274,7 +274,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
         return (
             <div className={`formattedTextBox-cont-${style}`}
                 style={{
-                    borderRadius: `${this.props.borderRounding()}px`,
+                    borderRadius: "inherit",
                     background: color,
                 }}
                 onKeyDown={this.onKeyPress}
-- 
cgit v1.2.3-70-g09d2


From 7cda7f95e724bb621c57b5c53b083e6d6245afa5 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 26 Apr 2019 21:14:50 -0400
Subject: More

---
 src/client/views/InkingCanvas.tsx                  |  29 +-
 src/client/views/InkingControl.tsx                 |   7 +-
 .../views/collections/CollectionTreeView.tsx       |  51 ++-
 src/client/views/nodes/LinkEditor.tsx              |  16 +-
 src/debug/Viewer.tsx                               | 368 ++++++++++-----------
 src/fields/ScriptField.ts                          | 174 +++++-----
 src/mobile/ImageUpload.tsx                         |  34 +-
 src/new_fields/Doc.ts                              |   4 +-
 .../authentication/controllers/WorkspacesMenu.tsx  |  16 +-
 9 files changed, 344 insertions(+), 355 deletions(-)

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

diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 47ee8eb85..1e26893c5 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -1,9 +1,5 @@
 import { action, computed, trace, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Document } from "../../fields/Document";
-import { FieldWaiting } from "../../fields/Field";
-import { InkField, InkTool, StrokeData, StrokeMap } from "../../fields/InkField";
-import { KeyStore } from "../../fields/KeyStore";
 import { Utils } from "../../Utils";
 import { Transform } from "../util/Transform";
 import "./InkingCanvas.scss";
@@ -11,10 +7,13 @@ import { InkingControl } from "./InkingControl";
 import { InkingStroke } from "./InkingStroke";
 import React = require("react");
 import { undoBatch, UndoManager } from "../util/UndoManager";
+import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
+import { Doc } from "../../new_fields/Doc";
+import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
 
 interface InkCanvasProps {
     getScreenTransform: () => Transform;
-    Document: Document;
+    Document: Doc;
     children: () => JSX.Element[];
 }
 
@@ -23,7 +22,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome
     @observable inkMidX: number = 0;
     @observable inkMidY: number = 0;
-    private previousState?: StrokeMap;
+    private previousState?: Map<string, StrokeData>;
     private _currentStrokeId: string = "";
     public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean {
         return stroke.pathData.reduce((inside: boolean, val) => inside ||
@@ -33,9 +32,9 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     }
 
     componentDidMount() {
-        this.props.Document.GetTAsync(KeyStore.Ink, InkField, ink => runInAction(() => {
+        PromiseValue(Cast(this.props.Document.ink, InkField)).then(ink => runInAction(() => {
             if (ink) {
-                let bounds = Array.from(ink.Data).reduce(([mix, max, miy, may], [id, strokeData]) =>
+                let bounds = Array.from(ink.inkData).reduce(([mix, max, miy, may], [id, strokeData]) =>
                     strokeData.pathData.reduce(([mix, max, miy, may], p) =>
                         [Math.min(mix, p.x), Math.max(max, p.x), Math.min(miy, p.y), Math.max(may, p.y)],
                         [mix, max, miy, may]),
@@ -47,13 +46,13 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     }
 
     @computed
-    get inkData(): StrokeMap {
-        let map = this.props.Document.GetT(KeyStore.Ink, InkField);
-        return !map || map === FieldWaiting ? new Map : new Map(map.Data);
+    get inkData(): Map<string, StrokeData> {
+        let map = Cast(this.props.Document.ink, InkField);
+        return !map ? new Map : new Map(map.inkData);
     }
 
-    set inkData(value: StrokeMap) {
-        this.props.Document.SetDataOnPrototype(KeyStore.Ink, value, InkField);
+    set inkData(value: Map<string, StrokeData>) {
+        Doc.SetOnPrototype(this.props.Document, "ink", new InkField(value));
     }
 
     @action
@@ -78,7 +77,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
                 color: InkingControl.Instance.selectedColor,
                 width: InkingControl.Instance.selectedWidth,
                 tool: InkingControl.Instance.selectedTool,
-                page: this.props.Document.GetNumber(KeyStore.CurPage, -1)
+                page: NumCast(this.props.Document.curPage, -1)
             });
             this.inkData = data;
         }
@@ -137,7 +136,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     @computed
     get drawnPaths() {
-        let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
+        let curPage = NumCast(this.props.Document.curPage, -1);
         let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
             if (strokeData.page === -1 || strokeData.page === curPage) {
                 paths.push(<InkingStroke key={id} id={id} line={strokeData.pathData}
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 9a68f0671..4b3dbd4e0 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -2,15 +2,14 @@ import { observable, action, computed } from "mobx";
 
 import { CirclePicker, ColorResult } from 'react-color';
 import React = require("react");
-import { InkTool } from "../../fields/InkField";
 import { observer } from "mobx-react";
 import "./InkingControl.scss";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
 import { SelectionManager } from "../util/SelectionManager";
-import { KeyStore } from "../../fields/KeyStore";
-import { TextField } from "../../fields/TextField";
+import { InkTool } from "../../new_fields/InkField";
+import { Doc } from "../../new_fields/Doc";
 
 library.add(faPen, faHighlighter, faEraser, faBan);
 
@@ -39,7 +38,7 @@ export class InkingControl extends React.Component {
         if (SelectionManager.SelectedDocuments().length === 1) {
             var sdoc = SelectionManager.SelectedDocuments()[0];
             if (sdoc.props.ContainingCollectionView) {
-                sdoc.props.Document.SetDataOnPrototype(KeyStore.BackgroundColor, color.hex, TextField);
+                Doc.SetOnPrototype(sdoc.props.Document, "backgroundColor", color.hex);
             }
         }
     }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 905b48db7..c9d8d83c8 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -3,20 +3,19 @@ import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-s
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { FieldWaiting } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
 import { DragManager, SetupDrag } from "../../util/DragManager";
 import { EditableView } from "../EditableView";
 import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import React = require("react");
+import { Document, listSpec } from '../../../new_fields/Schema';
+import { Cast, StrCast, BoolCast } from '../../../new_fields/Types';
+import { Doc, Id } from '../../../new_fields/Doc';
 
 
 export interface TreeViewProps {
-    document: Document;
-    deleteDoc: (doc: Document) => void;
+    document: Doc;
+    deleteDoc: (doc: Doc) => void;
     moveDocument: DragManager.MoveFunction;
     copyOnDrag: boolean;
 }
@@ -43,9 +42,9 @@ class TreeView extends React.Component<TreeViewProps> {
 
     @action
     remove = (document: Document) => {
-        var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
-        if (children && children !== FieldWaiting) {
-            children.Data.splice(children.Data.indexOf(document), 1);
+        var children = Cast(this.props.document.data, listSpec(Doc));
+        if (children) {
+            children.splice(children.indexOf(document), 1);
         }
     }
 
@@ -80,15 +79,15 @@ class TreeView extends React.Component<TreeViewProps> {
                 display={"inline"}
                 contents={titleString}
                 height={36}
-                GetValue={() => this.props.document.Title}
+                GetValue={() => StrCast(this.props.document.title)}
                 SetValue={(value: string) => {
-                    this.props.document.SetText(KeyStore.Title, value);
+                    this.props.document.title = value;
                     return true;
                 }}
             />);
         return (
             <div className="docContainer" ref={reference} onPointerDown={onItemDown}>
-                {editableView(this.props.document.Title)}
+                {editableView(StrCast(this.props.document.title))}
                 <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
             </div >);
     }
@@ -96,12 +95,12 @@ class TreeView extends React.Component<TreeViewProps> {
     render() {
         let bulletType = BulletType.List;
         let childElements: JSX.Element | undefined = undefined;
-        var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
-        if (children && children !== FieldWaiting) { // add children for a collection
+        var children = Cast(this.props.document.data, listSpec(Doc));
+        if (children) { // add children for a collection
             if (!this._collapsed) {
                 bulletType = BulletType.Collapsible;
                 childElements = <ul>
-                    {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
+                    {children.map(value => <TreeView key={value[Id]} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
                 </ul >;
             }
             else bulletType = BulletType.Collapsed;
@@ -117,22 +116,22 @@ class TreeView extends React.Component<TreeViewProps> {
 }
 
 @observer
-export class CollectionTreeView extends CollectionSubView {
+export class CollectionTreeView extends CollectionSubView(Document) {
 
     @action
     remove = (document: Document) => {
-        var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
-        if (children && children !== FieldWaiting) {
-            children.Data.splice(children.Data.indexOf(document), 1);
+        const children = this.children;
+        if (children) {
+            children.splice(children.indexOf(document), 1);
         }
     }
 
     render() {
-        let children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
-        let copyOnDrag = this.props.Document.GetBoolean(KeyStore.CopyDraggedItems, false);
-        let childrenElement = !children || children === FieldWaiting ? (null) :
-            (children.Data.map(value =>
-                <TreeView document={value} key={value.Id} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
+        const children = this.children;
+        let copyOnDrag = BoolCast(this.props.Document.copyDraggedItems, false);
+        let childrenElement = !children ? (null) :
+            (children.map(value =>
+                <TreeView document={value} key={value[Id]} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
             );
 
         return (
@@ -145,9 +144,9 @@ export class CollectionTreeView extends CollectionSubView {
                         contents={this.props.Document.Title}
                         display={"inline"}
                         height={72}
-                        GetValue={() => this.props.Document.Title}
+                        GetValue={() => StrCast(this.props.Document.title)}
                         SetValue={(value: string) => {
-                            this.props.Document.SetText(KeyStore.Title, value);
+                            this.props.Document.title = value;
                             return true;
                         }} />
                 </div>
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index bde50fed8..f82c6e9cb 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -3,31 +3,29 @@ import React = require("react");
 import { SelectionManager } from "../../util/SelectionManager";
 import { observer } from "mobx-react";
 import './LinkEditor.scss';
-import { KeyStore } from '../../../fields/KeyStore';
 import { props } from "bluebird";
 import { DocumentView } from "./DocumentView";
-import { Document } from "../../../fields/Document";
-import { TextField } from "../../../fields/TextField";
 import { link } from "fs";
+import { StrCast } from "../../../new_fields/Types";
+import { Doc } from "../../../new_fields/Doc";
 
 interface Props {
-    linkDoc: Document;
+    linkDoc: Doc;
     showLinks: () => void;
 }
 
 @observer
 export class LinkEditor extends React.Component<Props> {
 
-    @observable private _nameInput: string = this.props.linkDoc.GetText(KeyStore.Title, "");
-    @observable private _descriptionInput: string = this.props.linkDoc.GetText(KeyStore.LinkDescription, "");
+    @observable private _nameInput: string = StrCast(this.props.linkDoc.title);
+    @observable private _descriptionInput: string = StrCast(this.props.linkDoc.linkDescription);
 
 
     onSaveButtonPressed = (e: React.PointerEvent): void => {
-        console.log("view down");
         e.stopPropagation();
 
-        this.props.linkDoc.SetData(KeyStore.Title, this._nameInput, TextField);
-        this.props.linkDoc.SetData(KeyStore.LinkDescription, this._descriptionInput, TextField);
+        this.props.linkDoc.title = this._nameInput;
+        this.props.linkDoc.linkDescription = this._descriptionInput;
 
         this.props.showLinks();
     }
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 857da1ebb..4cac09dee 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -3,190 +3,184 @@ import "normalize.css";
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { observer } from 'mobx-react';
-import { Document } from '../fields/Document';
-import { BasicField } from '../fields/BasicField';
-import { ListField } from '../fields/ListField';
-import { Key } from '../fields/Key';
-import { Opt, Field } from '../fields/Field';
-import { Server } from '../client/Server';
-
-configure({
-    enforceActions: "observed"
-});
-
-@observer
-class FieldViewer extends React.Component<{ field: BasicField<any> }> {
-    render() {
-        return <span>{JSON.stringify(this.props.field.Data)} ({this.props.field.Id})</span>;
-    }
-}
-
-@observer
-class KeyViewer extends React.Component<{ field: Key }> {
-    render() {
-        return this.props.field.Name;
-    }
-}
-
-@observer
-class ListViewer extends React.Component<{ field: ListField<Field> }>{
-    @observable
-    expanded = false;
-
-    render() {
-        let content;
-        if (this.expanded) {
-            content = (
-                <div>
-                    {this.props.field.Data.map(field => <DebugViewer fieldId={field.Id} key={field.Id} />)}
-                </div>
-            );
-        } else {
-            content = <>[...] ({this.props.field.Id})</>;
-        }
-        return (
-            <div>
-                <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
-                {content}
-            </div >
-        );
-    }
-}
-
-@observer
-class DocumentViewer extends React.Component<{ field: Document }> {
-    private keyMap: ObservableMap<string, Key> = new ObservableMap;
-
-    private disposer?: Lambda;
-
-    componentDidMount() {
-        let f = () => {
-            Array.from(this.props.field._proxies.keys()).forEach(id => {
-                if (!this.keyMap.has(id)) {
-                    Server.GetField(id, (field) => {
-                        if (field && field instanceof Key) {
-                            this.keyMap.set(id, field);
-                        }
-                    });
-                }
-            });
-        };
-        this.disposer = this.props.field._proxies.observe(f);
-        f();
-    }
-
-    componentWillUnmount() {
-        if (this.disposer) {
-            this.disposer();
-        }
-    }
-
-    render() {
-        let fields = Array.from(this.props.field._proxies.entries()).map(kv => {
-            let key = this.keyMap.get(kv[0]);
-            return (
-                <div key={kv[0]}>
-                    <b>({key ? key.Name : kv[0]}): </b>
-                    <DebugViewer fieldId={kv[1]}></DebugViewer>
-                </div>
-            );
-        });
-        return (
-            <div>
-                Document ({this.props.field.Id})
-                <div style={{ paddingLeft: "25px" }}>
-                    {fields}
-                </div>
-            </div>
-        );
-    }
-}
-
-@observer
-class DebugViewer extends React.Component<{ fieldId: string }> {
-    @observable
-    private field?: Field;
-
-    @observable
-    private error?: string;
-
-    constructor(props: { fieldId: string }) {
-        super(props);
-        this.update();
-    }
-
-    update() {
-        Server.GetField(this.props.fieldId, action((field: Opt<Field>) => {
-            this.field = field;
-            if (!field) {
-                this.error = `Field with id ${this.props.fieldId} not found`;
-            }
-        }));
-
-    }
-
-    render() {
-        let content;
-        if (this.field) {
-            // content = this.field.ToJson();
-            if (this.field instanceof ListField) {
-                content = (<ListViewer field={this.field} />);
-            } else if (this.field instanceof Document) {
-                content = (<DocumentViewer field={this.field} />);
-            } else if (this.field instanceof BasicField) {
-                content = (<FieldViewer field={this.field} />);
-            } else if (this.field instanceof Key) {
-                content = (<KeyViewer field={this.field} />);
-            } else {
-                content = (<span>Unrecognized field type</span>);
-            }
-        } else if (this.error) {
-            content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>;
-        } else {
-            content = <span>Field loading: {this.props.fieldId}</span>;
-        }
-        return content;
-    }
-}
-
-@observer
-class Viewer extends React.Component {
-    @observable
-    private idToAdd: string = '';
-
-    @observable
-    private ids: string[] = [];
-
-    @action
-    inputOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this.idToAdd = e.target.value;
-    }
-
-    @action
-    onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
-        if (e.key === "Enter") {
-            this.ids.push(this.idToAdd);
-            this.idToAdd = "";
-        }
-    }
-
-    render() {
-        return (
-            <>
-                <input value={this.idToAdd}
-                    onChange={this.inputOnChange}
-                    onKeyDown={this.onKeyPress} />
-                <div>
-                    {this.ids.map(id => <DebugViewer fieldId={id} key={id}></DebugViewer>)}
-                </div>
-            </>
-        );
-    }
-}
-
-ReactDOM.render((
-    <div style={{ position: "absolute", width: "100%", height: "100%" }}>
-        <Viewer />
-    </div>),
-    document.getElementById('root')
-);
\ No newline at end of file
+
+// configure({
+//     enforceActions: "observed"
+// });
+
+// @observer
+// class FieldViewer extends React.Component<{ field: BasicField<any> }> {
+//     render() {
+//         return <span>{JSON.stringify(this.props.field.Data)} ({this.props.field.Id})</span>;
+//     }
+// }
+
+// @observer
+// class KeyViewer extends React.Component<{ field: Key }> {
+//     render() {
+//         return this.props.field.Name;
+//     }
+// }
+
+// @observer
+// class ListViewer extends React.Component<{ field: ListField<Field> }>{
+//     @observable
+//     expanded = false;
+
+//     render() {
+//         let content;
+//         if (this.expanded) {
+//             content = (
+//                 <div>
+//                     {this.props.field.Data.map(field => <DebugViewer fieldId={field.Id} key={field.Id} />)}
+//                 </div>
+//             );
+//         } else {
+//             content = <>[...] ({this.props.field.Id})</>;
+//         }
+//         return (
+//             <div>
+//                 <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
+//                 {content}
+//             </div >
+//         );
+//     }
+// }
+
+// @observer
+// class DocumentViewer extends React.Component<{ field: Document }> {
+//     private keyMap: ObservableMap<string, Key> = new ObservableMap;
+
+//     private disposer?: Lambda;
+
+//     componentDidMount() {
+//         let f = () => {
+//             Array.from(this.props.field._proxies.keys()).forEach(id => {
+//                 if (!this.keyMap.has(id)) {
+//                     Server.GetField(id, (field) => {
+//                         if (field && field instanceof Key) {
+//                             this.keyMap.set(id, field);
+//                         }
+//                     });
+//                 }
+//             });
+//         };
+//         this.disposer = this.props.field._proxies.observe(f);
+//         f();
+//     }
+
+//     componentWillUnmount() {
+//         if (this.disposer) {
+//             this.disposer();
+//         }
+//     }
+
+//     render() {
+//         let fields = Array.from(this.props.field._proxies.entries()).map(kv => {
+//             let key = this.keyMap.get(kv[0]);
+//             return (
+//                 <div key={kv[0]}>
+//                     <b>({key ? key.Name : kv[0]}): </b>
+//                     <DebugViewer fieldId={kv[1]}></DebugViewer>
+//                 </div>
+//             );
+//         });
+//         return (
+//             <div>
+//                 Document ({this.props.field.Id})
+//                 <div style={{ paddingLeft: "25px" }}>
+//                     {fields}
+//                 </div>
+//             </div>
+//         );
+//     }
+// }
+
+// @observer
+// class DebugViewer extends React.Component<{ fieldId: string }> {
+//     @observable
+//     private field?: Field;
+
+//     @observable
+//     private error?: string;
+
+//     constructor(props: { fieldId: string }) {
+//         super(props);
+//         this.update();
+//     }
+
+//     update() {
+//         Server.GetField(this.props.fieldId, action((field: Opt<Field>) => {
+//             this.field = field;
+//             if (!field) {
+//                 this.error = `Field with id ${this.props.fieldId} not found`;
+//             }
+//         }));
+
+//     }
+
+//     render() {
+//         let content;
+//         if (this.field) {
+//             // content = this.field.ToJson();
+//             if (this.field instanceof ListField) {
+//                 content = (<ListViewer field={this.field} />);
+//             } else if (this.field instanceof Document) {
+//                 content = (<DocumentViewer field={this.field} />);
+//             } else if (this.field instanceof BasicField) {
+//                 content = (<FieldViewer field={this.field} />);
+//             } else if (this.field instanceof Key) {
+//                 content = (<KeyViewer field={this.field} />);
+//             } else {
+//                 content = (<span>Unrecognized field type</span>);
+//             }
+//         } else if (this.error) {
+//             content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>;
+//         } else {
+//             content = <span>Field loading: {this.props.fieldId}</span>;
+//         }
+//         return content;
+//     }
+// }
+
+// @observer
+// class Viewer extends React.Component {
+//     @observable
+//     private idToAdd: string = '';
+
+//     @observable
+//     private ids: string[] = [];
+
+//     @action
+//     inputOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+//         this.idToAdd = e.target.value;
+//     }
+
+//     @action
+//     onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
+//         if (e.key === "Enter") {
+//             this.ids.push(this.idToAdd);
+//             this.idToAdd = "";
+//         }
+//     }
+
+//     render() {
+//         return (
+//             <>
+//                 <input value={this.idToAdd}
+//                     onChange={this.inputOnChange}
+//                     onKeyDown={this.onKeyPress} />
+//                 <div>
+//                     {this.ids.map(id => <DebugViewer fieldId={id} key={id}></DebugViewer>)}
+//                 </div>
+//             </>
+//         );
+//     }
+// }
+
+// ReactDOM.render((
+//     <div style={{ position: "absolute", width: "100%", height: "100%" }}>
+//         <Viewer />
+//     </div>),
+//     document.getElementById('root')
+// );
\ No newline at end of file
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 7f87be45d..ae532c9e2 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -1,101 +1,101 @@
-import { Field, FieldId } from "./Field";
-import { Types } from "../server/Message";
-import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting";
-import { Server } from "../client/Server";
-import { Without } from "../Utils";
+// import { Field, FieldId } from "./Field";
+// import { Types } from "../server/Message";
+// import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting";
+// import { Server } from "../client/Server";
+// import { Without } from "../Utils";
 
-export interface SerializableOptions extends Without<ScriptOptions, "capturedVariables"> {
-    capturedIds: { [id: string]: string };
-}
+// export interface SerializableOptions extends Without<ScriptOptions, "capturedVariables"> {
+//     capturedIds: { [id: string]: string };
+// }
 
-export interface ScriptData {
-    script: string;
-    options: SerializableOptions;
-}
+// export interface ScriptData {
+//     script: string;
+//     options: SerializableOptions;
+// }
 
-export class ScriptField extends Field {
-    private _script?: CompiledScript;
-    get script(): CompiledScript {
-        return this._script!;
-    }
-    private options?: ScriptData;
+// export class ScriptField extends Field {
+//     private _script?: CompiledScript;
+//     get script(): CompiledScript {
+//         return this._script!;
+//     }
+//     private options?: ScriptData;
 
-    constructor(script?: CompiledScript, id?: FieldId, save: boolean = true) {
-        super(id);
+//     constructor(script?: CompiledScript, id?: FieldId, save: boolean = true) {
+//         super(id);
 
-        this._script = script;
+//         this._script = script;
 
-        if (save) {
-            Server.UpdateField(this);
-        }
-    }
+//         if (save) {
+//             Server.UpdateField(this);
+//         }
+//     }
 
-    ToScriptString() {
-        return "new ScriptField(...)";
-    }
+//     ToScriptString() {
+//         return "new ScriptField(...)";
+//     }
 
-    GetValue() {
-        return this.script;
-    }
+//     GetValue() {
+//         return this.script;
+//     }
 
-    TrySetValue(): boolean {
-        throw new Error("Script fields currently can't be modified");
-    }
+//     TrySetValue(): boolean {
+//         throw new Error("Script fields currently can't be modified");
+//     }
 
-    UpdateFromServer() {
-        throw new Error("Script fields currently can't be updated");
-    }
+//     UpdateFromServer() {
+//         throw new Error("Script fields currently can't be updated");
+//     }
 
-    static FromJson(id: string, data: ScriptData): ScriptField {
-        let field = new ScriptField(undefined, id, false);
-        field.options = data;
-        return field;
-    }
+//     static FromJson(id: string, data: ScriptData): ScriptField {
+//         let field = new ScriptField(undefined, id, false);
+//         field.options = data;
+//         return field;
+//     }
 
-    init(callback: (res: Field) => any) {
-        const options = this.options!;
-        const keys = Object.keys(options.options.capturedIds);
-        Server.GetFields(keys).then(fields => {
-            let captured: { [name: string]: Field } = {};
-            keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]);
-            const opts: ScriptOptions = {
-                addReturn: options.options.addReturn,
-                params: options.options.params,
-                requiredType: options.options.requiredType,
-                capturedVariables: captured
-            };
-            const script = CompileScript(options.script, opts);
-            if (!script.compiled) {
-                throw new Error("Can't compile script");
-            }
-            this._script = script;
-            callback(this);
-        });
-    }
+//     init(callback: (res: Field) => any) {
+//         const options = this.options!;
+//         const keys = Object.keys(options.options.capturedIds);
+//         Server.GetFields(keys).then(fields => {
+//             let captured: { [name: string]: Field } = {};
+//             keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]);
+//             const opts: ScriptOptions = {
+//                 addReturn: options.options.addReturn,
+//                 params: options.options.params,
+//                 requiredType: options.options.requiredType,
+//                 capturedVariables: captured
+//             };
+//             const script = CompileScript(options.script, opts);
+//             if (!script.compiled) {
+//                 throw new Error("Can't compile script");
+//             }
+//             this._script = script;
+//             callback(this);
+//         });
+//     }
 
-    ToJson() {
-        const { options, originalScript } = this.script;
-        let capturedIds: { [id: string]: string } = {};
-        for (const capt in options.capturedVariables) {
-            capturedIds[options.capturedVariables[capt].Id] = capt;
-        }
-        const opts: SerializableOptions = {
-            ...options,
-            capturedIds
-        };
-        delete (opts as any).capturedVariables;
-        return {
-            id: this.Id,
-            type: Types.Script,
-            data: {
-                script: originalScript,
-                options: opts,
-            },
-        };
-    }
+//     ToJson() {
+//         const { options, originalScript } = this.script;
+//         let capturedIds: { [id: string]: string } = {};
+//         for (const capt in options.capturedVariables) {
+//             capturedIds[options.capturedVariables[capt].Id] = capt;
+//         }
+//         const opts: SerializableOptions = {
+//             ...options,
+//             capturedIds
+//         };
+//         delete (opts as any).capturedVariables;
+//         return {
+//             id: this.Id,
+//             type: Types.Script,
+//             data: {
+//                 script: originalScript,
+//                 options: opts,
+//             },
+//         };
+//     }
 
-    Copy(): Field {
-        //Script fields are currently immutable, so we can fake copy them
-        return this;
-    }
-}
\ No newline at end of file
+//     Copy(): Field {
+//         //Script fields are currently immutable, so we can fake copy them
+//         return this;
+//     }
+// }
\ No newline at end of file
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index ec89a1194..1f9e160ce 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -1,15 +1,14 @@
 import * as ReactDOM from 'react-dom';
 import * as rp from 'request-promise';
-import { Documents } from '../client/documents/Documents';
-import { Server } from '../client/Server';
-import { Document } from '../fields/Document';
-import { KeyStore } from '../fields/KeyStore';
-import { ListField } from '../fields/ListField';
+import { Docs } from '../client/documents/Documents';
 import { RouteStore } from '../server/RouteStore';
-import { ServerUtils } from '../server/ServerUtil';
 import "./ImageUpload.scss";
 import React = require('react');
-import { Opt } from '../fields/Field';
+import { DocServer } from '../client/DocServer';
+import { Opt, Doc } from '../new_fields/Doc';
+import { Cast } from '../new_fields/Types';
+import { listSpec } from '../new_fields/Schema';
+import { List } from '../new_fields/List';
 
 
 
@@ -38,21 +37,24 @@ const onFileLoad = async (file: any) => {
             const json = await res.json();
             json.map(async (file: any) => {
                 let path = window.location.origin + file;
-                var doc: Document = Documents.ImageDocument(path, { nativeWidth: 200, width: 200 });
+                var doc = Docs.ImageDocument(path, { nativeWidth: 200, width: 200 });
 
-                const res = await rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId));
+                const res = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId));
                 if (!res) {
                     throw new Error("No user id returned");
                 }
-                const field = await Server.GetField(res);
-                let pending: Opt<Document>;
-                if (field instanceof Document) {
-                    pending = await field.GetTAsync(KeyStore.OptionalRightCollection, Document);
+                const field = await DocServer.GetRefField(res);
+                let pending: Opt<Doc>;
+                if (field instanceof Doc) {
+                    pending = await Cast(field.optionalRightCollection, Doc);
                 }
                 if (pending) {
-                    pending.GetOrCreateAsync(KeyStore.Data, ListField, list => {
-                        list.Data.push(doc);
-                    });
+                    const data = await Cast(pending.data, listSpec(Doc));
+                    if (data) {
+                        data.push(doc);
+                    } else {
+                        pending.data = new List([doc]);
+                    }
                 }
             });
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index a5f495477..6dfa37c1b 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -166,6 +166,4 @@ export namespace Doc {
         return delegate;
     }
     export const Prototype = Symbol("Prototype");
-}
-
-export const GetAsync = Doc.GetAsync;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/server/authentication/controllers/WorkspacesMenu.tsx b/src/server/authentication/controllers/WorkspacesMenu.tsx
index b08c1aebe..29327e5ad 100644
--- a/src/server/authentication/controllers/WorkspacesMenu.tsx
+++ b/src/server/authentication/controllers/WorkspacesMenu.tsx
@@ -2,15 +2,15 @@ import * as React from 'react';
 import { observable, action, configure, reaction, computed, ObservableMap, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import './WorkspacesMenu.css';
-import { Document } from '../../../fields/Document';
 import { EditableView } from '../../../client/views/EditableView';
-import { KeyStore } from '../../../fields/KeyStore';
+import { Doc, Id } from '../../../new_fields/Doc';
+import { StrCast } from '../../../new_fields/Types';
 
 export interface WorkspaceMenuProps {
-    active: Document | undefined;
-    open: (workspace: Document) => void;
+    active: Doc | undefined;
+    open: (workspace: Doc) => void;
     new: () => void;
-    allWorkspaces: Document[];
+    allWorkspaces: Doc[];
     isShown: () => boolean;
     toggle: () => void;
 }
@@ -60,7 +60,7 @@ export class WorkspacesMenu extends React.Component<WorkspaceMenuProps> {
                 />
                 {this.props.allWorkspaces.map((s, i) =>
                     <div
-                        key={s.Id}
+                        key={s[Id]}
                         onContextMenu={(e) => {
                             e.preventDefault();
                             this.props.open(s);
@@ -73,9 +73,9 @@ export class WorkspacesMenu extends React.Component<WorkspaceMenuProps> {
                         <span>{i + 1} - </span>
                         <EditableView
                             display={"inline"}
-                            GetValue={() => s.Title}
+                            GetValue={() => StrCast(s.title)}
                             SetValue={(title: string): boolean => {
-                                s.SetText(KeyStore.Title, title);
+                                s.title = title;
                                 return true;
                             }}
                             contents={s.Title}
-- 
cgit v1.2.3-70-g09d2


From d4a77dd055685dd81a762ef40e0c3b7606586e9c Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sat, 27 Apr 2019 21:40:17 -0400
Subject: Split more files up

---
 src/client/DocServer.ts                            |  3 +-
 src/client/northstar/dash-fields/HistogramField.ts |  2 +-
 src/client/northstar/dash-nodes/HistogramBox.tsx   |  3 +-
 src/client/util/Scripting.ts                       |  1 +
 src/client/views/Main.tsx                          |  3 +-
 .../views/collections/CollectionBaseView.tsx       |  3 +-
 .../views/collections/CollectionDockingView.tsx    |  3 +-
 .../views/collections/CollectionSchemaView.tsx     |  3 +-
 src/client/views/collections/CollectionSubView.tsx |  3 +-
 .../views/collections/CollectionTreeView.tsx       |  3 +-
 .../CollectionFreeFormLinksView.tsx                |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  3 +-
 src/client/views/nodes/DocumentView.tsx            |  3 +-
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/LinkMenu.tsx                |  3 +-
 src/new_fields/Doc.ts                              | 37 +++-------------------
 src/new_fields/HtmlField.ts                        |  2 +-
 src/new_fields/IconField.ts                        |  2 +-
 src/new_fields/InkField.ts                         |  2 +-
 src/new_fields/List.ts                             |  3 +-
 src/new_fields/ObjectField.ts                      | 17 ++++++++++
 src/new_fields/Proxy.ts                            |  4 ++-
 src/new_fields/RefField.ts                         | 18 +++++++++++
 src/new_fields/RichTextField.ts                    |  2 +-
 src/new_fields/Schema.ts                           |  8 ++---
 src/new_fields/Types.ts                            |  2 +-
 src/new_fields/URLField.ts                         |  2 +-
 src/new_fields/util.ts                             |  4 ++-
 .../authentication/controllers/WorkspacesMenu.tsx  |  3 +-
 src/server/database.ts                             |  2 ++
 30 files changed, 88 insertions(+), 60 deletions(-)
 create mode 100644 src/new_fields/ObjectField.ts
 create mode 100644 src/new_fields/RefField.ts

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 3f17baec6..c7cbfce37 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,8 +1,9 @@
 import * as OpenSocket from 'socket.io-client';
 import { MessageStore, Types, Message } from "./../server/Message";
-import { Opt, FieldWaiting, RefField, HandleUpdate } from '../new_fields/Doc';
+import { Opt, FieldWaiting } from '../new_fields/Doc';
 import { Utils } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
+import { RefField, HandleUpdate } from '../new_fields/RefField';
 
 export namespace DocServer {
     const _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index 118f4cf7f..730289536 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -3,7 +3,7 @@ import { custom, serializable } from "serializr";
 import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
 import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
 import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
-import { ObjectField } from "../../../new_fields/Doc";
+import { ObjectField } from "../../../new_fields/ObjectField";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { OmitKeys } from "../../../Utils";
 import { Deserializable } from "../../util/SerializationHelper";
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 4a65b14cf..a9c68ccba 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -20,7 +20,8 @@ import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
 import { StyleConstants } from "../utils/StyleContants";
 import { NumCast, Cast } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
-import { Doc, Id } from "../../../new_fields/Doc";
+import { Doc } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/RefField";
 
 
 @observer
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index dbec82340..e45f61c11 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -11,6 +11,7 @@ import { Docs } from "../documents/Documents";
 import { Doc, Field } from '../../new_fields/Doc';
 import { ImageField, PdfField, VideoField, AudioField } from '../../new_fields/URLField';
 import { List } from '../../new_fields/List';
+import { RichTextField } from '../../new_fields/RichTextField';
 
 export interface ScriptSucccess {
     success: true;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 1e3d4e259..4a68d1c68 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -33,10 +33,11 @@ import { MainOverlayTextBox } from './MainOverlayTextBox';
 import { DocumentView } from './nodes/DocumentView';
 import { PreviewCursor } from './PreviewCursor';
 import { SelectionManager } from '../util/SelectionManager';
-import { FieldResult, Field, Doc, Id, Opt } from '../../new_fields/Doc';
+import { FieldResult, Field, Doc, Opt } from '../../new_fields/Doc';
 import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
 import { DocServer } from '../DocServer';
 import { listSpec } from '../../new_fields/Schema';
+import { Id } from '../../new_fields/RefField';
 
 
 @observer
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 4807dc40a..b2fba1415 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -4,9 +4,10 @@ import * as React from 'react';
 import { ContextMenu } from '../ContextMenu';
 import { FieldViewProps } from '../nodes/FieldView';
 import { Cast, FieldValue, PromiseValue, NumCast } from '../../../new_fields/Types';
-import { Doc, FieldResult, Opt, Id } from '../../../new_fields/Doc';
+import { Doc, FieldResult, Opt } from '../../../new_fields/Doc';
 import { listSpec } from '../../../new_fields/Schema';
 import { List } from '../../../new_fields/List';
+import { Id } from '../../../new_fields/RefField';
 
 export enum CollectionViewType {
     Invalid,
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 2ff409b9b..1574562c6 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -13,11 +13,12 @@ import React = require("react");
 import { SubCollectionViewProps } from "./CollectionSubView";
 import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
 import { Transform } from '../../util/Transform';
-import { Doc, Id, Opt, Field, FieldId } from "../../../new_fields/Doc";
+import { Doc, Opt, Field } from "../../../new_fields/Doc";
 import { Cast, NumCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { DocServer } from "../../DocServer";
 import { listSpec } from "../../../new_fields/Schema";
+import { Id, FieldId } from "../../../new_fields/RefField";
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 874170f3d..58d20819b 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -19,10 +19,11 @@ import { DocumentView } from "../nodes/DocumentView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import "./CollectionSchemaView.scss";
 import { CollectionSubView } from "./CollectionSubView";
-import { Opt, Field, Doc, Id } from "../../../new_fields/Doc";
+import { Opt, Field, Doc } from "../../../new_fields/Doc";
 import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
 import { List } from "../../../new_fields/List";
+import { Id } from "../../../new_fields/RefField";
 
 
 // bcz: need to add drag and drop of rows and columns.  This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 558a8728f..4d090b680 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -10,12 +10,13 @@ import * as rp from 'request-promise';
 import { CollectionView } from "./CollectionView";
 import { CollectionPDFView } from "./CollectionPDFView";
 import { CollectionVideoView } from "./CollectionVideoView";
-import { Doc, ObjectField, Opt } from "../../../new_fields/Doc";
+import { Doc, Opt } from "../../../new_fields/Doc";
 import { DocComponent } from "../DocComponent";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, PromiseValue } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { DocServer } from "../../DocServer";
+import { ObjectField } from "../../../new_fields/ObjectField";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index c9d8d83c8..7ec9a8549 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -10,7 +10,8 @@ import "./CollectionTreeView.scss";
 import React = require("react");
 import { Document, listSpec } from '../../../new_fields/Schema';
 import { Cast, StrCast, BoolCast } from '../../../new_fields/Types';
-import { Doc, Id } from '../../../new_fields/Doc';
+import { Doc } from '../../../new_fields/Doc';
+import { Id } from '../../../new_fields/RefField';
 
 
 export interface TreeViewProps {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index ce9995630..f693d55e8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -7,10 +7,11 @@ import { CollectionViewProps } from "../CollectionSubView";
 import "./CollectionFreeFormLinksView.scss";
 import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
 import React = require("react");
-import { Doc, Id } from "../../../../new_fields/Doc";
+import { Doc } from "../../../../new_fields/Doc";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
 import { listSpec } from "../../../../new_fields/Schema";
 import { List } from "../../../../new_fields/List";
+import { Id } from "../../../../new_fields/RefField";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 047fbad18..18107e98a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -19,10 +19,11 @@ import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
 import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema";
-import { Doc, Id } from "../../../../new_fields/Doc";
+import { Doc } from "../../../../new_fields/Doc";
 import { FieldValue, Cast, NumCast } from "../../../../new_fields/Types";
 import { pageSchema } from "../../nodes/ImageBox";
 import { List } from "../../../../new_fields/List";
+import { Id } from "../../../../new_fields/RefField";
 
 export const panZoomSchema = createSchema({
     panX: "number",
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index aabc1633e..c304b6a35 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -15,7 +15,7 @@ import { ContextMenu } from "../ContextMenu";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
-import { Field, Opt, Doc, Id } from "../../../new_fields/Doc";
+import { Opt, Doc } from "../../../new_fields/Doc";
 import { DocComponent } from "../DocComponent";
 import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
 import { FieldValue, Cast, PromiseValue } from "../../../new_fields/Types";
@@ -24,6 +24,7 @@ import { CollectionFreeFormView } from "../collections/collectionFreeForm/Collec
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { MarqueeView } from "../collections/collectionFreeForm/MarqueeView";
 import { DocServer } from "../../DocServer";
+import { Id } from "../../../new_fields/RefField";
 
 const linkSchema = createSchema({
     title: "string",
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index c00c47fc4..dc36c5914 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -16,6 +16,7 @@ import { Opt, Doc, FieldResult } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { ImageField, VideoField, AudioField } from "../../../new_fields/URLField";
 import { IconField } from "../../../new_fields/IconField";
+import { RichTextField } from "../../../new_fields/RichTextField";
 
 
 //
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 3ecc8555d..e21adebbc 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -5,9 +5,10 @@ import { LinkBox } from "./LinkBox";
 import { LinkEditor } from "./LinkEditor";
 import './LinkMenu.scss';
 import React = require("react");
-import { Doc, Id } from "../../../new_fields/Doc";
+import { Doc } from "../../../new_fields/Doc";
 import { Cast, FieldValue } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
+import { Id } from "../../../new_fields/RefField";
 
 interface Props {
     docView: DocumentView;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index fb7b6e360..4ef2a465f 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -8,38 +8,8 @@ import { Cast, ToConstructor, PromiseValue, FieldValue } from "./Types";
 import { UndoManager, undoBatch } from "../client/util/UndoManager";
 import { listSpec } from "./Schema";
 import { List } from "./List";
-
-export type FieldId = string;
-export const HandleUpdate = Symbol("HandleUpdate");
-export const Id = Symbol("Id");
-export abstract class RefField {
-    @serializable(alias("id", primitive()))
-    private __id: FieldId;
-    readonly [Id]: FieldId;
-
-    constructor(id?: FieldId) {
-        this.__id = id || Utils.GenerateGuid();
-        this[Id] = this.__id;
-    }
-
-    protected [HandleUpdate]?(diff: any): void;
-}
-
-export const Update = Symbol("Update");
-export const OnUpdate = Symbol("OnUpdate");
-export const Parent = Symbol("Parent");
-export class ObjectField {
-    protected [OnUpdate]?: (diff?: any) => void;
-    private [Parent]?: Doc;
-    readonly [Id] = "";
-}
-
-export namespace ObjectField {
-    export function MakeCopy(field: ObjectField) {
-        //TODO Types
-        return field;
-    }
-}
+import { ObjectField } from "./ObjectField";
+import { RefField, FieldId, Id } from "./RefField";
 
 export function IsField(field: any): field is Field {
     return (typeof field === "string")
@@ -53,6 +23,7 @@ export type Opt<T> = T | undefined;
 export type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>;
 export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
 
+export const Update = Symbol("Update");
 export const Self = Symbol("Self");
 
 @Deserializable("doc").withFields(["id"])
@@ -161,7 +132,7 @@ export namespace Doc {
                     copy[key] = field;
                 }
             }
-        })
+        });
         return copy;
     }
 
diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts
index 76fdb1f62..808a3499b 100644
--- a/src/new_fields/HtmlField.ts
+++ b/src/new_fields/HtmlField.ts
@@ -1,6 +1,6 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, primitive } from "serializr";
-import { ObjectField } from "./Doc";
+import { ObjectField } from "./ObjectField";
 
 @Deserializable("html")
 export class HtmlField extends ObjectField {
diff --git a/src/new_fields/IconField.ts b/src/new_fields/IconField.ts
index 32f3aa4d5..46f111f8e 100644
--- a/src/new_fields/IconField.ts
+++ b/src/new_fields/IconField.ts
@@ -1,6 +1,6 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, primitive } from "serializr";
-import { ObjectField } from "./Doc";
+import { ObjectField } from "./ObjectField";
 
 @Deserializable("icon")
 export class IconField extends ObjectField {
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 49e6bf61e..42223c494 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -1,6 +1,6 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, custom, createSimpleSchema, list, object, map } from "serializr";
-import { ObjectField } from "./Doc";
+import { ObjectField } from "./ObjectField";
 
 export enum InkTool {
     None,
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index f01ac210a..428f661c9 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -1,8 +1,9 @@
 import { Deserializable, autoObject } from "../client/util/SerializationHelper";
-import { Field, ObjectField, Update, OnUpdate, Self } from "./Doc";
+import { Field, Update, Self } from "./Doc";
 import { setter, getter } from "./util";
 import { serializable, alias, list } from "serializr";
 import { observable } from "mobx";
+import { ObjectField, OnUpdate } from "./ObjectField";
 
 @Deserializable("list")
 class ListImpl<T extends Field> extends ObjectField {
diff --git a/src/new_fields/ObjectField.ts b/src/new_fields/ObjectField.ts
new file mode 100644
index 000000000..9cac2c528
--- /dev/null
+++ b/src/new_fields/ObjectField.ts
@@ -0,0 +1,17 @@
+import { Doc } from "./Doc";
+
+export const OnUpdate = Symbol("OnUpdate");
+export const Parent = Symbol("Parent");
+const Id = Symbol("Object Id");
+export class ObjectField {
+    protected [OnUpdate]?: (diff?: any) => void;
+    private [Parent]?: Doc;
+    readonly [Id] = "";
+}
+
+export namespace ObjectField {
+    export function MakeCopy(field: ObjectField) {
+        //TODO Types
+        return field;
+    }
+}
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
index 2aa78731e..56e41cc0f 100644
--- a/src/new_fields/Proxy.ts
+++ b/src/new_fields/Proxy.ts
@@ -1,8 +1,10 @@
 import { Deserializable } from "../client/util/SerializationHelper";
-import { RefField, Id, ObjectField, FieldWaiting } from "./Doc";
+import { FieldWaiting } from "./Doc";
 import { primitive, serializable } from "serializr";
 import { observable, action } from "mobx";
 import { DocServer } from "../client/DocServer";
+import { RefField, Id } from "./RefField";
+import { ObjectField } from "./ObjectField";
 
 @Deserializable("proxy")
 export class ProxyField<T extends RefField> extends ObjectField {
diff --git a/src/new_fields/RefField.ts b/src/new_fields/RefField.ts
new file mode 100644
index 000000000..202c65f21
--- /dev/null
+++ b/src/new_fields/RefField.ts
@@ -0,0 +1,18 @@
+import { serializable, primitive, alias } from "serializr";
+import { Utils } from "../Utils";
+
+export type FieldId = string;
+export const HandleUpdate = Symbol("HandleUpdate");
+export const Id = Symbol("Id");
+export abstract class RefField {
+    @serializable(alias("id", primitive()))
+    private __id: FieldId;
+    readonly [Id]: FieldId;
+
+    constructor(id?: FieldId) {
+        this.__id = id || Utils.GenerateGuid();
+        this[Id] = this.__id;
+    }
+
+    protected [HandleUpdate]?(diff: any): void;
+}
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index 156e4efd9..0fa3cf73c 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -1,4 +1,4 @@
-import { ObjectField } from "./Doc";
+import { ObjectField } from "./ObjectField";
 import { serializable } from "serializr";
 
 export class RichTextField extends ObjectField {
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
index 5081521c7..7444878fe 100644
--- a/src/new_fields/Schema.ts
+++ b/src/new_fields/Schema.ts
@@ -21,15 +21,15 @@ export function makeInterface<T extends Interface[]>(...schemas: T): (doc?: Doc)
         }
     }
     const proto = new Proxy({}, {
-        get(target: any, prop) {
-            const field = target.doc[prop];
+        get(target: any, prop, receiver) {
+            const field = receiver.doc[prop];
             if (prop in schema) {
                 return Cast(field, (schema as any)[prop]);
             }
             return field;
         },
-        set(target: any, prop, value) {
-            target.doc[prop] = value;
+        set(target: any, prop, value, receiver) {
+            receiver.doc[prop] = value;
             return true;
         }
     });
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index 7fa18673f..3f8eabd45 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -1,4 +1,4 @@
-import { Field, Opt, FieldWaiting, FieldResult, RefField } from "./Doc";
+import { Field, Opt, FieldResult } from "./Doc";
 import { List } from "./List";
 
 export type ToType<T extends ToConstructor<Field> | ListSpec<Field>> =
diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts
index 1da245e73..95c679df7 100644
--- a/src/new_fields/URLField.ts
+++ b/src/new_fields/URLField.ts
@@ -1,6 +1,6 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, custom } from "serializr";
-import { ObjectField } from "./Doc";
+import { ObjectField } from "./ObjectField";
 
 function url() {
     return custom(
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 2d9721b2e..011e8c8d9 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -1,8 +1,10 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Update, OnUpdate, Parent, ObjectField, RefField, Doc, Id, Field } from "./Doc";
+import { Update, Doc, Field } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField } from "./Proxy";
 import { FieldValue } from "./Types";
+import { RefField, Id } from "./RefField";
+import { ObjectField, Parent, OnUpdate } from "./ObjectField";
 
 export function setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
     if (SerializationHelper.IsSerializing()) {
diff --git a/src/server/authentication/controllers/WorkspacesMenu.tsx b/src/server/authentication/controllers/WorkspacesMenu.tsx
index 29327e5ad..91756315d 100644
--- a/src/server/authentication/controllers/WorkspacesMenu.tsx
+++ b/src/server/authentication/controllers/WorkspacesMenu.tsx
@@ -3,8 +3,9 @@ import { observable, action, configure, reaction, computed, ObservableMap, runIn
 import { observer } from "mobx-react";
 import './WorkspacesMenu.css';
 import { EditableView } from '../../../client/views/EditableView';
-import { Doc, Id } from '../../../new_fields/Doc';
+import { Doc } from '../../../new_fields/Doc';
 import { StrCast } from '../../../new_fields/Types';
+import { Id } from '../../../new_fields/RefField';
 
 export interface WorkspaceMenuProps {
     active: Doc | undefined;
diff --git a/src/server/database.ts b/src/server/database.ts
index a61b4d823..6b3b6797f 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -60,11 +60,13 @@ export class Database {
     }
 
     public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = Database.DocumentsCollection) {
+        console.log("getDocument");
         this.db && this.db.collection(collectionName).findOne({ id: id }, (err, result) =>
             fn(result ? ({ id: result._id, type: result.type, data: result.data }) : undefined));
     }
 
     public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = Database.DocumentsCollection) {
+        console.log("getDocuments");
         this.db && this.db.collection(collectionName).find({ id: { "$in": ids } }).toArray((err, docs) => {
             if (err) {
                 console.log(err.message);
-- 
cgit v1.2.3-70-g09d2


From ecb4c692c6f317c4108cb065874003fc51242f7a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 1 May 2019 20:43:26 -0400
Subject: made tree view work for docking views

---
 src/client/documents/Documents.ts                  |  5 +++--
 src/client/views/Main.tsx                          | 11 ++++++-----
 .../views/collections/CollectionDockingView.tsx    | 22 +++++++++++++++++-----
 .../views/collections/CollectionTreeView.tsx       |  9 +++++++--
 src/client/views/nodes/DocumentView.tsx            |  4 +++-
 5 files changed, 36 insertions(+), 15 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4045fb22b..6ca2567dc 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -57,6 +57,7 @@ export interface DocumentOptions {
     documentText?: string;
     borderRounding?: number;
     schemaColumns?: List<string>;
+    dockingConfig?: string;
     // [key: string]: Opt<Field>;
 }
 const delegateKeys = ["x", "y", "width", "height", "panX", "panY"];
@@ -247,8 +248,8 @@ export namespace Docs {
     export function TreeDocument(documents: Array<Doc>, options: DocumentOptions) {
         return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree });
     }
-    export function DockDocument(config: string, options: DocumentOptions) {
-        return CreateInstance(collProto, config, { ...options, viewType: CollectionViewType.Docking });
+    export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions) {
+        return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config });
     }
 
     export function CaptionDocument(doc: Doc) {
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 3f8314d5f..2636b08bd 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -150,7 +150,7 @@ export class Main extends React.Component {
         if (list) {
             let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, title: "mini collection" });
             var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc)] }] };
-            let mainDoc = Docs.DockDocument(JSON.stringify(dockingLayout), { title: `Main Container ${list.length + 1}` });
+            let mainDoc = Docs.DockDocument([freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` });
             list.push(mainDoc);
             CurrentUserUtils.MainDocId = mainDoc[Id];
             // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
@@ -208,8 +208,8 @@ export class Main extends React.Component {
     }
 
     /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
-    @computed
-    get nodesMenu() {
+    nodesMenu() {
+
         let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
         let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c27211_sample_explain.pdf";
         let weburl = "https://cs.brown.edu/courses/cs166/";
@@ -219,7 +219,8 @@ export class Main extends React.Component {
         let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
         let addColNode = action(() => Docs.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
         let addSchemaNode = action(() => Docs.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
-        let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true }));
+        let addTreeNode = action(() => Docs.TreeDocument([this.mainContainer!], { width: 250, height: 400, title: "Library:" + StrCast(this.mainContainer!.title), copyDraggedItems: true }));
+        // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true }));
         let addVideoNode = action(() => Docs.VideoDocument(videourl, { width: 200, title: "video node" }));
         let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
         let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
@@ -294,7 +295,7 @@ export class Main extends React.Component {
                 {this.mainContent}
                 <PreviewCursor />
                 <ContextMenu />
-                {this.nodesMenu}
+                {this.nodesMenu()}
                 {this.miscButtons}
                 {this.workspaceMenu}
                 <InkingControl />
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6c8f3844a..2facd404f 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,7 +1,7 @@
 import * as GoldenLayout from "golden-layout";
 import 'golden-layout/src/css/goldenlayout-base.css';
 import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, observable, reaction, trace } from "mobx";
+import { action, observable, reaction, trace, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
 import Measure from "react-measure";
@@ -14,7 +14,7 @@ import { SubCollectionViewProps } from "./CollectionSubView";
 import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
 import { Transform } from '../../util/Transform';
 import { Doc, Opt, Field } from "../../../new_fields/Doc";
-import { Cast, NumCast } from "../../../new_fields/Types";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { DocServer } from "../../DocServer";
 import { listSpec } from "../../../new_fields/Schema";
@@ -87,6 +87,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     //
     @action
     public AddRightSplit(document: Doc, minimize: boolean = false) {
+        let docs = Cast(this.props.Document.data, listSpec(Doc));
+        if (docs) {
+            docs.push(document);
+        }
         let newItemStackConfig = {
             type: 'stack',
             content: [CollectionDockingView.makeDocumentConfig(document)]
@@ -122,7 +126,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
 
     setupGoldenLayout() {
-        var config = Cast(this.props.Document.data, "string", "");
+        var config = StrCast(this.props.Document.dockingConfig);
         if (config) {
             if (!this._goldenLayout) {
                 this._goldenLayout = new GoldenLayout(JSON.parse(config));
@@ -157,7 +161,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     componentDidMount: () => void = () => {
         if (this._containerRef.current) {
             reaction(
-                () => Cast(this.props.Document.data, "string", ""),
+                () => StrCast(this.props.Document.dockingConfig),
                 () => {
                     if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
                         setTimeout(() => this.setupGoldenLayout(), 1);
@@ -238,7 +242,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     @undoBatch
     stateChanged = () => {
         var json = JSON.stringify(this._goldenLayout.toConfig());
-        this.props.Document.data = json;
+        this.props.Document.dockingConfig = json;
         if (this.undohack && !this.hack) {
             this.undohack.end();
             this.undohack = undefined;
@@ -285,6 +289,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 if (tab.reactionDisposer) {
                     tab.reactionDisposer();
                 }
+                DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async f => runInAction(() => {
+                    if (f instanceof Doc) {
+                        let docs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc));
+                        docs && docs.map((d, i) => d[Id] === f[Id] && docs!.splice(i, 1));
+                        // bcz: this seems like it should work, but it only does occasionally -- usually I get -1
+                        // docs && docs.indexOf(f) !== -1 && docs.splice(docs.indexOf(f), 1);
+                    }
+                }));
                 tab.contentItem.remove();
             });
     }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 7ec9a8549..1a6f1121d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -12,6 +12,7 @@ import { Document, listSpec } from '../../../new_fields/Schema';
 import { Cast, StrCast, BoolCast } from '../../../new_fields/Types';
 import { Doc } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/RefField';
+import { Utils } from '../../../Utils';
 
 
 export interface TreeViewProps {
@@ -101,7 +102,9 @@ class TreeView extends React.Component<TreeViewProps> {
             if (!this._collapsed) {
                 bulletType = BulletType.Collapsible;
                 childElements = <ul>
-                    {children.map(value => <TreeView key={value[Id]} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
+                    {/* // bcz: should this work? 
+                     {children.map(value => <TreeView key={value[Id]} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)} */}
+                    {children.map(value => <TreeView key={Utils.GenerateGuid()} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
                 </ul >;
             }
             else bulletType = BulletType.Collapsed;
@@ -132,7 +135,9 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         let copyOnDrag = BoolCast(this.props.Document.copyDraggedItems, false);
         let childrenElement = !children ? (null) :
             (children.map(value =>
-                <TreeView document={value} key={value[Id]} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
+                //bcz: shouldn't this work?   - I think value[Id] is undefined sometimes
+                // <TreeView document={value} key={value[Id]} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
+                <TreeView document={value} key={Utils.GenerateGuid()} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
             );
 
         return (
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f58dc4a02..b35d68c4b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -198,7 +198,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         CollectionDockingView.Instance.AddRightSplit(kvp);
     }
     fullScreenClicked = (e: React.MouseEvent): void => {
-        const doc = Doc.MakeDelegate(FieldValue(this.Document.proto));
+        const doc = Doc.MakeDelegate(FieldValue(this.props.Document.proto));
+        // bcz .. should this work?  
+        // const doc = Doc.MakeDelegate(FieldValue(this.Document.proto));
         if (doc) {
             CollectionDockingView.Instance.OpenFullScreen(doc);
         }
-- 
cgit v1.2.3-70-g09d2


From 6d52cfb2a7b2b575f448e7d7c3f02a97afa0027b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 1 May 2019 22:38:49 -0400
Subject: fixed drag drop making source documents disappear.  improved TreeView
 collapsing.  Chnaged schemaView options placement

---
 src/client/util/DragManager.ts                        | 17 +++++++++++------
 src/client/views/collections/CollectionSchemaView.tsx |  2 +-
 src/client/views/collections/CollectionTreeView.tsx   |  4 ++--
 3 files changed, 14 insertions(+), 9 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index a149da52f..4acf609f4 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -301,15 +301,20 @@ export namespace DragManager {
 
     function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) {
         let removed = dragEles.map(dragEle => {
-            let parent = dragEle.parentElement;
-            if (parent) parent.removeChild(dragEle);
-            return [dragEle, parent];
+            // let parent = dragEle.parentElement;
+            // if (parent) parent.removeChild(dragEle);
+            let ret = [dragEle, dragEle.style.width, dragEle.style.height];
+            dragEle.style.width = "0";
+            dragEle.style.height = "0";
+            return ret;
         });
         const target = document.elementFromPoint(e.x, e.y);
         removed.map(r => {
-            let dragEle = r[0];
-            let parent = r[1];
-            if (parent && dragEle) parent.appendChild(dragEle);
+            let dragEle = r[0] as HTMLElement;
+            dragEle.style.width = r[1] as string;
+            dragEle.style.height = r[2] as string;
+            // let parent = r[1];
+            // if (parent && dragEle) parent.appendChild(dragEle);
         });
         if (target) {
             if (finishDrag) finishDrag(dragData);
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 1f26941c2..67784fa81 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -295,7 +295,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     get tableOptionsPanel() {
         return !this.props.active() ? (null) :
             (<Flyout
-                anchorPoint={anchorPoints.LEFT_TOP}
+                anchorPoint={anchorPoints.RIGHT_TOP}
                 content={<div>
                     <div id="schema-options-header"><h5><b>Options</b></h5></div>
                     <div id="options-flyout-div">
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 1a6f1121d..2a620a5e6 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -104,7 +104,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 childElements = <ul>
                     {/* // bcz: should this work? 
                      {children.map(value => <TreeView key={value[Id]} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)} */}
-                    {children.map(value => <TreeView key={Utils.GenerateGuid()} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
+                    {children.map(value => <TreeView key={value[Id] !== undefined ? value[Id] : Utils.GenerateGuid()} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
                 </ul >;
             }
             else bulletType = BulletType.Collapsed;
@@ -137,7 +137,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
             (children.map(value =>
                 //bcz: shouldn't this work?   - I think value[Id] is undefined sometimes
                 // <TreeView document={value} key={value[Id]} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
-                <TreeView document={value} key={Utils.GenerateGuid()} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
+                <TreeView document={value} key={value[Id] !== undefined ? value[Id] : Utils.GenerateGuid()} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
             );
 
         return (
-- 
cgit v1.2.3-70-g09d2


From be3719bbbede85b4dd099f436b4f7d3bade157cb Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 1 May 2019 23:49:23 -0400
Subject: Fixed treeview stuff

---
 src/client/views/collections/CollectionSubView.tsx  | 6 ++++--
 src/client/views/collections/CollectionTreeView.tsx | 9 ++-------
 2 files changed, 6 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 2c2d74302..bcbde24f0 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -13,7 +13,7 @@ import { CollectionVideoView } from "./CollectionVideoView";
 import { Doc, Opt } from "../../../new_fields/Doc";
 import { DocComponent } from "../DocComponent";
 import { listSpec } from "../../../new_fields/Schema";
-import { Cast, PromiseValue } from "../../../new_fields/Types";
+import { Cast, PromiseValue, FieldValue } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { DocServer } from "../../DocServer";
 import { ObjectField } from "../../../new_fields/ObjectField";
@@ -48,7 +48,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         }
 
         get children() {
-            return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc));
+            //TODO tfs: This might not be what we want?
+            //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue)
+            return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(doc => FieldValue(doc));
         }
 
         @action
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 2a620a5e6..f148c2b2f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -102,9 +102,7 @@ class TreeView extends React.Component<TreeViewProps> {
             if (!this._collapsed) {
                 bulletType = BulletType.Collapsible;
                 childElements = <ul>
-                    {/* // bcz: should this work? 
-                     {children.map(value => <TreeView key={value[Id]} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)} */}
-                    {children.map(value => <TreeView key={value[Id] !== undefined ? value[Id] : Utils.GenerateGuid()} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
+                    {children.map(value => <TreeView key={value[Id]} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
                 </ul >;
             }
             else bulletType = BulletType.Collapsed;
@@ -135,10 +133,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         let copyOnDrag = BoolCast(this.props.Document.copyDraggedItems, false);
         let childrenElement = !children ? (null) :
             (children.map(value =>
-                //bcz: shouldn't this work?   - I think value[Id] is undefined sometimes
-                // <TreeView document={value} key={value[Id]} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
-                <TreeView document={value} key={value[Id] !== undefined ? value[Id] : Utils.GenerateGuid()} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
-            );
+                <TreeView document={value} key={value[Id]} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />));
 
         return (
             <div id="body" className="collectionTreeView-dropTarget"
-- 
cgit v1.2.3-70-g09d2


From 5f8f133040918713ace577cfe82f38254ea07964 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 2 May 2019 12:19:31 -0400
Subject: library and workspace stuff.

---
 src/client/views/Main.tsx                          | 28 ++-----
 src/client/views/TemplateMenu.tsx                  |  7 +-
 .../views/collections/CollectionDockingView.tsx    | 27 ++-----
 .../views/collections/CollectionTreeView.scss      |  1 -
 .../views/collections/CollectionTreeView.tsx       | 57 ++++++++++----
 src/client/views/collections/CollectionView.tsx    |  1 +
 src/client/views/nodes/DocumentView.tsx            | 10 +--
 .../authentication/controllers/WorkspacesMenu.css  |  3 -
 .../authentication/controllers/WorkspacesMenu.tsx  | 90 ----------------------
 9 files changed, 60 insertions(+), 164 deletions(-)
 delete mode 100644 src/server/authentication/controllers/WorkspacesMenu.css
 delete mode 100644 src/server/authentication/controllers/WorkspacesMenu.tsx

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

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 4e918221c..b2ca4a196 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -100,8 +100,7 @@ export class Main extends React.Component {
     onHistory = () => {
         if (window.location.pathname !== RouteStore.home) {
             let pathname = window.location.pathname.split("/");
-            CurrentUserUtils.MainDocId = pathname[pathname.length - 1];
-            DocServer.GetRefField(CurrentUserUtils.MainDocId).then(action((field: Opt<Field>) => {
+            DocServer.GetRefField(pathname[pathname.length - 1]).then(action((field: Opt<Field>) => {
                 if (field instanceof Doc) {
                     this.openWorkspace(field, true);
                 }
@@ -132,7 +131,6 @@ export class Main extends React.Component {
         if (!CurrentUserUtils.MainDocId) {
             const doc = await Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc);
             if (doc) {
-                CurrentUserUtils.MainDocId = doc[Id];
                 this.openWorkspace(doc);
             } else {
                 this.createNewWorkspace();
@@ -148,11 +146,12 @@ export class Main extends React.Component {
     createNewWorkspace = async (id?: string) => {
         const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
         if (list) {
+            let libraryDoc = Docs.TreeDocument([CurrentUserUtils.UserDocument], { x: 0, y: 400, title: `Library: ${CurrentUserUtils.email} ${list.length + 1}` });
+            libraryDoc.excludeFromLibrary = true;
             let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, title: `WS collection ${list.length + 1}` });
-            var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc)] }] };
-            let mainDoc = Docs.DockDocument([freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` });
+            var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(libraryDoc, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
+            let mainDoc = Docs.DockDocument([libraryDoc, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` });
             list.push(mainDoc);
-            CurrentUserUtils.MainDocId = mainDoc[Id];
             // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
             setTimeout(() => {
                 this.openWorkspace(mainDoc);
@@ -164,6 +163,7 @@ export class Main extends React.Component {
 
     @action
     openWorkspace = async (doc: Doc, fromHistory = false) => {
+        CurrentUserUtils.MainDocId = doc[Id];
         this.mainContainer = doc;
         fromHistory || window.history.pushState(null, StrCast(doc.title), "/doc/" + doc[Id]);
         const col = await Cast(CurrentUserUtils.UserDocument.optionalRightCollection, Doc);
@@ -259,9 +259,7 @@ export class Main extends React.Component {
     /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
     @computed
     get miscButtons() {
-        let workspacesRef = React.createRef<HTMLDivElement>();
         let logoutRef = React.createRef<HTMLDivElement>();
-        let toggleWorkspaces = () => runInAction(() => this._workspacesShown = !this._workspacesShown);
 
         return [
             <button className="clear-db-button" key="clear-db" onClick={DocServer.DeleteDatabase}>Clear Database</button>,
@@ -270,24 +268,11 @@ export class Main extends React.Component {
                 <button className="toolbar-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button>
                 <button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button>
             </div >,
-            <div className="main-buttonDiv" key="workspaces" style={{ top: '34px', left: '2px', position: 'absolute' }} ref={workspacesRef}>
-                <button onClick={toggleWorkspaces}>Workspaces</button></div>,
             <div className="main-buttonDiv" key="logout" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}>
                 <button onClick={() => request.get(DocServer.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
         ];
     }
 
-    @computed
-    get workspaceMenu() {
-        let areWorkspacesShown = () => this._workspacesShown;
-        let toggleWorkspaces = () => runInAction(() => this._workspacesShown = !this._workspacesShown);
-        let workspaces = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
-        return (!workspaces || !this.mainContainer) ? (null) :
-            <WorkspacesMenu active={this.mainContainer} open={this.openWorkspace}
-                new={this.createNewWorkspace} allWorkspaces={workspaces}
-                isShown={areWorkspacesShown} toggle={toggleWorkspaces} />;
-    }
-
     render() {
         return (
             <div id="main-div">
@@ -297,7 +282,6 @@ export class Main extends React.Component {
                 <ContextMenu />
                 {this.nodesMenu()}
                 {this.miscButtons}
-                {this.workspaceMenu}
                 <InkingControl />
                 <MainOverlayTextBox />
             </div>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 7be846e05..f29d9c8a1 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -59,12 +59,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     }
 
     render() {
-        trace();
         let templateMenu: Array<JSX.Element> = [];
-        this.props.templates.forEach((checked, template) => {
-            console.log("checked + " + checked + " " + this.props.templates.get(template));
-            templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />);
-        });
+        this.props.templates.forEach((checked, template) =>
+            templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
 
         return (
             <div className="templating-menu" >
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 014773ab6..cfb1aef7d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -19,15 +19,17 @@ import { List } from "../../../new_fields/List";
 import { DocServer } from "../../DocServer";
 import { listSpec } from "../../../new_fields/Schema";
 import { Id, FieldId } from "../../../new_fields/RefField";
+import { faSignInAlt } from "@fortawesome/free-solid-svg-icons";
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
     public static Instance: CollectionDockingView;
-    public static makeDocumentConfig(document: Doc) {
+    public static makeDocumentConfig(document: Doc, width?: number) {
         return {
             type: 'react-component',
             component: 'DocumentFrameRenderer',
             title: document.title,
+            width: width,
             props: {
                 documentId: document[Id],
                 //collectionDockingView: CollectionDockingView.Instance
@@ -37,7 +39,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     private _goldenLayout: any = null;
     private _containerRef = React.createRef<HTMLDivElement>();
-    private _fullScreen: any = null;
     private _flush: boolean = false;
     private _ignoreStateChange = "";
 
@@ -67,20 +68,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         this._goldenLayout.root.contentItems[0].addChild(docconfig);
         docconfig.callDownwards('_$init');
         this._goldenLayout._$maximiseItem(docconfig);
-        this._fullScreen = docconfig;
         this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         this.stateChanged();
     }
-    @action
-    public CloseFullScreen() {
-        if (this._fullScreen) {
-            this._goldenLayout._$minimiseItem(this._fullScreen);
-            this._goldenLayout.root.contentItems[0].removeChild(this._fullScreen);
-            this._fullScreen = null;
-            this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
-            this.stateChanged();
-        }
-    }
 
     //
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
@@ -330,7 +320,6 @@ interface DockedFrameProps {
 }
 @observer
 export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
-
     _mainCont = React.createRef<HTMLDivElement>();
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
@@ -347,10 +336,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         const nativeH = this.nativeHeight();
         const nativeW = this.nativeWidth();
         let wscale = this._panelWidth / nativeW;
-        if (wscale * nativeH > this._panelHeight) {
-            return this._panelHeight / nativeH;
-        }
-        return wscale;
+        return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
     }
 
     ScreenToLocalTransform = () => {
@@ -364,6 +350,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
 
     get content() {
+        if (!this._document)
+            return (null);
         return (
             <div className="collectionDockingView-content" ref={this._mainCont}
                 style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
@@ -385,9 +373,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
 
     render() {
+        let theContent = this.content;
         return !this._document ? (null) :
             <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
-                {({ measureRef }) => <div ref={measureRef}>  {this.content} </div>}
+                {({ measureRef }) => <div ref={measureRef}>  {theContent} </div>}
             </Measure>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 95df5edb9..ecb5f78bb 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -41,7 +41,6 @@
 
     .coll-title {
         font-size: 24px;
-        margin-bottom: 20px;
     }
 
     .docContainer {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f148c2b2f..0520e0f88 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -9,10 +9,15 @@ import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import React = require("react");
 import { Document, listSpec } from '../../../new_fields/Schema';
-import { Cast, StrCast, BoolCast } from '../../../new_fields/Types';
+import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types';
 import { Doc } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/RefField';
 import { Utils } from '../../../Utils';
+import { JSXElement } from 'babel-types';
+import { ContextMenu } from '../ContextMenu';
+import { undoBatch } from '../../util/UndoManager';
+import { Main } from '../Main';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 
 
 export interface TreeViewProps {
@@ -42,11 +47,14 @@ class TreeView extends React.Component<TreeViewProps> {
 
     delete = () => this.props.deleteDoc(this.props.document);
 
+    get children() {
+        return Cast(this.props.document.data, listSpec(Doc), []).filter(doc => FieldValue(doc));
+    }
+
     @action
     remove = (document: Document) => {
-        var children = Cast(this.props.document.data, listSpec(Doc));
-        if (children) {
-            children.splice(children.indexOf(document), 1);
+        if (this.children) {
+            this.children.splice(this.children.indexOf(document), 1);
         }
     }
 
@@ -94,27 +102,38 @@ class TreeView extends React.Component<TreeViewProps> {
             </div >);
     }
 
+    onWorkspaceContextMenu = (e: React.MouseEvent): void => {
+        if (!e.isPropagationStopped() && this.props.document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+            if (!ContextMenu.Instance.getItems().some(item => item.description === "Open as Workspace")) {
+                ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => Main.Instance.openWorkspace(this.props.document)) });
+            }
+        }
+    }
     render() {
         let bulletType = BulletType.List;
-        let childElements: JSX.Element | undefined = undefined;
+        let contentElement: JSX.Element | null = (null);
         var children = Cast(this.props.document.data, listSpec(Doc));
         if (children) { // add children for a collection
             if (!this._collapsed) {
                 bulletType = BulletType.Collapsible;
-                childElements = <ul>
-                    {children.map(value => <TreeView key={value[Id]} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
+                contentElement = <ul>
+                    {TreeView.GetChildElements(children, this.remove, this.move, this.props.copyOnDrag)}
                 </ul >;
             }
             else bulletType = BulletType.Collapsed;
         }
-        return <div className="treeViewItem-container" >
+        return <div className="treeViewItem-container" onContextMenu={this.onWorkspaceContextMenu} >
             <li className="collection-child">
                 {this.renderBullet(bulletType)}
                 {this.renderTitle()}
-                {childElements ? childElements : (null)}
+                {contentElement}
             </li>
         </div>;
     }
+    public static GetChildElements(docs: Doc[], remove: ((doc: Doc) => void), move: DragManager.MoveFunction, copyOnDrag: boolean) {
+        return docs.filter(child => !child.excludeFromLibrary).map(child =>
+            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} copyOnDrag={copyOnDrag} />);
+    }
 }
 
 @observer
@@ -128,21 +147,30 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         }
     }
 
+    onContextMenu = (e: React.MouseEvent): void => {
+        if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+            ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => Main.Instance.createNewWorkspace()) });
+        }
+    }
     render() {
         const children = this.children;
         let copyOnDrag = BoolCast(this.props.Document.copyDraggedItems, false);
-        let childrenElement = !children ? (null) :
-            (children.map(value =>
-                <TreeView document={value} key={value[Id]} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />));
+        if (!children) {
+            return (null);
+        }
+        let testForLibrary = children && children.length === 1 && children[0] === CurrentUserUtils.UserDocument;
+        var subchildren = testForLibrary ? Cast(children[0].data, listSpec(Doc), children) : children;
+        let childElements = TreeView.GetChildElements(subchildren, this.remove, this.props.moveDocument, copyOnDrag);
 
         return (
             <div id="body" className="collectionTreeView-dropTarget"
                 style={{ borderRadius: "inherit" }}
+                onContextMenu={this.onContextMenu}
                 onWheel={(e: React.WheelEvent) => e.stopPropagation()}
                 onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
                 <div className="coll-title">
                     <EditableView
-                        contents={this.props.Document.Title}
+                        contents={this.props.Document.title}
                         display={"inline"}
                         height={72}
                         GetValue={() => StrCast(this.props.Document.title)}
@@ -151,9 +179,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                             return true;
                         }} />
                 </div>
-                <hr />
                 <ul className="no-indent">
-                    {childrenElement}
+                    {childElements}
                 </ul>
             </div >
         );
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c2049a09a..8c1442d38 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -11,6 +11,7 @@ import { observer } from 'mobx-react';
 import { undoBatch } from '../../util/UndoManager';
 import { trace } from 'mobx';
 import { Id } from '../../../new_fields/RefField';
+import { Main } from '../Main';
 
 @observer
 export class CollectionView extends React.Component<FieldViewProps> {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5588fa1ac..86d34725d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -205,16 +205,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             CollectionDockingView.Instance.OpenFullScreen(doc);
         }
         ContextMenu.Instance.clearItems();
-        ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
-        ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
-    }
-    closeFullScreenClicked = (e: React.MouseEvent): void => {
-        CollectionDockingView.Instance.CloseFullScreen();
-        ContextMenu.Instance.clearItems();
-        ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked });
-        ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+        SelectionManager.DeselectAll();
     }
-
     @undoBatch
     @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
diff --git a/src/server/authentication/controllers/WorkspacesMenu.css b/src/server/authentication/controllers/WorkspacesMenu.css
deleted file mode 100644
index b89039965..000000000
--- a/src/server/authentication/controllers/WorkspacesMenu.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.ids:hover {
-    color: darkblue;
-}
\ No newline at end of file
diff --git a/src/server/authentication/controllers/WorkspacesMenu.tsx b/src/server/authentication/controllers/WorkspacesMenu.tsx
deleted file mode 100644
index 91756315d..000000000
--- a/src/server/authentication/controllers/WorkspacesMenu.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import * as React from 'react';
-import { observable, action, configure, reaction, computed, ObservableMap, runInAction } from 'mobx';
-import { observer } from "mobx-react";
-import './WorkspacesMenu.css';
-import { EditableView } from '../../../client/views/EditableView';
-import { Doc } from '../../../new_fields/Doc';
-import { StrCast } from '../../../new_fields/Types';
-import { Id } from '../../../new_fields/RefField';
-
-export interface WorkspaceMenuProps {
-    active: Doc | undefined;
-    open: (workspace: Doc) => void;
-    new: () => void;
-    allWorkspaces: Doc[];
-    isShown: () => boolean;
-    toggle: () => void;
-}
-
-@observer
-export class WorkspacesMenu extends React.Component<WorkspaceMenuProps> {
-    constructor(props: WorkspaceMenuProps) {
-        super(props);
-        this.addNewWorkspace = this.addNewWorkspace.bind(this);
-    }
-
-    @action
-    addNewWorkspace() {
-        this.props.new();
-        this.props.toggle();
-    }
-
-    render() {
-        return (
-            <div
-                style={{
-                    width: "auto",
-                    maxHeight: '200px',
-                    overflow: 'scroll',
-                    borderRadius: 5,
-                    position: "absolute",
-                    top: 78,
-                    left: this.props.isShown() ? 11 : -500,
-                    background: "white",
-                    border: "black solid 2px",
-                    transition: "all 1s ease",
-                    zIndex: 15,
-                    padding: 10,
-                    paddingRight: 12,
-                }}>
-                <img
-                    src="https://bit.ly/2IBBkxk"
-                    style={{
-                        width: 20,
-                        height: 20,
-                        marginTop: 3,
-                        marginLeft: 3,
-                        marginBottom: 3,
-                        cursor: "grab"
-                    }}
-                    onClick={this.addNewWorkspace}
-                />
-                {this.props.allWorkspaces.map((s, i) =>
-                    <div
-                        key={s[Id]}
-                        onContextMenu={(e) => {
-                            e.preventDefault();
-                            this.props.open(s);
-                        }}
-                        style={{
-                            marginTop: 10,
-                            color: s === this.props.active ? "red" : "black"
-                        }}
-                    >
-                        <span>{i + 1} - </span>
-                        <EditableView
-                            display={"inline"}
-                            GetValue={() => StrCast(s.title)}
-                            SetValue={(title: string): boolean => {
-                                s.title = title;
-                                return true;
-                            }}
-                            contents={s.Title}
-                            height={20}
-                        />
-                    </div>
-                )}
-            </div>
-        );
-    }
-}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 1ece3904b6b3fbf4ddde29e93e31a30f3898e720 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 2 May 2019 13:20:27 -0400
Subject: cleaned up drag code - userDropAction takes precedence over
 dropAction. default is alias

---
 src/client/documents/Documents.ts                  |  3 ++-
 src/client/util/DragManager.ts                     | 19 ++++++++++------
 src/client/views/DocumentDecorations.tsx           |  1 -
 src/client/views/EditableView.tsx                  |  2 +-
 src/client/views/Main.tsx                          | 14 +++++-------
 src/client/views/collections/CollectionSubView.tsx |  6 ++---
 .../views/collections/CollectionTreeView.scss      |  3 ++-
 .../views/collections/CollectionTreeView.tsx       | 26 +++++++++++-----------
 src/client/views/nodes/DocumentView.tsx            | 12 +++++-----
 src/client/views/nodes/FormattedTextBox.tsx        |  6 +++--
 10 files changed, 49 insertions(+), 43 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2de389a3c..8706359e4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -32,6 +32,7 @@ import { IconField } from "../../new_fields/IconField";
 import { listSpec } from "../../new_fields/Schema";
 import { DocServer } from "../DocServer";
 import { StrokeData, InkField } from "../../new_fields/InkField";
+import { dropActionType } from "../util/DragManager";
 
 export interface DocumentOptions {
     x?: number;
@@ -51,7 +52,7 @@ export interface DocumentOptions {
     templates?: List<string>;
     viewType?: number;
     backgroundColor?: string;
-    copyDraggedItems?: boolean;
+    dropAction?: dropActionType;
     backgroundLayout?: string;
     curPage?: number;
     documentText?: string;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4acf609f4..a3dbe6e43 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,14 +1,14 @@
 import { action } from "mobx";
 import { emptyFunction } from "../../Utils";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import { DocumentDecorations } from "../views/DocumentDecorations";
 import * as globalCssVariables from "../views/globalCssVariables.scss";
 import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
 import { Doc } from "../../new_fields/Doc";
 import { Cast } from "../../new_fields/Types";
 import { listSpec } from "../../new_fields/Schema";
 
-export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Doc, moveFunc?: DragManager.MoveFunction, copyOnDrop: boolean = false) {
+export type dropActionType = "alias" | "copy" | undefined;
+export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Doc, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType) {
     let onRowMove = action((e: PointerEvent): void => {
         e.stopPropagation();
         e.preventDefault();
@@ -16,7 +16,7 @@ export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
         document.removeEventListener("pointermove", onRowMove);
         document.removeEventListener('pointerup', onRowUp);
         var dragData = new DragManager.DocumentDragData([docFunc()]);
-        dragData.copyOnDrop = copyOnDrop;
+        dragData.dropAction = dropAction;
         dragData.moveDocument = moveFunc;
         DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
     });
@@ -146,15 +146,20 @@ export namespace DragManager {
         droppedDocuments: Doc[];
         xOffset: number;
         yOffset: number;
-        aliasOnDrop?: boolean;
-        copyOnDrop?: boolean;
+        dropAction: dropActionType;
+        userDropAction: dropActionType;
         moveDocument?: MoveFunction;
         [id: string]: any;
     }
 
     export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
         StartDrag(eles, dragData, downX, downY, options,
-            (dropData: { [id: string]: any }) => (dropData.droppedDocuments = dragData.aliasOnDrop ? dragData.draggedDocuments.map(d => Doc.MakeAlias(d)) : dragData.copyOnDrop ? dragData.draggedDocuments.map(d => Doc.MakeCopy(d, true)) : dragData.draggedDocuments));
+            (dropData: { [id: string]: any }) =>
+                (dropData.droppedDocuments = dragData.userDropAction == "alias" || (!dragData.userDropAction && dragData.dropAction == "alias") ?
+                    dragData.draggedDocuments.map(d => Doc.MakeAlias(d)) :
+                    dragData.userDropAction == "copy" || (!dragData.userDropAction && dragData.dropAction == "copy") ?
+                        dragData.draggedDocuments.map(d => Doc.MakeCopy(d, true)) :
+                        dragData.draggedDocuments));
     }
 
     export class LinkDragData {
@@ -252,7 +257,7 @@ export namespace DragManager {
             e.stopPropagation();
             e.preventDefault();
             if (dragData instanceof DocumentDragData) {
-                dragData.aliasOnDrop = e.ctrlKey || e.altKey;
+                dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
             }
             if (e.shiftKey) {
                 AbortDrag();
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 7dcd71495..d07adcb71 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -144,7 +144,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
         dragData.xOffset = xoff;
         dragData.yOffset = yoff;
-        dragData.aliasOnDrop = false;
         dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument;
         this.Interacting = true;
         this._hidden = true;
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 2f17c6c51..73467eb9d 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -60,7 +60,7 @@ export class EditableView extends React.Component<EditableProps> {
             return (
                 <div className="editableView-container-editing" style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
                     onClick={action(() => this.editing = true)} >
-                    {this.props.contents}
+                    <span>{this.props.contents}</span>
                 </div>
             );
         }
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index b2ca4a196..a07a2d5b1 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -8,9 +8,7 @@ import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import Measure from 'react-measure';
 import * as request from 'request';
-import { WorkspacesMenu } from '../../server/authentication/controllers/WorkspacesMenu';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { MessageStore } from '../../server/Message';
 import { RouteStore } from '../../server/RouteStore';
 import { emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils';
 import { Docs } from '../documents/Documents';
@@ -86,11 +84,11 @@ export class Main extends React.Component {
         this.initEventListeners();
         this.initAuthenticationRouters();
 
-        try {
-            this.initializeNorthstar();
-        } catch (e) {
+        // try {
+        //     this.initializeNorthstar();
+        // } catch (e) {
 
-        }
+        // }
     }
 
     componentDidMount() { window.onpopstate = this.onHistory; }
@@ -219,8 +217,8 @@ export class Main extends React.Component {
         let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
         let addColNode = action(() => Docs.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
         let addSchemaNode = action(() => Docs.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
-        let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, copyDraggedItems: true }));
-        // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true }));
+        let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" }));
+        // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy"  }));
         let addVideoNode = action(() => Docs.VideoDocument(videourl, { width: 200, title: "video node" }));
         let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
         let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index bcbde24f0..a59bc196e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -83,13 +83,13 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         @action
         protected drop(e: Event, de: DragManager.DropEvent): boolean {
             if (de.data instanceof DragManager.DocumentDragData) {
-                if (de.data.aliasOnDrop || de.data.copyOnDrop) {
+                if (de.data.dropAction || de.data.userDropAction) {
                     ["width", "height", "curPage"].map(key =>
                         de.data.draggedDocuments.map((draggedDocument: Doc, i: number) =>
                             PromiseValue(Cast(draggedDocument[key], "number")).then(f => f && (de.data.droppedDocuments[i][key] = f))));
                 }
                 let added = false;
-                if (de.data.aliasOnDrop || de.data.copyOnDrop) {
+                if (de.data.dropAction || de.data.userDropAction) {
                     added = de.data.droppedDocuments.reduce((added: boolean, d) => {
                         let moved = this.props.addDocument(d);
                         return moved || added;
@@ -129,7 +129,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             }
             if (type.indexOf("excel") !== -1) {
                 ctor = Docs.DBDocument;
-                options.copyDraggedItems = true;
+                options.dropAction = "copy";
             }
             if (type.indexOf("html") !== -1) {
                 if (path.includes('localhost')) {
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index ecb5f78bb..d00785879 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -45,7 +45,8 @@
 
     .docContainer {
         margin-left: 10px;
-        display: inline-table;
+        display: block;
+        width: max-content;
     }
 
     .docContainer:hover {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 0520e0f88..037703c46 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -3,7 +3,7 @@ import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-s
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import { DragManager, SetupDrag } from "../../util/DragManager";
+import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
 import { EditableView } from "../EditableView";
 import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
@@ -12,8 +12,6 @@ import { Document, listSpec } from '../../../new_fields/Schema';
 import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types';
 import { Doc } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/RefField';
-import { Utils } from '../../../Utils';
-import { JSXElement } from 'babel-types';
 import { ContextMenu } from '../ContextMenu';
 import { undoBatch } from '../../util/UndoManager';
 import { Main } from '../Main';
@@ -24,7 +22,7 @@ export interface TreeViewProps {
     document: Doc;
     deleteDoc: (doc: Doc) => void;
     moveDocument: DragManager.MoveFunction;
-    copyOnDrag: boolean;
+    dropAction: "alias" | "copy" | undefined;
 }
 
 export enum BulletType {
@@ -83,7 +81,7 @@ class TreeView extends React.Component<TreeViewProps> {
      */
     renderTitle() {
         let reference = React.createRef<HTMLDivElement>();
-        let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag);
+        let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.dropAction);
         let editableView = (titleString: string) =>
             (<EditableView
                 display={"inline"}
@@ -91,14 +89,15 @@ class TreeView extends React.Component<TreeViewProps> {
                 height={36}
                 GetValue={() => StrCast(this.props.document.title)}
                 SetValue={(value: string) => {
-                    this.props.document.title = value;
+                    let target = this.props.document.proto ? this.props.document.proto : this.props.document;
+                    target.title = value;
                     return true;
                 }}
             />);
         return (
             <div className="docContainer" ref={reference} onPointerDown={onItemDown}>
                 {editableView(StrCast(this.props.document.title))}
-                <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
+                {/* <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div> */}
             </div >);
     }
 
@@ -117,7 +116,7 @@ class TreeView extends React.Component<TreeViewProps> {
             if (!this._collapsed) {
                 bulletType = BulletType.Collapsible;
                 contentElement = <ul>
-                    {TreeView.GetChildElements(children, this.remove, this.move, this.props.copyOnDrag)}
+                    {TreeView.GetChildElements(children, this.remove, this.move, this.props.dropAction)}
                 </ul >;
             }
             else bulletType = BulletType.Collapsed;
@@ -130,9 +129,9 @@ class TreeView extends React.Component<TreeViewProps> {
             </li>
         </div>;
     }
-    public static GetChildElements(docs: Doc[], remove: ((doc: Doc) => void), move: DragManager.MoveFunction, copyOnDrag: boolean) {
+    public static GetChildElements(docs: Doc[], remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
         return docs.filter(child => !child.excludeFromLibrary).map(child =>
-            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} copyOnDrag={copyOnDrag} />);
+            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
     }
 }
 
@@ -154,13 +153,13 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     }
     render() {
         const children = this.children;
-        let copyOnDrag = BoolCast(this.props.Document.copyDraggedItems, false);
+        let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType;
         if (!children) {
             return (null);
         }
         let testForLibrary = children && children.length === 1 && children[0] === CurrentUserUtils.UserDocument;
         var subchildren = testForLibrary ? Cast(children[0].data, listSpec(Doc), children) : children;
-        let childElements = TreeView.GetChildElements(subchildren, this.remove, this.props.moveDocument, copyOnDrag);
+        let childElements = TreeView.GetChildElements(subchildren, this.remove, this.props.moveDocument, dropAction);
 
         return (
             <div id="body" className="collectionTreeView-dropTarget"
@@ -175,7 +174,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                         height={72}
                         GetValue={() => StrCast(this.props.Document.title)}
                         SetValue={(value: string) => {
-                            this.props.Document.title = value;
+                            let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+                            target.title = value;
                             return true;
                         }} />
                 </div>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 86d34725d..b151657e1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
 import { emptyFunction, Utils } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
-import { DragManager } from "../../util/DragManager";
+import { DragManager, dropActionType } from "../../util/DragManager";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -129,12 +129,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         e.stopPropagation();
     }
 
-    startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) {
+    startDragging(x: number, y: number, dropAction: dropActionType) {
         if (this._mainCont.current) {
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
             let dragData = new DragManager.DocumentDragData([this.props.Document]);
             const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
-            dragData.aliasOnDrop = dropAliasOfDraggedDoc;
+            dragData.dropAction = dropAction;
             dragData.xOffset = xoff;
             dragData.yOffset = yoff;
             dragData.moveDocument = this.props.moveDocument;
@@ -142,7 +142,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 handlers: {
                     dragComplete: action(emptyFunction)
                 },
-                hideSource: !dropAliasOfDraggedDoc
+                hideSource: !dropAction
             });
         }
     }
@@ -162,7 +162,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         if (e.shiftKey && e.buttons === 1) {
             if (this.props.isTopMost) {
-                this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
+                this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined);
             } else {
                 CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
             }
@@ -180,7 +180,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 document.removeEventListener("pointermove", this.onPointerMove);
                 document.removeEventListener("pointerup", this.onPointerUp);
                 if (!e.altKey && !this.topMost && (!CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 1) || (CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 2)) {
-                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey);
+                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined);
                 }
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7f269a505..1d362bdaa 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -89,7 +89,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             if (title && title.startsWith("-") && this._editorView) {
                 let str = this._editorView.state.doc.textContent;
                 let titlestr = str.substr(0, Math.min(40, str.length));
-                this.props.Document.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+                let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+                target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
             };
         }
     }
@@ -275,7 +276,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (StrCast(this.props.Document.title).startsWith("-") && this._editorView) {
             let str = this._editorView.state.doc.textContent;
             let titlestr = str.substr(0, Math.min(40, str.length));
-            this.props.Document.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+            let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+            target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
         }
     }
     render() {
-- 
cgit v1.2.3-70-g09d2


From 7dcd9dac57f848271ef6fa96f66064382331ee13 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 2 May 2019 14:17:15 -0400
Subject: added some hover highlighting.

---
 .../views/collections/CollectionTreeView.tsx       | 28 ++++++++++++++++------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  9 ++++++-
 src/client/views/nodes/DocumentView.tsx            |  5 ++--
 3 files changed, 32 insertions(+), 10 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 037703c46..3c8cfcffd 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -46,13 +46,14 @@ class TreeView extends React.Component<TreeViewProps> {
     delete = () => this.props.deleteDoc(this.props.document);
 
     get children() {
-        return Cast(this.props.document.data, listSpec(Doc), []).filter(doc => FieldValue(doc));
+        return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed?    .filter(doc => FieldValue(doc));
     }
 
     @action
     remove = (document: Document) => {
-        if (this.children) {
-            this.children.splice(this.children.indexOf(document), 1);
+        let children = Cast(this.props.document.data, listSpec(Doc), []);
+        if (children) {
+            children.splice(children.indexOf(document), 1);
         }
     }
 
@@ -106,8 +107,15 @@ class TreeView extends React.Component<TreeViewProps> {
             if (!ContextMenu.Instance.getItems().some(item => item.description === "Open as Workspace")) {
                 ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => Main.Instance.openWorkspace(this.props.document)) });
             }
+            if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) {
+                ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
+            }
         }
     }
+
+    onPointerEnter = (e: React.PointerEvent): void => { this.props.document.libraryBrush = true; }
+    onPointerLeave = (e: React.PointerEvent): void => { this.props.document.libraryBrush = false; }
+
     render() {
         let bulletType = BulletType.List;
         let contentElement: JSX.Element | null = (null);
@@ -121,7 +129,12 @@ class TreeView extends React.Component<TreeViewProps> {
             }
             else bulletType = BulletType.Collapsed;
         }
-        return <div className="treeViewItem-container" onContextMenu={this.onWorkspaceContextMenu} >
+        return <div className="treeViewItem-container"
+            style={{
+                background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0"
+            }}
+            onContextMenu={this.onWorkspaceContextMenu}
+            onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
             <li className="collection-child">
                 {this.renderBullet(bulletType)}
                 {this.renderTitle()}
@@ -137,19 +150,20 @@ class TreeView extends React.Component<TreeViewProps> {
 
 @observer
 export class CollectionTreeView extends CollectionSubView(Document) {
-
     @action
     remove = (document: Document) => {
-        const children = this.children;
+        let children = Cast(this.props.Document.data, listSpec(Doc), []);
         if (children) {
             children.splice(children.indexOf(document), 1);
         }
     }
-
     onContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => Main.Instance.createNewWorkspace()) });
         }
+        if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) {
+            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.remove(this.props.Document)) });
+        }
     }
     render() {
         const children = this.children;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 27928d00b..09f9c8516 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -6,7 +6,7 @@ import "./DocumentView.scss";
 import React = require("react");
 import { DocComponent } from "../DocComponent";
 import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { FieldValue, Cast, NumCast } from "../../../new_fields/Types";
+import { FieldValue, Cast, NumCast, BoolCast } from "../../../new_fields/Types";
 import { OmitKeys, Utils } from "../../../Utils";
 import { SelectionManager } from "../../util/SelectionManager";
 import { matchedData } from "express-validator/filter";
@@ -155,6 +155,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         }
     }
 
+    onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; }
+    onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; }
+
     borderRounding = () => {
         let br = NumCast(this.props.Document.borderRounding);
         return br >= 0 ? br :
@@ -180,8 +183,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return (
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
                 onPointerDown={this.onPointerDown}
+                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
                 onClick={this.onClick}
                 style={{
+                    outlineColor: "black",
+                    outlineStyle: "dashed",
+                    outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ? `${0.5 / this.contentScaling()}px` : "0px",
                     opacity: zoomFade,
                     borderRadius: `${this.borderRounding()}px`,
                     transformOrigin: "left top",
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b151657e1..3c8804415 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -19,7 +19,7 @@ import React = require("react");
 import { Opt, Doc } from "../../../new_fields/Doc";
 import { DocComponent } from "../DocComponent";
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { FieldValue, StrCast } from "../../../new_fields/Types";
+import { FieldValue, StrCast, BoolCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
@@ -268,13 +268,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])) });
         ContextMenu.Instance.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]) });
         //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
-        ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked });
+        ContextMenu.Instance.addItem({ description: "Delete" + this.props.Document.title, event: this.deleteClicked });
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
         if (!SelectionManager.IsSelected(this)) {
             SelectionManager.SelectDoc(this, false);
         }
     }
 
+
     isSelected = () => SelectionManager.IsSelected(this);
     select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed);
 
-- 
cgit v1.2.3-70-g09d2


From a79bda7e5c7e77e398a33b8262f861155c5f1462 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 2 May 2019 14:34:33 -0400
Subject: fixed some issues with contextmenus for tree view

---
 src/client/views/collections/CollectionTreeView.tsx | 18 ++++++++++--------
 src/client/views/nodes/DocumentView.tsx             |  2 +-
 2 files changed, 11 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 3c8cfcffd..48b226615 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -16,6 +16,8 @@ import { ContextMenu } from '../ContextMenu';
 import { undoBatch } from '../../util/UndoManager';
 import { Main } from '../Main';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { CollectionDockingView } from './CollectionDockingView';
+import { DocumentManager } from '../../util/DocumentManager';
 
 
 export interface TreeViewProps {
@@ -104,12 +106,14 @@ class TreeView extends React.Component<TreeViewProps> {
 
     onWorkspaceContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-            if (!ContextMenu.Instance.getItems().some(item => item.description === "Open as Workspace")) {
-                ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => Main.Instance.openWorkspace(this.props.document)) });
-            }
-            if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) {
-                ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
+            ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => Main.Instance.openWorkspace(this.props.document)) });
+            ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.document) });
+            if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
+                ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
             }
+            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
+            ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+            e.stopPropagation();
         }
     }
 
@@ -130,9 +134,7 @@ class TreeView extends React.Component<TreeViewProps> {
             else bulletType = BulletType.Collapsed;
         }
         return <div className="treeViewItem-container"
-            style={{
-                background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0"
-            }}
+            style={{ background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
             onContextMenu={this.onWorkspaceContextMenu}
             onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
             <li className="collection-child">
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3c8804415..8c245ab3a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -268,7 +268,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])) });
         ContextMenu.Instance.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]) });
         //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
-        ContextMenu.Instance.addItem({ description: "Delete" + this.props.Document.title, event: this.deleteClicked });
+        ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked });
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
         if (!SelectionManager.IsSelected(this)) {
             SelectionManager.SelectDoc(this, false);
-- 
cgit v1.2.3-70-g09d2


From a78282cdf7fbb99386484640e1fb89d4b9b0cbee Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 2 May 2019 16:19:07 -0400
Subject: fixed some things with trees and summaries.

---
 src/client/views/Main.tsx                                    |  3 +--
 src/client/views/Templates.tsx                               | 12 +++++++++++-
 src/client/views/collections/CollectionTreeView.tsx          | 10 +++++++---
 .../views/collections/collectionFreeForm/MarqueeView.tsx     |  4 +++-
 src/client/views/nodes/DocumentContentsView.tsx              |  3 ++-
 src/client/views/nodes/FieldView.tsx                         | 11 ++++-------
 src/client/views/nodes/ImageBox.scss                         |  2 ++
 src/new_fields/List.ts                                       |  6 +++---
 src/server/authentication/models/current_user_utils.ts       |  2 ++
 9 files changed, 35 insertions(+), 18 deletions(-)

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

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index a07a2d5b1..97eb73d7f 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -144,8 +144,7 @@ export class Main extends React.Component {
     createNewWorkspace = async (id?: string) => {
         const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
         if (list) {
-            let libraryDoc = Docs.TreeDocument([CurrentUserUtils.UserDocument], { x: 0, y: 400, title: `Library: ${CurrentUserUtils.email} ${list.length + 1}` });
-            libraryDoc.excludeFromLibrary = true;
+            let libraryDoc = await (CurrentUserUtils.UserDocument.library as Doc);
             let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, title: `WS collection ${list.length + 1}` });
             var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(libraryDoc, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
             let mainDoc = Docs.DockDocument([libraryDoc, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` });
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index e17dd2354..668ae5312 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -54,7 +54,17 @@ export namespace Templates {
         `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div><div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px">{props.Document.title}</div></div>`
     );
     export const Summary = new Template("Title", TemplatePosition.InnerTop,
-        `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div><div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px">{props.Document.doc1.title}</div></div>`
+        `<div style="height:100%; width:100%;position:absolute; margin:0; padding: 0">
+            <div style="height:60%; width:100%;position:absolute;background:yellow; padding:0; margin:0">
+                {layout}
+            </div>
+            <div style="bottom:0px; height:40%; width:50%; position:absolute; background-color: rgba(0, 0, 0, .4); color: white;">
+                <FieldView {...props} fieldKey={"doc1"} />
+            </div>
+            <div style="bottom:0; left: 50%; height:40%; width:50%; position:absolute; background-color: rgba(0, 0, 0, .4); color: white;">
+                <FieldView {...props} fieldKey={"doc2"} />
+            </div>
+        </div>`
     );
 
     export const TemplateList: Template[] = [Title, OuterCaption, InnerCaption, SideCaption];
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 48b226615..2cef1462b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,7 +1,7 @@
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
 import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from "mobx";
+import { action, observable, trace } from "mobx";
 import { observer } from "mobx-react";
 import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
 import { EditableView } from "../EditableView";
@@ -145,8 +145,11 @@ class TreeView extends React.Component<TreeViewProps> {
         </div>;
     }
     public static GetChildElements(docs: Doc[], remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
-        return docs.filter(child => !child.excludeFromLibrary).map(child =>
-            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
+        return docs.filter(child => !child.excludeFromLibrary).filter(doc => FieldValue(doc)).map(child => {
+            console.log("child = " + child[Id]);
+            return <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />
+        }
+        );
     }
 }
 
@@ -168,6 +171,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         }
     }
     render() {
+        trace();
         const children = this.children;
         let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType;
         if (!children) {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c58e7780c..82027a6f2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -180,7 +180,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             // SelectionManager.DeselectAll();
             if (e.key === "r") {
                 let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
-                summary.doc1 = newCollection.proto!;
+                summary.doc1 = selected[0];
+                if (selected.length > 1)
+                    summary.doc2 = selected[1];
                 summary.templates = new List<string>([Templates.Summary.Layout]);
                 this.props.addLiveTextDocument(summary);
                 e.preventDefault();
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 24e8a36ae..ddfe79a5c 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -15,6 +15,7 @@ import { IconBox } from "./IconBox";
 import { KeyValueBox } from "./KeyValueBox";
 import { PDFBox } from "./PDFBox";
 import { VideoBox } from "./VideoBox";
+import { FieldView } from "./FieldView";
 import { WebBox } from "./WebBox";
 import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import React = require("react");
@@ -63,7 +64,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
     render() {
         return <JsxParser
-            components={{ FormattedTextBox, ImageBox, IconBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+            components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
             jsx={this.finalLayout}
             showWarnings={true}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 63009f2db..a1e083b36 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -57,12 +57,12 @@ export class FieldView extends React.Component<FieldViewProps> {
     }
     render() {
         const field = this.field;
-        if (!field) {
+        if (field === undefined) {
             return <p>{'<null>'}</p>;
         }
-        if (typeof field === "string") {
-            return <p>{field}</p>;
-        }
+        // if (typeof field === "string") {
+        //     return <p>{field}</p>;
+        // }
         else if (field instanceof RichTextField) {
             return <FormattedTextBox {...this.props} />;
         }
@@ -108,9 +108,6 @@ export class FieldView extends React.Component<FieldViewProps> {
         // else if (field instanceof HtmlField) {
         //     return <WebBox {...this.props} />
         // }
-        else if (typeof field === "number") {
-            return <p>{field}</p>;
-        }
         else if (!(field instanceof Promise)) {
             return <p>{JSON.stringify(field)}</p>;
         }
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 9fe211df0..2316a050e 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -10,6 +10,8 @@
 }
 .imageBox-cont-interactive {
   pointer-events: all;
+  width:100%;
+  height:auto;
 }
 
 .imageBox-dot {
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index 1c4b96c81..db7932cec 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -2,7 +2,7 @@ import { Deserializable, autoObject } from "../client/util/SerializationHelper";
 import { Field, Update, Self, FieldResult } from "./Doc";
 import { setter, getter, deleteProperty } from "./util";
 import { serializable, alias, list } from "serializr";
-import { observable } from "mobx";
+import { observable, action } from "mobx";
 import { ObjectField, OnUpdate, Copy } from "./ObjectField";
 import { RefField } from "./RefField";
 import { ProxyField } from "./Proxy";
@@ -25,12 +25,12 @@ const listHandlers: any = {
         this[Update]();
         return field;
     },
-    push(...items: any[]) {
+    push: action(function (this: any, ...items: any[]) {
         items = items.map(toObjectField);
         const res = this[Self].__fields.push(...items);
         this[Update]();
         return res;
-    },
+    }),
     reverse() {
         const res = this[Self].__fields.reverse();
         this[Update]();
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 9db470ca0..93c2afb1d 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -26,6 +26,8 @@ export class CurrentUserUtils {
         doc.title = this.email;
         doc.data = new List<Doc>();
         doc.optionalRightCollection = Docs.SchemaDocument([], { title: "Pending documents" });
+        doc.library = Docs.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` });
+        (doc.library as Doc).excludeFromLibrary = true;
         return doc;
     }
 
-- 
cgit v1.2.3-70-g09d2


From ac9b42dba420139c0a7fad5685425b3cf9c1570e Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 2 May 2019 16:58:14 -0400
Subject: clean up

---
 src/client/views/Main.tsx                              |  5 ++---
 src/client/views/collections/CollectionTreeView.scss   |  2 ++
 src/client/views/collections/CollectionTreeView.tsx    | 11 +++--------
 src/new_fields/Doc.ts                                  |  5 +++--
 src/server/authentication/models/current_user_utils.ts | 10 ++++++++--
 5 files changed, 18 insertions(+), 15 deletions(-)

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

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 97eb73d7f..90339aea2 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -144,10 +144,9 @@ export class Main extends React.Component {
     createNewWorkspace = async (id?: string) => {
         const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
         if (list) {
-            let libraryDoc = await (CurrentUserUtils.UserDocument.library as Doc);
             let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, title: `WS collection ${list.length + 1}` });
-            var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(libraryDoc, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
-            let mainDoc = Docs.DockDocument([libraryDoc, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` });
+            var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
+            let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` });
             list.push(mainDoc);
             // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
             setTimeout(() => {
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index ce7da5767..19d4abc05 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -37,6 +37,8 @@
         width: 1.5em;
         display: inline-block;
         color: $intermediate-color;
+        margin-top: 3px;
+        transform: scale(1.3,1.3);
     }
 
     .coll-title {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 2cef1462b..b67d6f965 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -145,11 +145,8 @@ class TreeView extends React.Component<TreeViewProps> {
         </div>;
     }
     public static GetChildElements(docs: Doc[], remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
-        return docs.filter(child => !child.excludeFromLibrary).filter(doc => FieldValue(doc)).map(child => {
-            console.log("child = " + child[Id]);
-            return <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />
-        }
-        );
+        return docs.filter(child => !child.excludeFromLibrary).filter(doc => FieldValue(doc)).map(child =>
+            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
     }
 }
 
@@ -177,9 +174,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         if (!children) {
             return (null);
         }
-        let testForLibrary = children && children.length === 1 && children[0] === CurrentUserUtils.UserDocument;
-        var subchildren = testForLibrary ? Cast(children[0].data, listSpec(Doc), children) : children;
-        let childElements = TreeView.GetChildElements(subchildren, this.remove, this.props.moveDocument, dropAction);
+        let childElements = TreeView.GetChildElements(children, this.remove, this.props.moveDocument, dropAction);
 
         return (
             <div id="body" className="collectionTreeView-dropTarget"
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index b37b52ea0..978aacaac 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -9,6 +9,7 @@ import { listSpec } from "./Schema";
 import { List } from "./List";
 import { ObjectField, Parent, OnUpdate } from "./ObjectField";
 import { RefField, FieldId, Id, HandleUpdate } from "./RefField";
+import { Docs } from "../client/documents/Documents";
 // import { Docs } from "../client/documents/Documents";
 
 export function IsField(field: any): field is Field {
@@ -183,8 +184,8 @@ export namespace Doc {
 
     export function MakeLink(source: Doc, target: Doc) {
         UndoManager.RunInBatch(() => {
-            // let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 });
-            let linkDoc = new Doc;
+            let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 });
+            //let linkDoc = new Doc;
             linkDoc.title = "-link name-";
             linkDoc.linkDescription = "";
             linkDoc.linkTags = "Default";
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 93c2afb1d..5f45d7bcc 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -7,6 +7,9 @@ import { RouteStore } from "../../RouteStore";
 import { DocServer } from "../../../client/DocServer";
 import { Doc } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
+import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView";
+import { CollectionTreeView } from "../../../client/views/collections/CollectionTreeView";
+import { CollectionView } from "../../../client/views/collections/CollectionView";
 
 export class CurrentUserUtils {
     private static curr_email: string;
@@ -23,11 +26,14 @@ export class CurrentUserUtils {
 
     private static createUserDocument(id: string): Doc {
         let doc = new Doc(id, true);
+        doc.viewType = CollectionViewType.Tree;
+        doc.layout = CollectionView.LayoutString();
         doc.title = this.email;
         doc.data = new List<Doc>();
+        doc.excludeFromLibrary = true;
         doc.optionalRightCollection = Docs.SchemaDocument([], { title: "Pending documents" });
-        doc.library = Docs.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` });
-        (doc.library as Doc).excludeFromLibrary = true;
+        // doc.library = Docs.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` });
+        // (doc.library as Doc).excludeFromLibrary = true;
         return doc;
     }
 
-- 
cgit v1.2.3-70-g09d2


From c9f3a1934548169ebe3c2e97968680ff339e304e Mon Sep 17 00:00:00 2001
From: Sam Wilkins <abdullah_ahmed@brown.edu>
Date: Sat, 4 May 2019 18:59:30 -0400
Subject: initial commit - todo: use cursor field rather than generic tuple
 field

---
 .../northstar/operations/HistogramOperation.ts     |  5 +-
 src/client/util/DragManager.ts                     |  8 +--
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/PresentationView.tsx              |  4 +-
 src/client/views/PreviewCursor.tsx                 |  4 +-
 .../views/collections/CollectionDockingView.tsx    |  6 ++-
 src/client/views/collections/CollectionSubView.tsx |  9 ++--
 .../views/collections/CollectionTreeView.tsx       |  4 +-
 .../views/collections/CollectionVideoView.tsx      |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  7 +--
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  9 ++--
 src/client/views/nodes/FormattedTextBox.tsx        |  8 +--
 src/client/views/nodes/PDFBox.tsx                  |  5 +-
 src/new_fields/InkField.ts                         |  2 +-
 src/new_fields/ObjectField.ts                      |  2 +-
 src/new_fields/TupleField.ts                       | 63 ++++++++++++++++++++++
 16 files changed, 106 insertions(+), 34 deletions(-)
 create mode 100644 src/new_fields/TupleField.ts

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

diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index 5c9c832c0..78b206bdc 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -65,7 +65,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     @computed
     public get FilterString(): string {
         if (this.OverridingFilters.length > 0) {
-            return "(" + this.OverridingFilters.filter(fm => fm != null).map(fm => fm.ToPythonString()).join(" || ") + ")";
+            return "(" + this.OverridingFilters.filter(fm => fm !== null).map(fm => fm.ToPythonString()).join(" || ") + ")";
         }
         let filterModels: FilterModel[] = [];
         return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true);
@@ -89,8 +89,9 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     @action
     public DrillDown(up: boolean) {
         if (!up) {
-            if (!this.BarFilterModels.length)
+            if (!this.BarFilterModels.length) {
                 return;
+            }
             this._stackedFilters.push(this.BarFilterModels.map(f => f));
             this.OverridingFilters.length = 0;
             this.OverridingFilters.push(...this._stackedFilters[this._stackedFilters.length - 1]);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index a3dbe6e43..8f27f5b21 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -155,9 +155,9 @@ export namespace DragManager {
     export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
         StartDrag(eles, dragData, downX, downY, options,
             (dropData: { [id: string]: any }) =>
-                (dropData.droppedDocuments = dragData.userDropAction == "alias" || (!dragData.userDropAction && dragData.dropAction == "alias") ?
+                (dropData.droppedDocuments = dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ?
                     dragData.draggedDocuments.map(d => Doc.MakeAlias(d)) :
-                    dragData.userDropAction == "copy" || (!dragData.userDropAction && dragData.dropAction == "copy") ?
+                    dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ?
                         dragData.draggedDocuments.map(d => Doc.MakeCopy(d, true)) :
                         dragData.draggedDocuments));
     }
@@ -280,7 +280,7 @@ export namespace DragManager {
         };
 
         let hideDragElements = () => {
-            dragElements.map(dragElement => dragElement.parentNode == dragDiv && dragDiv.removeChild(dragElement));
+            dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
             eles.map(ele => (ele.hidden = false));
         };
         let endDrag = () => {
@@ -289,7 +289,7 @@ export namespace DragManager {
             if (options) {
                 options.handlers.dragComplete({});
             }
-        }
+        };
 
         AbortDrag = () => {
             hideDragElements();
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 693d6ec55..16e5b6b48 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -278,7 +278,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             iconDoc = this.createIcon([docView], layout);
         }
         if (SelectionManager.SelectedDocuments()[0].props.addDocument !== undefined) {
-            SelectionManager.SelectedDocuments()[0].props.addDocument!(iconDoc!);
+            SelectionManager.SelectedDocuments()[0].props.addDocument!(iconDoc);
         }
         return iconDoc;
     }
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index b4c12d057..d8bdbacca 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -1,7 +1,7 @@
 import { observer } from "mobx-react";
-import React = require("react")
+import React = require("react");
 import { observable, action } from "mobx";
-import "./PresentationView.scss"
+import "./PresentationView.scss";
 import "./Main.tsx";
 import { DocumentManager } from "../util/DocumentManager";
 import { Utils } from "../../Utils";
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 4359ba093..9520f489c 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -17,7 +17,7 @@ export class PreviewCursor extends React.Component<{}> {
 
     constructor(props: any) {
         super(props);
-        document.addEventListener("keydown", this.onKeyPress)
+        document.addEventListener("keydown", this.onKeyPress);
     }
 
     @action
@@ -27,7 +27,7 @@ export class PreviewCursor extends React.Component<{}> {
         // the keyPress here.
         //if not these keys, make a textbox if preview cursor is active!
         if (e.key.startsWith("F") && !e.key.endsWith("F")) {
-        } else if (e.key != "Escape" && e.key != "Alt" && e.key != "Shift" && e.key != "Meta" && e.key != "Control" && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
+        } else if (e.key !== "Escape" && e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
             if ((!e.ctrlKey && !e.metaKey) || e.key === "v") {
                 PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e);
                 PreviewCursor.Visible = false;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index cfb1aef7d..725f0ab51 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -350,13 +350,15 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
 
     get content() {
-        if (!this._document)
+        if (!this._document) {
             return (null);
+        }
         return (
             <div className="collectionDockingView-content" ref={this._mainCont}
                 style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
-                <DocumentView key={this._document![Id]} Document={this._document!}
+                <DocumentView key={this._document[Id]} Document={this._document}
                     toggleMinimized={emptyFunction}
+                    bringToFront={emptyFunction}
                     addDocument={undefined}
                     removeDocument={undefined}
                     ContentScaling={this.contentScaling}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 828ac880a..1e8723fc6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -17,6 +17,7 @@ import { Cast, PromiseValue, FieldValue } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { DocServer } from "../../DocServer";
 import { ObjectField } from "../../../new_fields/ObjectField";
+import { TupleField } from "../../../new_fields/TupleField";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -66,12 +67,12 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 if (!proto) {
                     return;
                 }
-                let cursors = await Cast(proto.cursors, listSpec(ObjectField));
+                let cursors = await Cast(proto!.cursors, listSpec(TupleField));
                 if (!cursors) {
-                    proto.cursors = cursors = new List<ObjectField>();
+                    proto!.cursors = cursors = new List<TupleField<[]>>();
                 }
-                if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) {
-                    cursors[ind].Data[1] = position;
+                if (cursors!.length > 0 && (ind = cursors!.findIndex(entry => entry.data[0][0] === id)) > -1) {
+                    cursors![ind].data[1] = position;
                 } else {
                     let entry = new TupleField<[string, string], [number, number]>([textInfo, position]);
                     cursors.push(entry);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b67d6f965..7898d74ce 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -117,8 +117,8 @@ class TreeView extends React.Component<TreeViewProps> {
         }
     }
 
-    onPointerEnter = (e: React.PointerEvent): void => { this.props.document.libraryBrush = true; }
-    onPointerLeave = (e: React.PointerEvent): void => { this.props.document.libraryBrush = false; }
+    onPointerEnter = (e: React.PointerEvent): void => { this.props.document.libraryBrush = true; };
+    onPointerLeave = (e: React.PointerEvent): void => { this.props.document.libraryBrush = false; };
 
     render() {
         let bulletType = BulletType.List;
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 9dee217cb..cb3fd1ba4 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -90,7 +90,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
         }
     }
 
-    setVideoBox = (player: VideoBox) => { this._videoBox = player; }
+    setVideoBox = (player: VideoBox) => { this._videoBox = player; };
 
     private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
         let props = { ...this.props, ...renderProps };
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 8c81f6990..a9e627188 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -139,7 +139,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         if (this._commandExecuted) {
             return;
         }
-        if (e.key === "Backspace" || e.key === "Delete" || e.key == "d") {
+        if (e.key === "Backspace" || e.key === "Delete" || e.key === "d") {
             this._commandExecuted = true;
             this.marqueeSelect().map(d => this.props.removeDocument(d));
             let ink = Cast(this.props.container.props.Document.ink, InkField);
@@ -154,8 +154,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             e.stopPropagation();
             let bounds = this.Bounds;
             let selected = this.marqueeSelect().map(d => {
-                if (e.key !== "r")
+                if (e.key !== "r") {
                     this.props.removeDocument(d);
+                }
                 d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
                 d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
                 d.page = -1;
@@ -194,7 +195,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     let maxy = NumCast(maximizedDoc.y, undefined);
                     let maxw = NumCast(maximizedDoc.width, undefined);
                     let maxh = NumCast(maximizedDoc.height, undefined);
-                    maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0])
+                    maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0]);
                 });
             }
             else {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 2ba0458f5..6186cf348 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -148,7 +148,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                         maxw !== undefined && maxh !== undefined) {
                         let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny);
                         maximizedDoc.isMinimized = false;
-                        maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), isMinimized ? 1 : 0])
+                        maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), isMinimized ? 1 : 0]);
                     }
                 }
             });
@@ -167,16 +167,17 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             if (await BoolCast(this.props.Document.isButton, false)) {
                 let maximizedDocs = await Cast(this.props.Document.maximizedDocs, listSpec(Doc));
                 if (maximizedDocs) {   // bcz: need a better way to associate behaviors with click events on widget-documents
-                    if (ctrlKey)
+                    if (ctrlKey) {
                         this.props.addDocument && maximizedDocs.filter(d => d instanceof Doc).map(maxDoc => this.props.addDocument!(maxDoc, false));
+                    }
                     this.toggleIcon();
                 }
             }
         }
     }
 
-    onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; }
-    onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; }
+    onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; };
+    onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
 
     borderRounding = () => {
         let br = NumCast(this.props.Document.borderRounding);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index eeb60cb3d..3873dfd62 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -91,7 +91,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 let titlestr = str.substr(0, Math.min(40, str.length));
                 let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
                 target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
-            };
+            }
         }
     }
 
@@ -176,8 +176,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
             e.stopPropagation();
-            if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip)
+            if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
                 this._toolTipTextMenu.tooltip.style.opacity = "0";
+            }
         }
         if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
             this._gotDown = true;
@@ -185,8 +186,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
     onPointerUp = (e: React.PointerEvent): void => {
-        if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip)
+        if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
             this._toolTipTextMenu.tooltip.style.opacity = "1";
+        }
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
         }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index eb45ea273..caa66cbeb 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -215,8 +215,9 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
             if (e.altKey) {
                 this._alt = true;
             } else {
-                if (e.metaKey)
+                if (e.metaKey) {
                     e.stopPropagation();
+                }
             }
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointerup", this.onPointerUp);
@@ -286,7 +287,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     renderHeight = 2400;
     @computed
     get pdfPage() {
-        return <Page height={this.renderHeight} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />
+        return <Page height={this.renderHeight} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />;
     }
     @computed
     get pdfContent() {
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 86a8bd18a..a3157857f 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -39,6 +39,6 @@ export class InkField extends ObjectField {
     }
 
     [Copy]() {
-        return new InkField(deepCopy(this.inkData))
+        return new InkField(deepCopy(this.inkData));
     }
 }
diff --git a/src/new_fields/ObjectField.ts b/src/new_fields/ObjectField.ts
index 0f3777af6..715c6a924 100644
--- a/src/new_fields/ObjectField.ts
+++ b/src/new_fields/ObjectField.ts
@@ -5,7 +5,7 @@ export const Parent = Symbol("Parent");
 export const Copy = Symbol("Copy");
 
 export abstract class ObjectField {
-    protected [OnUpdate]?: (diff?: any) => void;
+    protected [OnUpdate](diff?: any) { };
     private [Parent]?: Doc;
     abstract [Copy](): ObjectField;
 }
diff --git a/src/new_fields/TupleField.ts b/src/new_fields/TupleField.ts
new file mode 100644
index 000000000..1ff57fefc
--- /dev/null
+++ b/src/new_fields/TupleField.ts
@@ -0,0 +1,63 @@
+import { ObjectField, Copy } from "./ObjectField";
+import { IObservableArray, IArrayChange, IArraySplice, observe, Lambda, observable } from "mobx";
+import { UndoManager } from "../client/util/UndoManager";
+import { Field } from "./Doc";
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, createSimpleSchema, list, object } from "serializr";
+import { array } from "prop-types";
+
+const tupleSchema = createSimpleSchema({
+
+});
+
+@Deserializable("tuple")
+export class TupleField<T, U> extends ObjectField {
+
+
+    @serializable(list(object(tupleSchema)))
+    private Data: [T, U];
+
+    public get data() {
+        return this.Data;
+    }
+
+    constructor(data: [T, U]) {
+        super();
+        this.Data = data;
+        this.observeTuple();
+    }
+
+    private observeDisposer: Lambda | undefined;
+    private observeTuple(): void {
+        this.observeDisposer = observe(this.Data as (T | U)[] as IObservableArray<T | U>, (change: IArrayChange<T | U> | IArraySplice<T | U>) => {
+            if (change.type === "update") {
+                UndoManager.AddEvent({
+                    undo: () => this.Data[change.index] = change.oldValue,
+                    redo: () => this.Data[change.index] = change.newValue
+                });
+            } else {
+                throw new Error("Why are you messing with the length of a tuple, huh?");
+            }
+        });
+    }
+
+    protected setData(value: [T, U]) {
+        if (this.observeDisposer) {
+            this.observeDisposer();
+        }
+        this.Data = observable(value) as (T | U)[] as [T, U];
+        this.observeTuple();
+    }
+
+    UpdateFromServer(values: [T, U]) {
+        this.setData(values);
+    }
+
+    ToScriptString(): string {
+        return `new TupleField([${this.Data[0], this.Data[1]}])`;
+    }
+
+    [Copy]() {
+        return new TupleField<T, U>(this.Data);
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From af2e5dbf49e0e82d76f267c681761968d4bafc62 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 4 May 2019 23:03:49 -0400
Subject: fixed tree view.  added non-data keys.

---
 src/client/views/Main.scss                         |  1 +
 .../views/collections/CollectionTreeView.scss      | 17 +++++++---
 .../views/collections/CollectionTreeView.tsx       | 37 ++++++++++++++--------
 src/new_fields/Doc.ts                              |  2 +-
 4 files changed, 38 insertions(+), 19 deletions(-)

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

diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index cbf920793..5c5c252e9 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -182,6 +182,7 @@ button:hover {
     top: 0;
     left: 0;
     overflow: scroll;
+    z-index: 1;
 }
 #mainContent-div {
     width: 100%;
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 19d4abc05..6ce13cf56 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -33,9 +33,10 @@
     }
 
     .bullet {
-        position: absolute;
-        width: 1.5em;
-        display: inline-block;
+        float:left;
+        position: relative;
+        width: 15px;
+        display: block;
         color: $intermediate-color;
         margin-top: 3px;
         transform: scale(1.3,1.3);
@@ -50,7 +51,7 @@
     .docContainer {
         margin-left: 10px;
         display: block;
-        width: max-content;
+        width:100%;//width: max-content;
     }
 
     .docContainer:hover {
@@ -59,6 +60,9 @@
             // width: auto;
         }
     }
+    .editableView-container {
+        font-weight: bold;
+    }
 
     .delete-button {
         color: $intermediate-color;
@@ -67,4 +71,9 @@
         // margin-top: 3px;
         display: inline;
     }
+
+    .collectionTreeView-keyHeader {
+        font-style: italic;
+        font-size: 8pt;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b67d6f965..17109508d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -18,6 +18,8 @@ import { Main } from '../Main';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { CollectionDockingView } from './CollectionDockingView';
 import { DocumentManager } from '../../util/DocumentManager';
+import { Utils } from '../../../Utils';
+import { List } from '../../../new_fields/List';
 
 
 export interface TreeViewProps {
@@ -122,17 +124,25 @@ class TreeView extends React.Component<TreeViewProps> {
 
     render() {
         let bulletType = BulletType.List;
-        let contentElement: JSX.Element | null = (null);
-        var children = Cast(this.props.document.data, listSpec(Doc));
-        if (children) { // add children for a collection
-            if (!this._collapsed) {
-                bulletType = BulletType.Collapsible;
-                contentElement = <ul>
-                    {TreeView.GetChildElements(children, this.remove, this.move, this.props.dropAction)}
-                </ul >;
-            }
-            else bulletType = BulletType.Collapsed;
+        let contentElement: (JSX.Element | null)[] = [];
+        let keys = Array.from(Object.keys(this.props.document));
+        if (this.props.document.proto instanceof Doc) {
+            keys.push(...Array.from(Object.keys(this.props.document.proto)));
         }
+        keys.map(key => {
+            let docList = Cast(this.props.document[key], listSpec(Doc));
+            if (docList instanceof List && docList.length && docList[0] instanceof Doc) {
+                if (!this._collapsed) {
+                    bulletType = BulletType.Collapsible;
+                    contentElement.push(<ul key={key + "more"}>
+                        {(key === "data") ? (null) :
+                            <span className="collectionTreeView-keyHeader" key={key}>{key}</span>}
+                        {TreeView.GetChildElements(docList, key !== "data", this.remove, this.move, this.props.dropAction)}
+                    </ul >);
+                } else
+                    bulletType = BulletType.Collapsed;
+            }
+        });
         return <div className="treeViewItem-container"
             style={{ background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
             onContextMenu={this.onWorkspaceContextMenu}
@@ -144,8 +154,8 @@ class TreeView extends React.Component<TreeViewProps> {
             </li>
         </div>;
     }
-    public static GetChildElements(docs: Doc[], remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
-        return docs.filter(child => !child.excludeFromLibrary).filter(doc => FieldValue(doc)).map(child =>
+    public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
+        return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).filter(doc => FieldValue(doc)).map(child =>
             <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
     }
 }
@@ -168,13 +178,12 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         }
     }
     render() {
-        trace();
         const children = this.children;
         let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType;
         if (!children) {
             return (null);
         }
-        let childElements = TreeView.GetChildElements(children, this.remove, this.props.moveDocument, dropAction);
+        let childElements = TreeView.GetChildElements(children, false, this.remove, this.props.moveDocument, dropAction);
 
         return (
             <div id="body" className="collectionTreeView-dropTarget"
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 3055af1bf..b2863c632 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -186,7 +186,7 @@ export namespace Doc {
         UndoManager.RunInBatch(() => {
             let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 });
             //let linkDoc = new Doc;
-            linkDoc.title = "-link name-";
+            linkDoc.proto!.title = "-link name-";
             linkDoc.linkDescription = "";
             linkDoc.linkTags = "Default";
 
-- 
cgit v1.2.3-70-g09d2


From 9830eb15041fa8e99ef8f5bb6fdcf6b3c28c058d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 6 May 2019 01:10:14 -0400
Subject: added start of "minimap" support via better buttons and summaries.

---
 .../views/collections/CollectionDockingView.tsx    | 36 +++++++++++++++++---
 .../views/collections/CollectionTreeView.scss      | 39 +++++++++++++---------
 .../views/collections/CollectionTreeView.tsx       | 13 +++++---
 .../collections/collectionFreeForm/MarqueeView.tsx | 37 ++++++++++++--------
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 28 +++++++++++++---
 src/client/views/nodes/DocumentView.tsx            |  9 ++---
 src/client/views/nodes/FormattedTextBox.tsx        |  4 +--
 7 files changed, 118 insertions(+), 48 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 278065479..e4f02a4bc 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -72,6 +72,37 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         this.stateChanged();
     }
 
+    @undoBatch
+    @action
+    public CloseRightSplit(document: Doc) {
+        if (this._goldenLayout.root.contentItems[0].isRow) {
+            this._goldenLayout.root.contentItems[0].contentItems.map((child: any, i: number) => {
+                if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
+                    child.contentItems[0].config.props.documentId == document[Id]) {
+                    child.contentItems[0].remove();
+                    //this._goldenLayout.root.contentItems[0].contentItems.splice(i, 1);
+                    this.layoutChanged(document);
+                } else
+                    child.contentItems.map((tab: any, j: number) => {
+                        if (tab.config.component === "DocumentFrameRenderer" && tab.config.props.documentId === document[Id]) {
+                            child.contentItems[j].remove();
+                            let docs = Cast(this.props.Document.data, listSpec(Doc));
+                            docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1);
+                        }
+                    });
+            })
+        }
+    }
+
+    @action
+    layoutChanged(removed?: Doc) {
+        this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
+        this._goldenLayout.emit('sbcreteChanged');
+        this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
+        if (removed) CollectionDockingView.Instance._removedDocs.push(removed);
+        this.stateChanged();
+    }
+
     //
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
     //
@@ -107,10 +138,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             newContentItem.config.height = 10;
         }
         newContentItem.callDownwards('_$init');
-        this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
-        this._goldenLayout.emit('stateChanged');
-        this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
-        this.stateChanged();
+        this.layoutChanged();
 
         return newContentItem;
     }
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 6ce13cf56..411d67ff7 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -23,10 +23,6 @@
         margin: 5px 0;
     }
 
-    .collection-child {
-        margin-top: 10px;
-        margin-bottom: 10px;
-    }
 
     .no-indent {
         padding-left: 0;
@@ -42,24 +38,18 @@
         transform: scale(1.3,1.3);
     }
 
-    .coll-title {
-        width:max-content;
-        display: block;
-        font-size: 24px;
-    }
-
     .docContainer {
         margin-left: 10px;
         display: block;
-        width:100%;//width: max-content;
+       // width:100%;//width: max-content;
     }
-
     .docContainer:hover {
-        .delete-button {
-            display: inline;
-            // width: auto;
+        .treeViewItem-openRight {
+            display:inline;
         }
     }
+
+
     .editableView-container {
         font-weight: bold;
     }
@@ -71,7 +61,26 @@
         // margin-top: 3px;
         display: inline;
     }
+    .treeViewItem-openRight {
+        margin-left: 5px;
+        display:none;
+    }
+    .docContainer:hover {
+        .delete-button {
+            display: inline;
+            // width: auto;
+        }
+    }
 
+    .coll-title {
+        width:max-content;
+        display: block;
+        font-size: 24px;
+    }
+    .collection-child {
+        margin-top: 10px;
+        margin-bottom: 10px;
+    }
     .collectionTreeView-keyHeader {
         font-style: italic;
         font-size: 8pt;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 17109508d..947a066d9 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,5 +1,5 @@
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
+import { faCaretDown, faCaretRight, faTrashAlt, faAngleRight } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable, trace } from "mobx";
 import { observer } from "mobx-react";
@@ -36,6 +36,7 @@ export enum BulletType {
 }
 
 library.add(faTrashAlt);
+library.add(faAngleRight);
 library.add(faCaretDown);
 library.add(faCaretRight);
 
@@ -48,6 +49,7 @@ class TreeView extends React.Component<TreeViewProps> {
     @observable _collapsed: boolean = true;
 
     delete = () => this.props.deleteDoc(this.props.document);
+    openRight = () => CollectionDockingView.Instance.AddRightSplit(this.props.document);
 
     get children() {
         return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed?    .filter(doc => FieldValue(doc));
@@ -100,9 +102,11 @@ class TreeView extends React.Component<TreeViewProps> {
                 }}
             />);
         return (
-            <div className="docContainer" ref={reference} onPointerDown={onItemDown}>
+            <div className="docContainer" ref={reference} onPointerDown={onItemDown}
+                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
                 {editableView(StrCast(this.props.document.title))}
-                {/* <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div> */}
+                <div className="treeViewItem-openRight" onClick={this.openRight}><FontAwesomeIcon icon="angle-right" size="lg" /><FontAwesomeIcon icon="angle-right" size="lg" /></div>
+                {/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */}
             </div >);
     }
 
@@ -145,8 +149,7 @@ class TreeView extends React.Component<TreeViewProps> {
         });
         return <div className="treeViewItem-container"
             style={{ background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
-            onContextMenu={this.onWorkspaceContextMenu}
-            onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+            onContextMenu={this.onWorkspaceContextMenu}>
             <li className="collection-child">
                 {this.renderBullet(bulletType)}
                 {this.renderTitle()}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 8c81f6990..805921ad4 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -15,6 +15,8 @@ import { NumCast, Cast } from "../../../../new_fields/Types";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { Templates } from "../../Templates";
 import { List } from "../../../../new_fields/List";
+import { emitKeypressEvents } from "readline";
+import { listSpec } from "../../../../new_fields/Schema";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -149,16 +151,17 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             this.cleanupInteractions(false);
             e.stopPropagation();
         }
-        if (e.key === "c" || e.key === "r" || e.key === "e") {
+        if (e.key === "c" || e.key === "r" || e.key === "R" || e.key === "e") {
             this._commandExecuted = true;
             e.stopPropagation();
             let bounds = this.Bounds;
             let selected = this.marqueeSelect().map(d => {
-                if (e.key !== "r")
+                if (e.key !== "R") {
                     this.props.removeDocument(d);
-                d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
-                d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
-                d.page = -1;
+                    d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
+                    d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
+                    d.page = -1;
+                }
                 return d;
             });
             let ink = Cast(this.props.container.props.Document.ink, InkField);
@@ -179,16 +182,23 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
 
             this.marqueeInkDelete(inkData);
             // SelectionManager.DeselectAll();
-            if (e.key === "r") {
-                let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
-                summary.maximizedDocs = new List<Doc>(selected);
-                // summary.doc1 = selected[0];
-                // if (selected.length > 1)
-                //     summary.doc2 = selected[1];
-                // summary.templates = new List<string>([Templates.Summary.Layout]);
-                this.props.addLiveTextDocument(summary);
+            if (e.key === "r" || e.key === "R") {
                 e.preventDefault();
                 let scrpt = this.props.getTransform().inverse().transformPoint(bounds.left, bounds.top);
+                let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
+
+                if (e.key === "r") {
+                    summary.proto!.maximizeOnRight = true;
+                    let list = Cast(newCollection.data, listSpec(Doc));
+                    if (list && list.length === 1) {
+                        selected = list;
+                    } else {
+                        selected = [newCollection];
+                        this.props.addDocument(newCollection, false);
+                    }
+                }
+                summary.proto!.maximizedDocs = new List<Doc>(selected);
+                summary.proto!.isButton = true;
                 selected.map(maximizedDoc => {
                     let maxx = NumCast(maximizedDoc.x, undefined);
                     let maxy = NumCast(maximizedDoc.y, undefined);
@@ -196,6 +206,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     let maxh = NumCast(maximizedDoc.height, undefined);
                     maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0])
                 });
+                this.props.addLiveTextDocument(summary);
             }
             else {
                 this.props.addDocument(newCollection, false);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 2ba0458f5..cf08c1bc4 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -6,11 +6,12 @@ import "./DocumentView.scss";
 import React = require("react");
 import { DocComponent } from "../DocComponent";
 import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { FieldValue, Cast, NumCast, BoolCast } from "../../../new_fields/Types";
+import { FieldValue, Cast, NumCast, BoolCast, PromiseValue } from "../../../new_fields/Types";
 import { OmitKeys, Utils } from "../../../Utils";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Doc } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 }
@@ -86,7 +87,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 let scrpt = this.props.ScreenToLocalTransform().transformPoint(values[0], values[1]);
                 this.animateBetweenIcon(true, scrpt, [values[2], values[3]], values[4], values[5], values[6], this.props.Document, values[7] ? true : false);
             }
-        });
+        }, { fireImmediately: true });
     }
 
     componentWillUnmount() {
@@ -162,14 +163,31 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     onClick = async (e: React.MouseEvent) => {
         e.stopPropagation();
         let ctrlKey = e.ctrlKey;
+        let metaKey = e.metaKey;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
             if (await BoolCast(this.props.Document.isButton, false)) {
                 let maximizedDocs = await Cast(this.props.Document.maximizedDocs, listSpec(Doc));
                 if (maximizedDocs) {   // bcz: need a better way to associate behaviors with click events on widget-documents
-                    if (ctrlKey)
-                        this.props.addDocument && maximizedDocs.filter(d => d instanceof Doc).map(maxDoc => this.props.addDocument!(maxDoc, false));
-                    this.toggleIcon();
+                    if ((metaKey && !this.props.Document.maximizeOnRight) || (!metaKey && this.props.Document.maximizeOnRight)) {
+                        SelectionManager.DeselectAll();
+                        maximizedDocs.map(async mdoc => {
+                            let maxDoc = await mdoc;
+                            let dataDocs = await Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc));
+                            if (dataDocs) {
+                                Promise.all(dataDocs.map(async doc => await doc)).then(docs => {
+                                    if (!docs || docs.indexOf(maxDoc) == -1) {
+                                        CollectionDockingView.Instance.AddRightSplit(maxDoc);
+                                    } else {
+                                        CollectionDockingView.Instance.CloseRightSplit(maxDoc);
+                                    }
+                                })
+                            }
+                        });
+                    } else {
+                        this.props.addDocument && maximizedDocs.map(async maxDoc => this.props.addDocument!(await maxDoc, false));
+                        this.toggleIcon();
+                    }
                 }
             }
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 096a02d9b..9d356cc30 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -202,10 +202,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         CollectionDockingView.Instance.AddRightSplit(kvp);
     }
     makeButton = (e: React.MouseEvent): void => {
-        this.props.Document.isButton = !BoolCast(this.props.Document.isButton, false);
-        if (this.props.Document.isButton && !this.props.Document.nativeWidth) {
-            this.props.Document.nativeWidth = this.props.Document[WidthSym]();
-            this.props.Document.nativeHeight = this.props.Document[HeightSym]();
+        let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+        doc.isButton = !BoolCast(doc.isButton, false);
+        if (doc.isButton && !doc.nativeWidth) {
+            doc.nativeWidth = doc[WidthSym]();
+            doc.nativeHeight = doc[HeightSym]();
         }
     }
     fullScreenClicked = (e: React.MouseEvent): void => {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index eeb60cb3d..65b8b805f 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -128,7 +128,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             );
         } else {
             this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
-                () => this.props.isSelected() && !BoolCast(this.props.Document.isButton, false) && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform));
+                () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform));
         }
 
 
@@ -310,7 +310,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 // tfs: do we need this event handler
                 onWheel={this.onPointerWheel}
             >
-                <div className={`formattedTextBox-inner${rounded}`} style={{ pointerEvents: this.props.Document.isButton ? "none" : "all" }} ref={this._proseRef} />
+                <div className={`formattedTextBox-inner${rounded}`} style={{ pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} ref={this._proseRef} />
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From c631db5ff9cc8f376588c856a71c9ad09e309f1d Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 6 May 2019 12:50:32 -0400
Subject: added goldenlayout as a file, changed list / type stuff a bit.

---
 src/client/goldenLayout.js                         | 5359 ++++++++++++++++++++
 .../views/collections/CollectionDockingView.tsx    |   48 +-
 .../views/collections/CollectionTreeView.tsx       |   12 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   39 +-
 src/new_fields/List.ts                             |    2 +-
 5 files changed, 5413 insertions(+), 47 deletions(-)
 create mode 100644 src/client/goldenLayout.js

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

diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
new file mode 100644
index 000000000..56a71f1ac
--- /dev/null
+++ b/src/client/goldenLayout.js
@@ -0,0 +1,5359 @@
+(function ($) {
+    var lm = { "config": {}, "container": {}, "controls": {}, "errors": {}, "items": {}, "utils": {} };
+    lm.utils.F = function () {
+    };
+
+    lm.utils.extend = function (subClass, superClass) {
+        subClass.prototype = lm.utils.createObject(superClass.prototype);
+        subClass.prototype.contructor = subClass;
+    };
+
+    lm.utils.createObject = function (prototype) {
+        if (typeof Object.create === 'function') {
+            return Object.create(prototype);
+        } else {
+            lm.utils.F.prototype = prototype;
+            return new lm.utils.F();
+        }
+    };
+
+    lm.utils.objectKeys = function (object) {
+        var keys, key;
+
+        if (typeof Object.keys === 'function') {
+            return Object.keys(object);
+        } else {
+            keys = [];
+            for (key in object) {
+                keys.push(key);
+            }
+            return keys;
+        }
+    };
+
+    lm.utils.getHashValue = function (key) {
+        var matches = location.hash.match(new RegExp(key + '=([^&]*)'));
+        return matches ? matches[1] : null;
+    };
+
+    lm.utils.getQueryStringParam = function (param) {
+        if (window.location.hash) {
+            return lm.utils.getHashValue(param);
+        } else if (!window.location.search) {
+            return null;
+        }
+
+        var keyValuePairs = window.location.search.substr(1).split('&'),
+            params = {},
+            pair,
+            i;
+
+        for (i = 0; i < keyValuePairs.length; i++) {
+            pair = keyValuePairs[i].split('=');
+            params[pair[0]] = pair[1];
+        }
+
+        return params[param] || null;
+    };
+
+    lm.utils.copy = function (target, source) {
+        for (var key in source) {
+            target[key] = source[key];
+        }
+        return target;
+    };
+
+	/**
+	 * This is based on Paul Irish's shim, but looks quite odd in comparison. Why?
+	 * Because
+	 * a) it shouldn't affect the global requestAnimationFrame function
+	 * b) it shouldn't pass on the time that has passed
+	 *
+	 * @param   {Function} fn
+	 *
+	 * @returns {void}
+	 */
+    lm.utils.animFrame = function (fn) {
+        return (window.requestAnimationFrame ||
+            window.webkitRequestAnimationFrame ||
+            window.mozRequestAnimationFrame ||
+            function (callback) {
+                window.setTimeout(callback, 1000 / 60);
+            })(function () {
+                fn();
+            });
+    };
+
+    lm.utils.indexOf = function (needle, haystack) {
+        if (!(haystack instanceof Array)) {
+            throw new Error('Haystack is not an Array');
+        }
+
+        if (haystack.indexOf) {
+            return haystack.indexOf(needle);
+        } else {
+            for (var i = 0; i < haystack.length; i++) {
+                if (haystack[i] === needle) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+    };
+
+    if (typeof /./ != 'function' && typeof Int8Array != 'object') {
+        lm.utils.isFunction = function (obj) {
+            return typeof obj == 'function' || false;
+        };
+    } else {
+        lm.utils.isFunction = function (obj) {
+            return toString.call(obj) === '[object Function]';
+        };
+    }
+
+    lm.utils.fnBind = function (fn, context, boundArgs) {
+
+        if (Function.prototype.bind !== undefined) {
+            return Function.prototype.bind.apply(fn, [context].concat(boundArgs || []));
+        }
+
+        var bound = function () {
+
+            // Join the already applied arguments to the now called ones (after converting to an array again).
+            var args = (boundArgs || []).concat(Array.prototype.slice.call(arguments, 0));
+
+            // If not being called as a constructor
+            if (!(this instanceof bound)) {
+                // return the result of the function called bound to target and partially applied.
+                return fn.apply(context, args);
+            }
+            // If being called as a constructor, apply the function bound to self.
+            fn.apply(this, args);
+        };
+        // Attach the prototype of the function to our newly created function.
+        bound.prototype = fn.prototype;
+        return bound;
+    };
+
+    lm.utils.removeFromArray = function (item, array) {
+        var index = lm.utils.indexOf(item, array);
+
+        if (index === -1) {
+            throw new Error('Can\'t remove item from array. Item is not in the array');
+        }
+
+        array.splice(index, 1);
+    };
+
+    lm.utils.now = function () {
+        if (typeof Date.now === 'function') {
+            return Date.now();
+        } else {
+            return (new Date()).getTime();
+        }
+    };
+
+    lm.utils.getUniqueId = function () {
+        return (Math.random() * 1000000000000000)
+            .toString(36)
+            .replace('.', '');
+    };
+
+	/**
+	 * A basic XSS filter. It is ultimately up to the
+	 * implementing developer to make sure their particular
+	 * applications and usecases are save from cross site scripting attacks
+	 *
+	 * @param   {String} input
+	 * @param    {Boolean} keepTags
+	 *
+	 * @returns {String} filtered input
+	 */
+    lm.utils.filterXss = function (input, keepTags) {
+
+        var output = input
+            .replace(/javascript/gi, 'j&#97;vascript')
+            .replace(/expression/gi, 'expr&#101;ssion')
+            .replace(/onload/gi, 'onlo&#97;d')
+            .replace(/script/gi, '&#115;cript')
+            .replace(/onerror/gi, 'on&#101;rror');
+
+        if (keepTags === true) {
+            return output;
+        } else {
+            return output
+                .replace(/>/g, '&gt;')
+                .replace(/</g, '&lt;');
+        }
+    };
+
+	/**
+	 * Removes html tags from a string
+	 *
+	 * @param   {String} input
+	 *
+	 * @returns {String} input without tags
+	 */
+    lm.utils.stripTags = function (input) {
+        return $.trim(input.replace(/(<([^>]+)>)/ig, ''));
+    };
+	/**
+	 * A generic and very fast EventEmitter
+	 * implementation. On top of emitting the
+	 * actual event it emits an
+	 *
+	 * lm.utils.EventEmitter.ALL_EVENT
+	 *
+	 * event for every event triggered. This allows
+	 * to hook into it and proxy events forwards
+	 *
+	 * @constructor
+	 */
+    lm.utils.EventEmitter = function () {
+        this._mSubscriptions = {};
+        this._mSubscriptions[lm.utils.EventEmitter.ALL_EVENT] = [];
+
+		/**
+		 * Listen for events
+		 *
+		 * @param   {String} sEvent    The name of the event to listen to
+		 * @param   {Function} fCallback The callback to execute when the event occurs
+		 * @param   {[Object]} oContext The value of the this pointer within the callback function
+		 *
+		 * @returns {void}
+		 */
+        this.on = function (sEvent, fCallback, oContext) {
+            if (!lm.utils.isFunction(fCallback)) {
+                throw new Error('Tried to listen to event ' + sEvent + ' with non-function callback ' + fCallback);
+            }
+
+            if (!this._mSubscriptions[sEvent]) {
+                this._mSubscriptions[sEvent] = [];
+            }
+
+            this._mSubscriptions[sEvent].push({ fn: fCallback, ctx: oContext });
+        };
+
+		/**
+		 * Emit an event and notify listeners
+		 *
+		 * @param   {String} sEvent The name of the event
+		 * @param    {Mixed}  various additional arguments that will be passed to the listener
+		 *
+		 * @returns {void}
+		 */
+        this.emit = function (sEvent) {
+            var i, ctx, args;
+
+            args = Array.prototype.slice.call(arguments, 1);
+
+            var subs = this._mSubscriptions[sEvent];
+
+            if (subs) {
+                subs = subs.slice();
+                for (i = 0; i < subs.length; i++) {
+                    ctx = subs[i].ctx || {};
+                    subs[i].fn.apply(ctx, args);
+                }
+            }
+
+            args.unshift(sEvent);
+
+            var allEventSubs = this._mSubscriptions[lm.utils.EventEmitter.ALL_EVENT].slice()
+
+            for (i = 0; i < allEventSubs.length; i++) {
+                ctx = allEventSubs[i].ctx || {};
+                allEventSubs[i].fn.apply(ctx, args);
+            }
+        };
+
+		/**
+		 * Removes a listener for an event, or all listeners if no callback and context is provided.
+		 *
+		 * @param   {String} sEvent    The name of the event
+		 * @param   {Function} fCallback The previously registered callback method (optional)
+		 * @param   {Object} oContext  The previously registered context (optional)
+		 *
+		 * @returns {void}
+		 */
+        this.unbind = function (sEvent, fCallback, oContext) {
+            if (!this._mSubscriptions[sEvent]) {
+                throw new Error('No subscribtions to unsubscribe for event ' + sEvent);
+            }
+
+            var i, bUnbound = false;
+
+            for (i = 0; i < this._mSubscriptions[sEvent].length; i++) {
+                if
+                    (
+                    (!fCallback || this._mSubscriptions[sEvent][i].fn === fCallback) &&
+                    (!oContext || oContext === this._mSubscriptions[sEvent][i].ctx)
+                ) {
+                    this._mSubscriptions[sEvent].splice(i, 1);
+                    bUnbound = true;
+                }
+            }
+
+            if (bUnbound === false) {
+                throw new Error('Nothing to unbind for ' + sEvent);
+            }
+        };
+
+		/**
+		 * Alias for unbind
+		 */
+        this.off = this.unbind;
+
+		/**
+		 * Alias for emit
+		 */
+        this.trigger = this.emit;
+    };
+
+	/**
+	 * The name of the event that's triggered for every other event
+	 *
+	 * usage
+	 *
+	 * myEmitter.on( lm.utils.EventEmitter.ALL_EVENT, function( eventName, argsArray ){
+	 * 	//do stuff
+	 * });
+	 *
+	 * @type {String}
+	 */
+    lm.utils.EventEmitter.ALL_EVENT = '__all';
+    lm.utils.DragListener = function (eElement, nButtonCode) {
+        lm.utils.EventEmitter.call(this);
+
+        this._eElement = $(eElement);
+        this._oDocument = $(document);
+        this._eBody = $(document.body);
+        this._nButtonCode = nButtonCode || 0;
+
+		/**
+		 * The delay after which to start the drag in milliseconds
+		 */
+        this._nDelay = 200;
+
+		/**
+		 * The distance the mouse needs to be moved to qualify as a drag
+		 */
+        this._nDistance = 10;//TODO - works better with delay only
+
+        this._nX = 0;
+        this._nY = 0;
+
+        this._nOriginalX = 0;
+        this._nOriginalY = 0;
+
+        this._bDragging = false;
+
+        this._fMove = lm.utils.fnBind(this.onMouseMove, this);
+        this._fUp = lm.utils.fnBind(this.onMouseUp, this);
+        this._fDown = lm.utils.fnBind(this.onMouseDown, this);
+
+
+        this._eElement.on('mousedown touchstart', this._fDown);
+    };
+
+    lm.utils.DragListener.timeout = null;
+
+    lm.utils.copy(lm.utils.DragListener.prototype, {
+        destroy: function () {
+            this._eElement.unbind('mousedown touchstart', this._fDown);
+            this._oDocument.unbind('mouseup touchend', this._fUp);
+            this._eElement = null;
+            this._oDocument = null;
+            this._eBody = null;
+        },
+
+        onMouseDown: function (oEvent) {
+            oEvent.preventDefault();
+
+            if (oEvent.button == 0 || oEvent.type === "touchstart") {
+                var coordinates = this._getCoordinates(oEvent);
+
+                this._nOriginalX = coordinates.x;
+                this._nOriginalY = coordinates.y;
+
+                this._oDocument.on('mousemove touchmove', this._fMove);
+                this._oDocument.one('mouseup touchend', this._fUp);
+
+                this._timeout = setTimeout(lm.utils.fnBind(this._startDrag, this), this._nDelay);
+            }
+        },
+
+        onMouseMove: function (oEvent) {
+            if (this._timeout != null) {
+                oEvent.preventDefault();
+
+                var coordinates = this._getCoordinates(oEvent);
+
+                this._nX = coordinates.x - this._nOriginalX;
+                this._nY = coordinates.y - this._nOriginalY;
+
+                if (this._bDragging === false) {
+                    if (
+                        Math.abs(this._nX) > this._nDistance ||
+                        Math.abs(this._nY) > this._nDistance
+                    ) {
+                        clearTimeout(this._timeout);
+                        this._startDrag();
+                    }
+                }
+
+                if (this._bDragging) {
+                    this.emit('drag', this._nX, this._nY, oEvent);
+                }
+            }
+        },
+
+        onMouseUp: function (oEvent) {
+            if (this._timeout != null) {
+                clearTimeout(this._timeout);
+                this._eBody.removeClass('lm_dragging');
+                this._eElement.removeClass('lm_dragging');
+                this._oDocument.find('iframe').css('pointer-events', '');
+                this._oDocument.unbind('mousemove touchmove', this._fMove);
+                this._oDocument.unbind('mouseup touchend', this._fUp);
+
+                if (this._bDragging === true) {
+                    this._bDragging = false;
+                    this.emit('dragStop', oEvent, this._nOriginalX + this._nX);
+                }
+            }
+        },
+
+        _startDrag: function () {
+            this._bDragging = true;
+            this._eBody.addClass('lm_dragging');
+            this._eElement.addClass('lm_dragging');
+            this._oDocument.find('iframe').css('pointer-events', 'none');
+            this.emit('dragStart', this._nOriginalX, this._nOriginalY);
+        },
+
+        _getCoordinates: function (event) {
+            event = event.originalEvent && event.originalEvent.touches ? event.originalEvent.touches[0] : event;
+            return {
+                x: event.pageX,
+                y: event.pageY
+            };
+        }
+    });
+	/**
+	 * The main class that will be exposed as GoldenLayout.
+	 *
+	 * @public
+	 * @constructor
+	 * @param {GoldenLayout config} config
+	 * @param {[DOM element container]} container Can be a jQuery selector string or a Dom element. Defaults to body
+	 *
+	 * @returns {VOID}
+	 */
+    lm.LayoutManager = function (config, container) {
+
+        if (!$ || typeof $.noConflict !== 'function') {
+            var errorMsg = 'jQuery is missing as dependency for GoldenLayout. ';
+            errorMsg += 'Please either expose $ on GoldenLayout\'s scope (e.g. window) or add "jquery" to ';
+            errorMsg += 'your paths when using RequireJS/AMD';
+            throw new Error(errorMsg);
+        }
+        lm.utils.EventEmitter.call(this);
+
+        this.isInitialised = false;
+        this._isFullPage = false;
+        this._resizeTimeoutId = null;
+        this._components = { 'lm-react-component': lm.utils.ReactComponentHandler };
+        this._itemAreas = [];
+        this._resizeFunction = lm.utils.fnBind(this._onResize, this);
+        this._unloadFunction = lm.utils.fnBind(this._onUnload, this);
+        this._maximisedItem = null;
+        this._maximisePlaceholder = $('<div class="lm_maximise_place"></div>');
+        this._creationTimeoutPassed = false;
+        this._subWindowsCreated = false;
+        this._dragSources = [];
+        this._updatingColumnsResponsive = false;
+        this._firstLoad = true;
+
+        this.width = null;
+        this.height = null;
+        this.root = null;
+        this.openPopouts = [];
+        this.selectedItem = null;
+        this.isSubWindow = false;
+        this.eventHub = new lm.utils.EventHub(this);
+        this.config = this._createConfig(config);
+        this.container = container;
+        this.dropTargetIndicator = null;
+        this.transitionIndicator = null;
+        this.tabDropPlaceholder = $('<div class="lm_drop_tab_placeholder"></div>');
+
+        if (this.isSubWindow === true) {
+            $('body').css('visibility', 'hidden');
+        }
+
+        this._typeToItem = {
+            'column': lm.utils.fnBind(lm.items.RowOrColumn, this, [true]),
+            'row': lm.utils.fnBind(lm.items.RowOrColumn, this, [false]),
+            'stack': lm.items.Stack,
+            'component': lm.items.Component
+        };
+    };
+
+	/**
+	 * Hook that allows to access private classes
+	 */
+    lm.LayoutManager.__lm = lm;
+
+	/**
+	 * Takes a GoldenLayout configuration object and
+	 * replaces its keys and values recursively with
+	 * one letter codes
+	 *
+	 * @static
+	 * @public
+	 * @param   {Object} config A GoldenLayout config object
+	 *
+	 * @returns {Object} minified config
+	 */
+    lm.LayoutManager.minifyConfig = function (config) {
+        return (new lm.utils.ConfigMinifier()).minifyConfig(config);
+    };
+
+	/**
+	 * Takes a configuration Object that was previously minified
+	 * using minifyConfig and returns its original version
+	 *
+	 * @static
+	 * @public
+	 * @param   {Object} minifiedConfig
+	 *
+	 * @returns {Object} the original configuration
+	 */
+    lm.LayoutManager.unminifyConfig = function (config) {
+        return (new lm.utils.ConfigMinifier()).unminifyConfig(config);
+    };
+
+    lm.utils.copy(lm.LayoutManager.prototype, {
+
+		/**
+		 * Register a component with the layout manager. If a configuration node
+		 * of type component is reached it will look up componentName and create the
+		 * associated component
+		 *
+		 *  {
+		 *		type: "component",
+		 *		componentName: "EquityNewsFeed",
+		 *		componentState: { "feedTopic": "us-bluechips" }
+		 *  }
+		 *
+		 * @public
+		 * @param   {String} name
+		 * @param   {Function} constructor
+		 *
+		 * @returns {void}
+		 */
+        registerComponent: function (name, constructor) {
+            if (typeof constructor !== 'function') {
+                throw new Error('Please register a constructor function');
+            }
+
+            if (this._components[name] !== undefined) {
+                throw new Error('Component ' + name + ' is already registered');
+            }
+
+            this._components[name] = constructor;
+        },
+
+		/**
+		 * Creates a layout configuration object based on the the current state
+		 *
+		 * @public
+		 * @returns {Object} GoldenLayout configuration
+		 */
+        toConfig: function (root) {
+            var config, next, i;
+
+            if (this.isInitialised === false) {
+                throw new Error('Can\'t create config, layout not yet initialised');
+            }
+
+            if (root && !(root instanceof lm.items.AbstractContentItem)) {
+                throw new Error('Root must be a ContentItem');
+            }
+
+			/*
+			 * settings & labels
+			 */
+            config = {
+                settings: lm.utils.copy({}, this.config.settings),
+                dimensions: lm.utils.copy({}, this.config.dimensions),
+                labels: lm.utils.copy({}, this.config.labels)
+            };
+
+			/*
+			 * Content
+			 */
+            config.content = [];
+            next = function (configNode, item) {
+                var key, i;
+
+                for (key in item.config) {
+                    if (key !== 'content') {
+                        configNode[key] = item.config[key];
+                    }
+                }
+
+                if (item.contentItems.length) {
+                    configNode.content = [];
+
+                    for (i = 0; i < item.contentItems.length; i++) {
+                        configNode.content[i] = {};
+                        next(configNode.content[i], item.contentItems[i]);
+                    }
+                }
+            };
+
+            if (root) {
+                next(config, { contentItems: [root] });
+            } else {
+                next(config, this.root);
+            }
+
+			/*
+			 * Retrieve config for subwindows
+			 */
+            this._$reconcilePopoutWindows();
+            config.openPopouts = [];
+            for (i = 0; i < this.openPopouts.length; i++) {
+                config.openPopouts.push(this.openPopouts[i].toConfig());
+            }
+
+			/*
+			 * Add maximised item
+			 */
+            config.maximisedItemId = this._maximisedItem ? '__glMaximised' : null;
+            return config;
+        },
+
+		/**
+		 * Returns a previously registered component
+		 *
+		 * @public
+		 * @param   {String} name The name used
+		 *
+		 * @returns {Function}
+		 */
+        getComponent: function (name) {
+            if (this._components[name] === undefined) {
+                throw new lm.errors.ConfigurationError('Unknown component "' + name + '"');
+            }
+
+            return this._components[name];
+        },
+
+		/**
+		 * Creates the actual layout. Must be called after all initial components
+		 * are registered. Recurses through the configuration and sets up
+		 * the item tree.
+		 *
+		 * If called before the document is ready it adds itself as a listener
+		 * to the document.ready event
+		 *
+		 * @public
+		 *
+		 * @returns {void}
+		 */
+        init: function () {
+
+			/**
+			 * Create the popout windows straight away. If popouts are blocked
+			 * an error is thrown on the same 'thread' rather than a timeout and can
+			 * be caught. This also prevents any further initilisation from taking place.
+			 */
+            if (this._subWindowsCreated === false) {
+                this._createSubWindows();
+                this._subWindowsCreated = true;
+            }
+
+
+			/**
+			 * If the document isn't ready yet, wait for it.
+			 */
+            if (document.readyState === 'loading' || document.body === null) {
+                $(document).ready(lm.utils.fnBind(this.init, this));
+                return;
+            }
+
+			/**
+			 * If this is a subwindow, wait a few milliseconds for the original
+			 * page's js calls to be executed, then replace the bodies content
+			 * with GoldenLayout
+			 */
+            if (this.isSubWindow === true && this._creationTimeoutPassed === false) {
+                setTimeout(lm.utils.fnBind(this.init, this), 7);
+                this._creationTimeoutPassed = true;
+                return;
+            }
+
+            if (this.isSubWindow === true) {
+                this._adjustToWindowMode();
+            }
+
+            this._setContainer();
+            this.dropTargetIndicator = new lm.controls.DropTargetIndicator(this.container);
+            this.transitionIndicator = new lm.controls.TransitionIndicator();
+            this.updateSize();
+            this._create(this.config);
+            this._bindEvents();
+            this.isInitialised = true;
+            this._adjustColumnsResponsive();
+            this.emit('initialised');
+        },
+
+		/**
+		 * Updates the layout managers size
+		 *
+		 * @public
+		 * @param   {[int]} width  height in pixels
+		 * @param   {[int]} height width in pixels
+		 *
+		 * @returns {void}
+		 */
+        updateSize: function (width, height) {
+            if (arguments.length === 2) {
+                this.width = width;
+                this.height = height;
+            } else {
+                this.width = this.container.width();
+                this.height = this.container.height();
+            }
+
+            if (this.isInitialised === true) {
+                this.root.callDownwards('setSize', [this.width, this.height]);
+
+                if (this._maximisedItem) {
+                    this._maximisedItem.element.width(this.container.width());
+                    this._maximisedItem.element.height(this.container.height());
+                    this._maximisedItem.callDownwards('setSize');
+                }
+
+                this._adjustColumnsResponsive();
+            }
+        },
+
+		/**
+		 * Destroys the LayoutManager instance itself as well as every ContentItem
+		 * within it. After this is called nothing should be left of the LayoutManager.
+		 *
+		 * @public
+		 * @returns {void}
+		 */
+        destroy: function () {
+            if (this.isInitialised === false) {
+                return;
+            }
+            this._onUnload();
+            $(window).off('resize', this._resizeFunction);
+            $(window).off('unload beforeunload', this._unloadFunction);
+            this.root.callDownwards('_$destroy', [], true);
+            this.root.contentItems = [];
+            this.tabDropPlaceholder.remove();
+            this.dropTargetIndicator.destroy();
+            this.transitionIndicator.destroy();
+            this.eventHub.destroy();
+
+            this._dragSources.forEach(function (dragSource) {
+                dragSource._dragListener.destroy();
+                dragSource._element = null;
+                dragSource._itemConfig = null;
+                dragSource._dragListener = null;
+            });
+            this._dragSources = [];
+        },
+
+		/**
+		 * Recursively creates new item tree structures based on a provided
+		 * ItemConfiguration object
+		 *
+		 * @public
+		 * @param   {Object} config ItemConfig
+		 * @param   {[ContentItem]} parent The item the newly created item should be a child of
+		 *
+		 * @returns {lm.items.ContentItem}
+		 */
+        createContentItem: function (config, parent) {
+            var typeErrorMsg, contentItem;
+
+            if (typeof config.type !== 'string') {
+                throw new lm.errors.ConfigurationError('Missing parameter \'type\'', config);
+            }
+
+            if (config.type === 'react-component') {
+                config.type = 'component';
+                config.componentName = 'lm-react-component';
+            }
+
+            if (!this._typeToItem[config.type]) {
+                typeErrorMsg = 'Unknown type \'' + config.type + '\'. ' +
+                    'Valid types are ' + lm.utils.objectKeys(this._typeToItem).join(',');
+
+                throw new lm.errors.ConfigurationError(typeErrorMsg);
+            }
+
+
+			/**
+			 * We add an additional stack around every component that's not within a stack anyways.
+			 */
+            if (
+                // If this is a component
+                config.type === 'component' &&
+
+                // and it's not already within a stack
+                !(parent instanceof lm.items.Stack) &&
+
+                // and we have a parent
+                !!parent &&
+
+                // and it's not the topmost item in a new window
+                !(this.isSubWindow === true && parent instanceof lm.items.Root)
+            ) {
+                config = {
+                    type: 'stack',
+                    width: config.width,
+                    height: config.height,
+                    content: [config]
+                };
+            }
+
+            contentItem = new this._typeToItem[config.type](this, config, parent);
+            return contentItem;
+        },
+
+		/**
+		 * Creates a popout window with the specified content and dimensions
+		 *
+		 * @param   {Object|lm.itemsAbstractContentItem} configOrContentItem
+		 * @param   {[Object]} dimensions A map with width, height, left and top
+		 * @param    {[String]} parentId the id of the element this item will be appended to
+		 *                             when popIn is called
+		 * @param    {[Number]} indexInParent The position of this item within its parent element
+	
+		 * @returns {lm.controls.BrowserPopout}
+		 */
+        createPopout: function (configOrContentItem, dimensions, parentId, indexInParent) {
+            var config = configOrContentItem,
+                isItem = configOrContentItem instanceof lm.items.AbstractContentItem,
+                self = this,
+                windowLeft,
+                windowTop,
+                offset,
+                parent,
+                child,
+                browserPopout;
+
+            parentId = parentId || null;
+
+            if (isItem) {
+                config = this.toConfig(configOrContentItem).content;
+                parentId = lm.utils.getUniqueId();
+
+				/**
+				 * If the item is the only component within a stack or for some
+				 * other reason the only child of its parent the parent will be destroyed
+				 * when the child is removed.
+				 *
+				 * In order to support this we move up the tree until we find something
+				 * that will remain after the item is being popped out
+				 */
+                parent = configOrContentItem.parent;
+                child = configOrContentItem;
+                while (parent.contentItems.length === 1 && !parent.isRoot) {
+                    parent = parent.parent;
+                    child = child.parent;
+                }
+
+                parent.addId(parentId);
+                if (isNaN(indexInParent)) {
+                    indexInParent = lm.utils.indexOf(child, parent.contentItems);
+                }
+            } else {
+                if (!(config instanceof Array)) {
+                    config = [config];
+                }
+            }
+
+
+            if (!dimensions && isItem) {
+                windowLeft = window.screenX || window.screenLeft;
+                windowTop = window.screenY || window.screenTop;
+                offset = configOrContentItem.element.offset();
+
+                dimensions = {
+                    left: windowLeft + offset.left,
+                    top: windowTop + offset.top,
+                    width: configOrContentItem.element.width(),
+                    height: configOrContentItem.element.height()
+                };
+            }
+
+            if (!dimensions && !isItem) {
+                dimensions = {
+                    left: window.screenX || window.screenLeft + 20,
+                    top: window.screenY || window.screenTop + 20,
+                    width: 500,
+                    height: 309
+                };
+            }
+
+            if (isItem) {
+                configOrContentItem.remove();
+            }
+
+            browserPopout = new lm.controls.BrowserPopout(config, dimensions, parentId, indexInParent, this);
+
+            browserPopout.on('initialised', function () {
+                self.emit('windowOpened', browserPopout);
+            });
+
+            browserPopout.on('closed', function () {
+                self._$reconcilePopoutWindows();
+            });
+
+            this.openPopouts.push(browserPopout);
+
+            return browserPopout;
+        },
+
+		/**
+		 * Attaches DragListener to any given DOM element
+		 * and turns it into a way of creating new ContentItems
+		 * by 'dragging' the DOM element into the layout
+		 *
+		 * @param   {jQuery DOM element} element
+		 * @param   {Object|Function} itemConfig for the new item to be created, or a function which will provide it
+		 *
+		 * @returns {void}
+		 */
+        createDragSource: function (element, itemConfig) {
+            this.config.settings.constrainDragToContainer = false;
+            var dragSource = new lm.controls.DragSource($(element), itemConfig, this);
+            this._dragSources.push(dragSource);
+
+            return dragSource;
+        },
+
+		/**
+		 * Programmatically selects an item. This deselects
+		 * the currently selected item, selects the specified item
+		 * and emits a selectionChanged event
+		 *
+		 * @param   {lm.item.AbstractContentItem} item#
+		 * @param   {[Boolean]} _$silent Wheather to notify the item of its selection
+		 * @event    selectionChanged
+		 *
+		 * @returns {VOID}
+		 */
+        selectItem: function (item, _$silent) {
+
+            if (this.config.settings.selectionEnabled !== true) {
+                throw new Error('Please set selectionEnabled to true to use this feature');
+            }
+
+            if (item === this.selectedItem) {
+                return;
+            }
+
+            if (this.selectedItem !== null) {
+                this.selectedItem.deselect();
+            }
+
+            if (item && _$silent !== true) {
+                item.select();
+            }
+
+            this.selectedItem = item;
+
+            this.emit('selectionChanged', item);
+        },
+
+		/*************************
+		 * PACKAGE PRIVATE
+		 *************************/
+        _$maximiseItem: function (contentItem) {
+            if (this._maximisedItem !== null) {
+                this._$minimiseItem(this._maximisedItem);
+            }
+            this._maximisedItem = contentItem;
+            this._maximisedItem.addId('__glMaximised');
+            contentItem.element.addClass('lm_maximised');
+            contentItem.element.after(this._maximisePlaceholder);
+            this.root.element.prepend(contentItem.element);
+            contentItem.element.width(this.container.width());
+            contentItem.element.height(this.container.height());
+            contentItem.callDownwards('setSize');
+            this._maximisedItem.emit('maximised');
+            this.emit('stateChanged');
+        },
+
+        _$minimiseItem: function (contentItem) {
+            contentItem.element.removeClass('lm_maximised');
+            contentItem.removeId('__glMaximised');
+            this._maximisePlaceholder.after(contentItem.element);
+            this._maximisePlaceholder.remove();
+            contentItem.parent.callDownwards('setSize');
+            this._maximisedItem = null;
+            contentItem.emit('minimised');
+            this.emit('stateChanged');
+        },
+
+		/**
+		 * This method is used to get around sandboxed iframe restrictions.
+		 * If 'allow-top-navigation' is not specified in the iframe's 'sandbox' attribute
+		 * (as is the case with codepens) the parent window is forbidden from calling certain
+		 * methods on the child, such as window.close() or setting document.location.href.
+		 *
+		 * This prevented GoldenLayout popouts from popping in in codepens. The fix is to call
+		 * _$closeWindow on the child window's gl instance which (after a timeout to disconnect
+		 * the invoking method from the close call) closes itself.
+		 *
+		 * @packagePrivate
+		 *
+		 * @returns {void}
+		 */
+        _$closeWindow: function () {
+            window.setTimeout(function () {
+                window.close();
+            }, 1);
+        },
+
+        _$getArea: function (x, y) {
+            var i, area, smallestSurface = Infinity, mathingArea = null;
+
+            for (i = 0; i < this._itemAreas.length; i++) {
+                area = this._itemAreas[i];
+
+                if (
+                    x > area.x1 &&
+                    x < area.x2 &&
+                    y > area.y1 &&
+                    y < area.y2 &&
+                    smallestSurface > area.surface
+                ) {
+                    smallestSurface = area.surface;
+                    mathingArea = area;
+                }
+            }
+
+            return mathingArea;
+        },
+
+        _$createRootItemAreas: function () {
+            var areaSize = 50;
+            var sides = { y2: 0, x2: 0, y1: 'y2', x1: 'x2' };
+            for (var side in sides) {
+                var area = this.root._$getArea();
+                area.side = side;
+                if (sides[side])
+                    area[side] = area[sides[side]] - areaSize;
+                else
+                    area[side] = areaSize;
+                area.surface = (area.x2 - area.x1) * (area.y2 - area.y1);
+                this._itemAreas.push(area);
+            }
+        },
+
+        _$calculateItemAreas: function () {
+            var i, area, allContentItems = this._getAllContentItems();
+            this._itemAreas = [];
+
+			/**
+			 * If the last item is dragged out, highlight the entire container size to
+			 * allow to re-drop it. allContentItems[ 0 ] === this.root at this point
+			 *
+			 * Don't include root into the possible drop areas though otherwise since it
+			 * will used for every gap in the layout, e.g. splitters
+			 */
+            if (allContentItems.length === 1) {
+                this._itemAreas.push(this.root._$getArea());
+                return;
+            }
+            this._$createRootItemAreas();
+
+            for (i = 0; i < allContentItems.length; i++) {
+
+                if (!(allContentItems[i].isStack)) {
+                    continue;
+                }
+
+                area = allContentItems[i]._$getArea();
+
+                if (area === null) {
+                    continue;
+                } else if (area instanceof Array) {
+                    this._itemAreas = this._itemAreas.concat(area);
+                } else {
+                    this._itemAreas.push(area);
+                    var header = {};
+                    lm.utils.copy(header, area);
+                    lm.utils.copy(header, area.contentItem._contentAreaDimensions.header.highlightArea);
+                    header.surface = (header.x2 - header.x1) * (header.y2 - header.y1);
+                    this._itemAreas.push(header);
+                }
+            }
+        },
+
+		/**
+		 * Takes a contentItem or a configuration and optionally a parent
+		 * item and returns an initialised instance of the contentItem.
+		 * If the contentItem is a function, it is first called
+		 *
+		 * @packagePrivate
+		 *
+		 * @param   {lm.items.AbtractContentItem|Object|Function} contentItemOrConfig
+		 * @param   {lm.items.AbtractContentItem} parent Only necessary when passing in config
+		 *
+		 * @returns {lm.items.AbtractContentItem}
+		 */
+        _$normalizeContentItem: function (contentItemOrConfig, parent) {
+            if (!contentItemOrConfig) {
+                throw new Error('No content item defined');
+            }
+
+            if (lm.utils.isFunction(contentItemOrConfig)) {
+                contentItemOrConfig = contentItemOrConfig();
+            }
+
+            if (contentItemOrConfig instanceof lm.items.AbstractContentItem) {
+                return contentItemOrConfig;
+            }
+
+            if ($.isPlainObject(contentItemOrConfig) && contentItemOrConfig.type) {
+                var newContentItem = this.createContentItem(contentItemOrConfig, parent);
+                newContentItem.callDownwards('_$init');
+                return newContentItem;
+            } else {
+                throw new Error('Invalid contentItem');
+            }
+        },
+
+		/**
+		 * Iterates through the array of open popout windows and removes the ones
+		 * that are effectively closed. This is necessary due to the lack of reliably
+		 * listening for window.close / unload events in a cross browser compatible fashion.
+		 *
+		 * @packagePrivate
+		 *
+		 * @returns {void}
+		 */
+        _$reconcilePopoutWindows: function () {
+            var openPopouts = [], i;
+
+            for (i = 0; i < this.openPopouts.length; i++) {
+                if (this.openPopouts[i].getWindow().closed === false) {
+                    openPopouts.push(this.openPopouts[i]);
+                } else {
+                    this.emit('windowClosed', this.openPopouts[i]);
+                }
+            }
+
+            if (this.openPopouts.length !== openPopouts.length) {
+                this.emit('stateChanged');
+                this.openPopouts = openPopouts;
+            }
+
+        },
+
+		/***************************
+		 * PRIVATE
+		 ***************************/
+		/**
+		 * Returns a flattened array of all content items,
+		 * regardles of level or type
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _getAllContentItems: function () {
+            var allContentItems = [];
+
+            var addChildren = function (contentItem) {
+                allContentItems.push(contentItem);
+
+                if (contentItem.contentItems instanceof Array) {
+                    for (var i = 0; i < contentItem.contentItems.length; i++) {
+                        addChildren(contentItem.contentItems[i]);
+                    }
+                }
+            };
+
+            addChildren(this.root);
+
+            return allContentItems;
+        },
+
+		/**
+		 * Binds to DOM/BOM events on init
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _bindEvents: function () {
+            if (this._isFullPage) {
+                $(window).resize(this._resizeFunction);
+            }
+            $(window).on('unload beforeunload', this._unloadFunction);
+        },
+
+		/**
+		 * Debounces resize events
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _onResize: function () {
+            clearTimeout(this._resizeTimeoutId);
+            this._resizeTimeoutId = setTimeout(lm.utils.fnBind(this.updateSize, this), 100);
+        },
+
+		/**
+		 * Extends the default config with the user specific settings and applies
+		 * derivations. Please note that there's a seperate method (AbstractContentItem._extendItemNode)
+		 * that deals with the extension of item configs
+		 *
+		 * @param   {Object} config
+		 * @static
+		 * @returns {Object} config
+		 */
+        _createConfig: function (config) {
+            var windowConfigKey = lm.utils.getQueryStringParam('gl-window');
+
+            if (windowConfigKey) {
+                this.isSubWindow = true;
+                config = localStorage.getItem(windowConfigKey);
+                config = JSON.parse(config);
+                config = (new lm.utils.ConfigMinifier()).unminifyConfig(config);
+                localStorage.removeItem(windowConfigKey);
+            }
+
+            config = $.extend(true, {}, lm.config.defaultConfig, config);
+
+            var nextNode = function (node) {
+                for (var key in node) {
+                    if (key !== 'props' && typeof node[key] === 'object') {
+                        nextNode(node[key]);
+                    }
+                    else if (key === 'type' && node[key] === 'react-component') {
+                        node.type = 'component';
+                        node.componentName = 'lm-react-component';
+                    }
+                }
+            }
+
+            nextNode(config);
+
+            if (config.settings.hasHeaders === false) {
+                config.dimensions.headerHeight = 0;
+            }
+
+            return config;
+        },
+
+		/**
+		 * This is executed when GoldenLayout detects that it is run
+		 * within a previously opened popout window.
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _adjustToWindowMode: function () {
+            var popInButton = $('<div class="lm_popin" title="' + this.config.labels.popin + '">' +
+                '<div class="lm_icon"></div>' +
+                '<div class="lm_bg"></div>' +
+                '</div>');
+
+            popInButton.click(lm.utils.fnBind(function () {
+                this.emit('popIn');
+            }, this));
+
+            document.title = lm.utils.stripTags(this.config.content[0].title);
+
+            $('head').append($('body link, body style, template, .gl_keep'));
+
+            this.container = $('body')
+                .html('')
+                .css('visibility', 'visible')
+                .append(popInButton);
+
+			/*
+			 * This seems a bit pointless, but actually causes a reflow/re-evaluation getting around
+			 * slickgrid's "Cannot find stylesheet." bug in chrome
+			 */
+            var x = document.body.offsetHeight; // jshint ignore:line
+
+			/*
+			 * Expose this instance on the window object
+			 * to allow the opening window to interact with
+			 * it
+			 */
+            window.__glInstance = this;
+        },
+
+		/**
+		 * Creates Subwindows (if there are any). Throws an error
+		 * if popouts are blocked.
+		 *
+		 * @returns {void}
+		 */
+        _createSubWindows: function () {
+            var i, popout;
+
+            for (i = 0; i < this.config.openPopouts.length; i++) {
+                popout = this.config.openPopouts[i];
+
+                this.createPopout(
+                    popout.content,
+                    popout.dimensions,
+                    popout.parentId,
+                    popout.indexInParent
+                );
+            }
+        },
+
+		/**
+		 * Determines what element the layout will be created in
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _setContainer: function () {
+            var container = $(this.container || 'body');
+
+            if (container.length === 0) {
+                throw new Error('GoldenLayout container not found');
+            }
+
+            if (container.length > 1) {
+                throw new Error('GoldenLayout more than one container element specified');
+            }
+
+            if (container[0] === document.body) {
+                this._isFullPage = true;
+
+                $('html, body').css({
+                    height: '100%',
+                    margin: 0,
+                    padding: 0,
+                    overflow: 'hidden'
+                });
+            }
+
+            this.container = container;
+        },
+
+		/**
+		 * Kicks of the initial, recursive creation chain
+		 *
+		 * @param   {Object} config GoldenLayout Config
+		 *
+		 * @returns {void}
+		 */
+        _create: function (config) {
+            var errorMsg;
+
+            if (!(config.content instanceof Array)) {
+                if (config.content === undefined) {
+                    errorMsg = 'Missing setting \'content\' on top level of configuration';
+                } else {
+                    errorMsg = 'Configuration parameter \'content\' must be an array';
+                }
+
+                throw new lm.errors.ConfigurationError(errorMsg, config);
+            }
+
+            if (config.content.length > 1) {
+                errorMsg = 'Top level content can\'t contain more then one element.';
+                throw new lm.errors.ConfigurationError(errorMsg, config);
+            }
+
+            this.root = new lm.items.Root(this, { content: config.content }, this.container);
+            this.root.callDownwards('_$init');
+
+            if (config.maximisedItemId === '__glMaximised') {
+                this.root.getItemsById(config.maximisedItemId)[0].toggleMaximise();
+            }
+        },
+
+		/**
+		 * Called when the window is closed or the user navigates away
+		 * from the page
+		 *
+		 * @returns {void}
+		 */
+        _onUnload: function () {
+            if (this.config.settings.closePopoutsOnUnload === true) {
+                for (var i = 0; i < this.openPopouts.length; i++) {
+                    this.openPopouts[i].close();
+                }
+            }
+        },
+
+		/**
+		 * Adjusts the number of columns to be lower to fit the screen and still maintain minItemWidth.
+		 *
+		 * @returns {void}
+		 */
+        _adjustColumnsResponsive: function () {
+
+            // If there is no min width set, or not content items, do nothing.
+            if (!this._useResponsiveLayout() || this._updatingColumnsResponsive || !this.config.dimensions || !this.config.dimensions.minItemWidth || this.root.contentItems.length === 0 || !this.root.contentItems[0].isRow) {
+                this._firstLoad = false;
+                return;
+            }
+
+            this._firstLoad = false;
+
+            // If there is only one column, do nothing.
+            var columnCount = this.root.contentItems[0].contentItems.length;
+            if (columnCount <= 1) {
+                return;
+            }
+
+            // If they all still fit, do nothing.
+            var minItemWidth = this.config.dimensions.minItemWidth;
+            var totalMinWidth = columnCount * minItemWidth;
+            if (totalMinWidth <= this.width) {
+                return;
+            }
+
+            // Prevent updates while it is already happening.
+            this._updatingColumnsResponsive = true;
+
+            // Figure out how many columns to stack, and put them all in the first stack container.
+            var finalColumnCount = Math.max(Math.floor(this.width / minItemWidth), 1);
+            var stackColumnCount = columnCount - finalColumnCount;
+
+            var rootContentItem = this.root.contentItems[0];
+            var firstStackContainer = this._findAllStackContainers()[0];
+            for (var i = 0; i < stackColumnCount; i++) {
+                // Stack from right.
+                var column = rootContentItem.contentItems[rootContentItem.contentItems.length - 1];
+                this._addChildContentItemsToContainer(firstStackContainer, column);
+            }
+
+            this._updatingColumnsResponsive = false;
+        },
+
+		/**
+		 * Determines if responsive layout should be used.
+		 *
+		 * @returns {bool} - True if responsive layout should be used; otherwise false.
+		 */
+        _useResponsiveLayout: function () {
+            return this.config.settings && (this.config.settings.responsiveMode == 'always' || (this.config.settings.responsiveMode == 'onload' && this._firstLoad));
+        },
+
+		/**
+		 * Adds all children of a node to another container recursively.
+		 * @param {object} container - Container to add child content items to.
+		 * @param {object} node - Node to search for content items.
+		 * @returns {void}
+		 */
+        _addChildContentItemsToContainer: function (container, node) {
+            if (node.type === 'stack') {
+                node.contentItems.forEach(function (item) {
+                    container.addChild(item);
+                    node.removeChild(item, true);
+                });
+            }
+            else {
+                node.contentItems.forEach(lm.utils.fnBind(function (item) {
+                    this._addChildContentItemsToContainer(container, item);
+                }, this));
+            }
+        },
+
+		/**
+		 * Finds all the stack containers.
+		 * @returns {array} - The found stack containers.
+		 */
+        _findAllStackContainers: function () {
+            var stackContainers = [];
+            this._findAllStackContainersRecursive(stackContainers, this.root);
+
+            return stackContainers;
+        },
+
+		/**
+		 * Finds all the stack containers.
+		 *
+		 * @param {array} - Set of containers to populate.
+		 * @param {object} - Current node to process.
+		 *
+		 * @returns {void}
+		 */
+        _findAllStackContainersRecursive: function (stackContainers, node) {
+            node.contentItems.forEach(lm.utils.fnBind(function (item) {
+                if (item.type == 'stack') {
+                    stackContainers.push(item);
+                }
+                else if (!item.isComponent) {
+                    this._findAllStackContainersRecursive(stackContainers, item);
+                }
+            }, this));
+        }
+    });
+
+	/**
+	 * Expose the Layoutmanager as the single entrypoint using UMD
+	 */
+    (function () {
+        /* global define */
+        if (typeof define === 'function' && define.amd) {
+            define(['jquery'], function (jquery) {
+                $ = jquery;
+                return lm.LayoutManager;
+            }); // jshint ignore:line
+        } else if (typeof exports === 'object') {
+            module.exports = lm.LayoutManager;
+        } else {
+            window.GoldenLayout = lm.LayoutManager;
+        }
+    })();
+
+    lm.config.itemDefaultConfig = {
+        isClosable: true,
+        reorderEnabled: true,
+        title: ''
+    };
+    lm.config.defaultConfig = {
+        openPopouts: [],
+        settings: {
+            hasHeaders: true,
+            constrainDragToContainer: true,
+            reorderEnabled: true,
+            selectionEnabled: false,
+            popoutWholeStack: false,
+            blockedPopoutsThrowError: true,
+            closePopoutsOnUnload: true,
+            showPopoutIcon: true,
+            showMaximiseIcon: true,
+            showCloseIcon: true,
+            responsiveMode: 'onload', // Can be onload, always, or none.
+            tabOverlapAllowance: 0, // maximum pixel overlap per tab
+            reorderOnTabMenuClick: true,
+            tabControlOffset: 10
+        },
+        dimensions: {
+            borderWidth: 5,
+            borderGrabWidth: 15,
+            minItemHeight: 10,
+            minItemWidth: 10,
+            headerHeight: 20,
+            dragProxyWidth: 300,
+            dragProxyHeight: 200
+        },
+        labels: {
+            close: 'close',
+            maximise: 'maximise',
+            minimise: 'minimise',
+            popout: 'open in new window',
+            popin: 'pop in',
+            tabDropdown: 'additional tabs'
+        }
+    };
+
+    lm.container.ItemContainer = function (config, parent, layoutManager) {
+        lm.utils.EventEmitter.call(this);
+
+        this.width = null;
+        this.height = null;
+        this.title = config.componentName;
+        this.parent = parent;
+        this.layoutManager = layoutManager;
+        this.isHidden = false;
+
+        this._config = config;
+        this._element = $([
+            '<div class="lm_item_container">',
+            '<div class="lm_content"></div>',
+            '</div>'
+        ].join(''));
+
+        this._contentElement = this._element.find('.lm_content');
+    };
+
+    lm.utils.copy(lm.container.ItemContainer.prototype, {
+
+		/**
+		 * Get the inner DOM element the container's content
+		 * is intended to live in
+		 *
+		 * @returns {DOM element}
+		 */
+        getElement: function () {
+            return this._contentElement;
+        },
+
+		/**
+		 * Hide the container. Notifies the containers content first
+		 * and then hides the DOM node. If the container is already hidden
+		 * this should have no effect
+		 *
+		 * @returns {void}
+		 */
+        hide: function () {
+            this.emit('hide');
+            this.isHidden = true;
+            this._element.hide();
+        },
+
+		/**
+		 * Shows a previously hidden container. Notifies the
+		 * containers content first and then shows the DOM element.
+		 * If the container is already visible this has no effect.
+		 *
+		 * @returns {void}
+		 */
+        show: function () {
+            this.emit('show');
+            this.isHidden = false;
+            this._element.show();
+            // call shown only if the container has a valid size
+            if (this.height != 0 || this.width != 0) {
+                this.emit('shown');
+            }
+        },
+
+		/**
+		 * Set the size from within the container. Traverses up
+		 * the item tree until it finds a row or column element
+		 * and resizes its items accordingly.
+		 *
+		 * If this container isn't a descendant of a row or column
+		 * it returns false
+		 * @todo  Rework!!!
+		 * @param {Number} width  The new width in pixel
+		 * @param {Number} height The new height in pixel
+		 *
+		 * @returns {Boolean} resizeSuccesful
+		 */
+        setSize: function (width, height) {
+            var rowOrColumn = this.parent,
+                rowOrColumnChild = this,
+                totalPixel,
+                percentage,
+                direction,
+                newSize,
+                delta,
+                i;
+
+            while (!rowOrColumn.isColumn && !rowOrColumn.isRow) {
+                rowOrColumnChild = rowOrColumn;
+                rowOrColumn = rowOrColumn.parent;
+
+
+				/**
+				 * No row or column has been found
+				 */
+                if (rowOrColumn.isRoot) {
+                    return false;
+                }
+            }
+
+            direction = rowOrColumn.isColumn ? "height" : "width";
+            newSize = direction === "height" ? height : width;
+
+            totalPixel = this[direction] * (1 / (rowOrColumnChild.config[direction] / 100));
+            percentage = (newSize / totalPixel) * 100;
+            delta = (rowOrColumnChild.config[direction] - percentage) / (rowOrColumn.contentItems.length - 1);
+
+            for (i = 0; i < rowOrColumn.contentItems.length; i++) {
+                if (rowOrColumn.contentItems[i] === rowOrColumnChild) {
+                    rowOrColumn.contentItems[i].config[direction] = percentage;
+                } else {
+                    rowOrColumn.contentItems[i].config[direction] += delta;
+                }
+            }
+
+            rowOrColumn.callDownwards('setSize');
+
+            return true;
+        },
+
+		/**
+		 * Closes the container if it is closable. Can be called by
+		 * both the component within at as well as the contentItem containing
+		 * it. Emits a close event before the container itself is closed.
+		 *
+		 * @returns {void}
+		 */
+        close: function () {
+            if (this._config.isClosable) {
+                this.emit('close');
+                this.parent.close();
+            }
+        },
+
+		/**
+		 * Returns the current state object
+		 *
+		 * @returns {Object} state
+		 */
+        getState: function () {
+            return this._config.componentState;
+        },
+
+		/**
+		 * Merges the provided state into the current one
+		 *
+		 * @param   {Object} state
+		 *
+		 * @returns {void}
+		 */
+        extendState: function (state) {
+            this.setState($.extend(true, this.getState(), state));
+        },
+
+		/**
+		 * Notifies the layout manager of a stateupdate
+		 *
+		 * @param {serialisable} state
+		 */
+        setState: function (state) {
+            this._config.componentState = state;
+            this.parent.emitBubblingEvent('stateChanged');
+        },
+
+		/**
+		 * Set's the components title
+		 *
+		 * @param {String} title
+		 */
+        setTitle: function (title) {
+            this.parent.setTitle(title);
+        },
+
+		/**
+		 * Set's the containers size. Called by the container's component.
+		 * To set the size programmatically from within the container please
+		 * use the public setSize method
+		 *
+		 * @param {[Int]} width  in px
+		 * @param {[Int]} height in px
+		 *
+		 * @returns {void}
+		 */
+        _$setSize: function (width, height) {
+            if (width !== this.width || height !== this.height) {
+                this.width = width;
+                this.height = height;
+                var cl = this._contentElement[0];
+                var hdelta = cl.offsetWidth - cl.clientWidth;
+                var vdelta = cl.offsetHeight - cl.clientHeight;
+                this._contentElement.width(this.width - hdelta)
+                    .height(this.height - vdelta);
+                this.emit('resize');
+            }
+        }
+    });
+
+	/**
+	 * Pops a content item out into a new browser window.
+	 * This is achieved by
+	 *
+	 *    - Creating a new configuration with the content item as root element
+	 *    - Serializing and minifying the configuration
+	 *    - Opening the current window's URL with the configuration as a GET parameter
+	 *    - GoldenLayout when opened in the new window will look for the GET parameter
+	 *      and use it instead of the provided configuration
+	 *
+	 * @param {Object} config GoldenLayout item config
+	 * @param {Object} dimensions A map with width, height, top and left
+	 * @param {String} parentId The id of the element the item will be appended to on popIn
+	 * @param {Number} indexInParent The position of this element within its parent
+	 * @param {lm.LayoutManager} layoutManager
+	 */
+    lm.controls.BrowserPopout = function (config, dimensions, parentId, indexInParent, layoutManager) {
+        lm.utils.EventEmitter.call(this);
+        this.isInitialised = false;
+
+        this._config = config;
+        this._dimensions = dimensions;
+        this._parentId = parentId;
+        this._indexInParent = indexInParent;
+        this._layoutManager = layoutManager;
+        this._popoutWindow = null;
+        this._id = null;
+        this._createWindow();
+    };
+
+    lm.utils.copy(lm.controls.BrowserPopout.prototype, {
+
+        toConfig: function () {
+            if (this.isInitialised === false) {
+                throw new Error('Can\'t create config, layout not yet initialised');
+                return;
+            }
+            return {
+                dimensions: {
+                    width: this.getGlInstance().width,
+                    height: this.getGlInstance().height,
+                    left: this._popoutWindow.screenX || this._popoutWindow.screenLeft,
+                    top: this._popoutWindow.screenY || this._popoutWindow.screenTop
+                },
+                content: this.getGlInstance().toConfig().content,
+                parentId: this._parentId,
+                indexInParent: this._indexInParent
+            };
+        },
+
+        getGlInstance: function () {
+            return this._popoutWindow.__glInstance;
+        },
+
+        getWindow: function () {
+            return this._popoutWindow;
+        },
+
+        close: function () {
+            if (this.getGlInstance()) {
+                this.getGlInstance()._$closeWindow();
+            } else {
+                try {
+                    this.getWindow().close();
+                } catch (e) {
+                }
+            }
+        },
+
+		/**
+		 * Returns the popped out item to its original position. If the original
+		 * parent isn't available anymore it falls back to the layout's topmost element
+		 */
+        popIn: function () {
+            var childConfig,
+                parentItem,
+                index = this._indexInParent;
+
+            if (this._parentId) {
+
+				/*
+				 * The $.extend call seems a bit pointless, but it's crucial to
+				 * copy the config returned by this.getGlInstance().toConfig()
+				 * onto a new object. Internet Explorer keeps the references
+				 * to objects on the child window, resulting in the following error
+				 * once the child window is closed:
+				 *
+				 * The callee (server [not server application]) is not available and disappeared
+				 */
+                childConfig = $.extend(true, {}, this.getGlInstance().toConfig()).content[0];
+                parentItem = this._layoutManager.root.getItemsById(this._parentId)[0];
+
+				/*
+				 * Fallback if parentItem is not available. Either add it to the topmost
+				 * item or make it the topmost item if the layout is empty
+				 */
+                if (!parentItem) {
+                    if (this._layoutManager.root.contentItems.length > 0) {
+                        parentItem = this._layoutManager.root.contentItems[0];
+                    } else {
+                        parentItem = this._layoutManager.root;
+                    }
+                    index = 0;
+                }
+            }
+
+            parentItem.addChild(childConfig, this._indexInParent);
+            this.close();
+        },
+
+		/**
+		 * Creates the URL and window parameter
+		 * and opens a new window
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _createWindow: function () {
+            var checkReadyInterval,
+                url = this._createUrl(),
+
+				/**
+				 * Bogus title to prevent re-usage of existing window with the
+				 * same title. The actual title will be set by the new window's
+				 * GoldenLayout instance if it detects that it is in subWindowMode
+				 */
+                title = Math.floor(Math.random() * 1000000).toString(36),
+
+				/**
+				 * The options as used in the window.open string
+				 */
+                options = this._serializeWindowOptions({
+                    width: this._dimensions.width,
+                    height: this._dimensions.height,
+                    innerWidth: this._dimensions.width,
+                    innerHeight: this._dimensions.height,
+                    menubar: 'no',
+                    toolbar: 'no',
+                    location: 'no',
+                    personalbar: 'no',
+                    resizable: 'yes',
+                    scrollbars: 'no',
+                    status: 'no'
+                });
+
+            this._popoutWindow = window.open(url, title, options);
+
+            if (!this._popoutWindow) {
+                if (this._layoutManager.config.settings.blockedPopoutsThrowError === true) {
+                    var error = new Error('Popout blocked');
+                    error.type = 'popoutBlocked';
+                    throw error;
+                } else {
+                    return;
+                }
+            }
+
+            $(this._popoutWindow)
+                .on('load', lm.utils.fnBind(this._positionWindow, this))
+                .on('unload beforeunload', lm.utils.fnBind(this._onClose, this));
+
+			/**
+			 * Polling the childwindow to find out if GoldenLayout has been initialised
+			 * doesn't seem optimal, but the alternatives - adding a callback to the parent
+			 * window or raising an event on the window object - both would introduce knowledge
+			 * about the parent to the child window which we'd rather avoid
+			 */
+            checkReadyInterval = setInterval(lm.utils.fnBind(function () {
+                if (this._popoutWindow.__glInstance && this._popoutWindow.__glInstance.isInitialised) {
+                    this._onInitialised();
+                    clearInterval(checkReadyInterval);
+                }
+            }, this), 10);
+        },
+
+		/**
+		 * Serialises a map of key:values to a window options string
+		 *
+		 * @param   {Object} windowOptions
+		 *
+		 * @returns {String} serialised window options
+		 */
+        _serializeWindowOptions: function (windowOptions) {
+            var windowOptionsString = [], key;
+
+            for (key in windowOptions) {
+                windowOptionsString.push(key + '=' + windowOptions[key]);
+            }
+
+            return windowOptionsString.join(',');
+        },
+
+		/**
+		 * Creates the URL for the new window, including the
+		 * config GET parameter
+		 *
+		 * @returns {String} URL
+		 */
+        _createUrl: function () {
+            var config = { content: this._config },
+                storageKey = 'gl-window-config-' + lm.utils.getUniqueId(),
+                urlParts;
+
+            config = (new lm.utils.ConfigMinifier()).minifyConfig(config);
+
+            try {
+                localStorage.setItem(storageKey, JSON.stringify(config));
+            } catch (e) {
+                throw new Error('Error while writing to localStorage ' + e.toString());
+            }
+
+            urlParts = document.location.href.split('?');
+
+            // URL doesn't contain GET-parameters
+            if (urlParts.length === 1) {
+                return urlParts[0] + '?gl-window=' + storageKey;
+
+                // URL contains GET-parameters
+            } else {
+                return document.location.href + '&gl-window=' + storageKey;
+            }
+        },
+
+		/**
+		 * Move the newly created window roughly to
+		 * where the component used to be.
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _positionWindow: function () {
+            this._popoutWindow.moveTo(this._dimensions.left, this._dimensions.top);
+            this._popoutWindow.focus();
+        },
+
+		/**
+		 * Callback when the new window is opened and the GoldenLayout instance
+		 * within it is initialised
+		 *
+		 * @returns {void}
+		 */
+        _onInitialised: function () {
+            this.isInitialised = true;
+            this.getGlInstance().on('popIn', this.popIn, this);
+            this.emit('initialised');
+        },
+
+		/**
+		 * Invoked 50ms after the window unload event
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _onClose: function () {
+            setTimeout(lm.utils.fnBind(this.emit, this, ['closed']), 50);
+        }
+    });
+	/**
+	 * This class creates a temporary container
+	 * for the component whilst it is being dragged
+	 * and handles drag events
+	 *
+	 * @constructor
+	 * @private
+	 *
+	 * @param {Number} x              The initial x position
+	 * @param {Number} y              The initial y position
+	 * @param {lm.utils.DragListener} dragListener
+	 * @param {lm.LayoutManager} layoutManager
+	 * @param {lm.item.AbstractContentItem} contentItem
+	 * @param {lm.item.AbstractContentItem} originalParent
+	 */
+    lm.controls.DragProxy = function (x, y, dragListener, layoutManager, contentItem, originalParent) {
+
+        lm.utils.EventEmitter.call(this);
+
+        this._dragListener = dragListener;
+        this._layoutManager = layoutManager;
+        this._contentItem = contentItem;
+        this._originalParent = originalParent;
+
+        this._area = null;
+        this._lastValidArea = null;
+
+        this._dragListener.on('drag', this._onDrag, this);
+        this._dragListener.on('dragStop', this._onDrop, this);
+
+        this.element = $(lm.controls.DragProxy._template);
+        if (originalParent && originalParent._side) {
+            this._sided = originalParent._sided;
+            this.element.addClass('lm_' + originalParent._side);
+            if (['right', 'bottom'].indexOf(originalParent._side) >= 0)
+                this.element.find('.lm_content').after(this.element.find('.lm_header'));
+        }
+        this.element.css({ left: x, top: y });
+        this.element.find('.lm_tab').attr('title', lm.utils.stripTags(this._contentItem.config.title));
+        this.element.find('.lm_title').html(this._contentItem.config.title);
+        this.childElementContainer = this.element.find('.lm_content');
+        this.childElementContainer.append(contentItem.element);
+
+        this._updateTree();
+        this._layoutManager._$calculateItemAreas();
+        this._setDimensions();
+
+        $(document.body).append(this.element);
+
+        var offset = this._layoutManager.container.offset();
+
+        this._minX = offset.left;
+        this._minY = offset.top;
+        this._maxX = this._layoutManager.container.width() + this._minX;
+        this._maxY = this._layoutManager.container.height() + this._minY;
+        this._width = this.element.width();
+        this._height = this.element.height();
+
+        this._setDropPosition(x, y);
+    };
+
+    lm.controls.DragProxy._template = '<div class="lm_dragProxy">' +
+        '<div class="lm_header">' +
+        '<ul class="lm_tabs">' +
+        '<li class="lm_tab lm_active"><i class="lm_left"></i>' +
+        '<span class="lm_title"></span>' +
+        '<i class="lm_right"></i></li>' +
+        '</ul>' +
+        '</div>' +
+        '<div class="lm_content"></div>' +
+        '</div>';
+
+    lm.utils.copy(lm.controls.DragProxy.prototype, {
+
+		/**
+		 * Callback on every mouseMove event during a drag. Determines if the drag is
+		 * still within the valid drag area and calls the layoutManager to highlight the
+		 * current drop area
+		 *
+		 * @param   {Number} offsetX The difference from the original x position in px
+		 * @param   {Number} offsetY The difference from the original y position in px
+		 * @param   {jQuery DOM event} event
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _onDrag: function (offsetX, offsetY, event) {
+
+            event = event.originalEvent && event.originalEvent.touches ? event.originalEvent.touches[0] : event;
+
+            var x = event.pageX,
+                y = event.pageY,
+                isWithinContainer = x > this._minX && x < this._maxX && y > this._minY && y < this._maxY;
+
+            if (!isWithinContainer && this._layoutManager.config.settings.constrainDragToContainer === true) {
+                return;
+            }
+
+            this._setDropPosition(x, y);
+        },
+
+		/**
+		 * Sets the target position, highlighting the appropriate area
+		 *
+		 * @param   {Number} x The x position in px
+		 * @param   {Number} y The y position in px
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _setDropPosition: function (x, y) {
+            this.element.css({ left: x, top: y });
+            this._area = this._layoutManager._$getArea(x, y);
+
+            if (this._area !== null) {
+                this._lastValidArea = this._area;
+                this._area.contentItem._$highlightDropZone(x, y, this._area);
+            }
+        },
+
+		/**
+		 * Callback when the drag has finished. Determines the drop area
+		 * and adds the child to it
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _onDrop: function () {
+            this._layoutManager.dropTargetIndicator.hide();
+
+			/*
+			 * Valid drop area found
+			 */
+            if (this._area !== null) {
+                this._area.contentItem._$onDrop(this._contentItem, this._area);
+
+				/**
+				 * No valid drop area available at present, but one has been found before.
+				 * Use it
+				 */
+            } else if (this._lastValidArea !== null) {
+                this._lastValidArea.contentItem._$onDrop(this._contentItem, this._lastValidArea);
+
+				/**
+				 * No valid drop area found during the duration of the drag. Return
+				 * content item to its original position if a original parent is provided.
+				 * (Which is not the case if the drag had been initiated by createDragSource)
+				 */
+            } else if (this._originalParent) {
+                this._originalParent.addChild(this._contentItem);
+
+				/**
+				 * The drag didn't ultimately end up with adding the content item to
+				 * any container. In order to ensure clean up happens, destroy the
+				 * content item.
+				 */
+            } else {
+                this._contentItem._$destroy();
+            }
+
+            this.element.remove();
+
+            this._layoutManager.emit('itemDropped', this._contentItem);
+        },
+
+		/**
+		 * Removes the item from its original position within the tree
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _updateTree: function () {
+
+			/**
+			 * parent is null if the drag had been initiated by a external drag source
+			 */
+            if (this._contentItem.parent) {
+                this._contentItem.parent.removeChild(this._contentItem, true);
+            }
+
+            this._contentItem._$setParent(this);
+        },
+
+		/**
+		 * Updates the Drag Proxie's dimensions
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _setDimensions: function () {
+            var dimensions = this._layoutManager.config.dimensions,
+                width = dimensions.dragProxyWidth,
+                height = dimensions.dragProxyHeight;
+
+            this.element.width(width);
+            this.element.height(height);
+            width -= (this._sided ? dimensions.headerHeight : 0);
+            height -= (!this._sided ? dimensions.headerHeight : 0);
+            this.childElementContainer.width(width);
+            this.childElementContainer.height(height);
+            this._contentItem.element.width(width);
+            this._contentItem.element.height(height);
+            this._contentItem.callDownwards('_$show');
+            this._contentItem.callDownwards('setSize');
+        }
+    });
+
+	/**
+	 * Allows for any DOM item to create a component on drag
+	 * start tobe dragged into the Layout
+	 *
+	 * @param {jQuery element} element
+	 * @param {Object} itemConfig the configuration for the contentItem that will be created
+	 * @param {LayoutManager} layoutManager
+	 *
+	 * @constructor
+	 */
+    lm.controls.DragSource = function (element, itemConfig, layoutManager) {
+        this._element = element;
+        this._itemConfig = itemConfig;
+        this._layoutManager = layoutManager;
+        this._dragListener = null;
+
+        this._createDragListener();
+    };
+
+    lm.utils.copy(lm.controls.DragSource.prototype, {
+
+		/**
+		 * Called initially and after every drag
+		 *
+		 * @returns {void}
+		 */
+        _createDragListener: function () {
+            if (this._dragListener !== null) {
+                this._dragListener.destroy();
+            }
+
+            this._dragListener = new lm.utils.DragListener(this._element);
+            this._dragListener.on('dragStart', this._onDragStart, this);
+            this._dragListener.on('dragStop', this._createDragListener, this);
+        },
+
+		/**
+		 * Callback for the DragListener's dragStart event
+		 *
+		 * @param   {int} x the x position of the mouse on dragStart
+		 * @param   {int} y the x position of the mouse on dragStart
+		 *
+		 * @returns {void}
+		 */
+        _onDragStart: function (x, y) {
+            var itemConfig = this._itemConfig;
+            if (lm.utils.isFunction(itemConfig)) {
+                itemConfig = itemConfig();
+            }
+            var contentItem = this._layoutManager._$normalizeContentItem($.extend(true, {}, itemConfig)),
+                dragProxy = new lm.controls.DragProxy(x, y, this._dragListener, this._layoutManager, contentItem, null);
+
+            this._layoutManager.transitionIndicator.transitionElements(this._element, dragProxy.element);
+        }
+    });
+
+    lm.controls.DropTargetIndicator = function () {
+        this.element = $(lm.controls.DropTargetIndicator._template);
+        $(document.body).append(this.element);
+    };
+
+    lm.controls.DropTargetIndicator._template = '<div class="lm_dropTargetIndicator"><div class="lm_inner"></div></div>';
+
+    lm.utils.copy(lm.controls.DropTargetIndicator.prototype, {
+        destroy: function () {
+            this.element.remove();
+        },
+
+        highlight: function (x1, y1, x2, y2) {
+            this.highlightArea({ x1: x1, y1: y1, x2: x2, y2: y2 });
+        },
+
+        highlightArea: function (area) {
+            this.element.css({
+                left: area.x1,
+                top: area.y1,
+                width: area.x2 - area.x1,
+                height: area.y2 - area.y1
+            }).show();
+        },
+
+        hide: function () {
+            this.element.hide();
+        }
+    });
+	/**
+	 * This class represents a header above a Stack ContentItem.
+	 *
+	 * @param {lm.LayoutManager} layoutManager
+	 * @param {lm.item.AbstractContentItem} parent
+	 */
+    lm.controls.Header = function (layoutManager, parent) {
+        lm.utils.EventEmitter.call(this);
+
+        this.layoutManager = layoutManager;
+        this.element = $(lm.controls.Header._template);
+
+        if (this.layoutManager.config.settings.selectionEnabled === true) {
+            this.element.addClass('lm_selectable');
+            this.element.on('click touchstart', lm.utils.fnBind(this._onHeaderClick, this));
+        }
+
+        this.tabsContainer = this.element.find('.lm_tabs');
+        this.tabDropdownContainer = this.element.find('.lm_tabdropdown_list');
+        this.tabDropdownContainer.hide();
+        this.controlsContainer = this.element.find('.lm_controls');
+        this.parent = parent;
+        this.parent.on('resize', this._updateTabSizes, this);
+        this.tabs = [];
+        this.activeContentItem = null;
+        this.closeButton = null;
+        this.tabDropdownButton = null;
+        this.hideAdditionalTabsDropdown = lm.utils.fnBind(this._hideAdditionalTabsDropdown, this);
+        $(document).mouseup(this.hideAdditionalTabsDropdown);
+
+        this._lastVisibleTabIndex = -1;
+        this._tabControlOffset = this.layoutManager.config.settings.tabControlOffset;
+        this._createControls();
+    };
+
+    lm.controls.Header._template = [
+        '<div class="lm_header">',
+        '<ul class="lm_tabs"></ul>',
+        '<ul class="lm_controls"></ul>',
+        '<ul class="lm_tabdropdown_list"></ul>',
+        '</div>'
+    ].join('');
+
+    lm.utils.copy(lm.controls.Header.prototype, {
+
+		/**
+		 * Creates a new tab and associates it with a contentItem
+		 *
+		 * @param    {lm.item.AbstractContentItem} contentItem
+		 * @param    {Integer} index The position of the tab
+		 *
+		 * @returns {void}
+		 */
+        createTab: function (contentItem, index) {
+            var tab, i;
+
+            //If there's already a tab relating to the
+            //content item, don't do anything
+            for (i = 0; i < this.tabs.length; i++) {
+                if (this.tabs[i].contentItem === contentItem) {
+                    return;
+                }
+            }
+
+            tab = new lm.controls.Tab(this, contentItem);
+
+            if (this.tabs.length === 0) {
+                this.tabs.push(tab);
+                this.tabsContainer.append(tab.element);
+                return;
+            }
+
+            if (index === undefined) {
+                index = this.tabs.length;
+            }
+
+            if (index > 0) {
+                this.tabs[index - 1].element.after(tab.element);
+            } else {
+                this.tabs[0].element.before(tab.element);
+            }
+
+            this.tabs.splice(index, 0, tab);
+            this._updateTabSizes();
+        },
+
+		/**
+		 * Finds a tab based on the contentItem its associated with and removes it.
+		 *
+		 * @param    {lm.item.AbstractContentItem} contentItem
+		 *
+		 * @returns {void}
+		 */
+        removeTab: function (contentItem) {
+            for (var i = 0; i < this.tabs.length; i++) {
+                if (this.tabs[i].contentItem === contentItem) {
+                    this.tabs[i]._$destroy();
+                    this.tabs.splice(i, 1);
+                    return;
+                }
+            }
+
+            throw new Error('contentItem is not controlled by this header');
+        },
+
+		/**
+		 * The programmatical equivalent of clicking a Tab.
+		 *
+		 * @param {lm.item.AbstractContentItem} contentItem
+		 */
+        setActiveContentItem: function (contentItem) {
+            var i, j, isActive, activeTab;
+
+            for (i = 0; i < this.tabs.length; i++) {
+                isActive = this.tabs[i].contentItem === contentItem;
+                this.tabs[i].setActive(isActive);
+                if (isActive === true) {
+                    this.activeContentItem = contentItem;
+                    this.parent.config.activeItemIndex = i;
+                }
+            }
+
+            if (this.layoutManager.config.settings.reorderOnTabMenuClick) {
+				/**
+				 * If the tab selected was in the dropdown, move everything down one to make way for this one to be the first.
+				 * This will make sure the most used tabs stay visible.
+				 */
+                if (this._lastVisibleTabIndex !== -1 && this.parent.config.activeItemIndex > this._lastVisibleTabIndex) {
+                    activeTab = this.tabs[this.parent.config.activeItemIndex];
+                    for (j = this.parent.config.activeItemIndex; j > 0; j--) {
+                        this.tabs[j] = this.tabs[j - 1];
+                    }
+                    this.tabs[0] = activeTab;
+                    this.parent.config.activeItemIndex = 0;
+                }
+            }
+
+            this._updateTabSizes();
+            this.parent.emitBubblingEvent('stateChanged');
+        },
+
+		/**
+		 * Programmatically operate with header position.
+		 *
+		 * @param {string} position one of ('top','left','right','bottom') to set or empty to get it.
+		 *
+		 * @returns {string} previous header position
+		 */
+        position: function (position) {
+            var previous = this.parent._header.show;
+            if (previous && !this.parent._side)
+                previous = 'top';
+            if (position !== undefined && this.parent._header.show != position) {
+                this.parent._header.show = position;
+                this.parent._setupHeaderPosition();
+            }
+            return previous;
+        },
+
+		/**
+		 * Programmatically set closability.
+		 *
+		 * @package private
+		 * @param {Boolean} isClosable Whether to enable/disable closability.
+		 *
+		 * @returns {Boolean} Whether the action was successful
+		 */
+        _$setClosable: function (isClosable) {
+            if (this.closeButton && this._isClosable()) {
+                this.closeButton.element[isClosable ? "show" : "hide"]();
+                return true;
+            }
+
+            return false;
+        },
+
+		/**
+		 * Destroys the entire header
+		 *
+		 * @package private
+		 *
+		 * @returns {void}
+		 */
+        _$destroy: function () {
+            this.emit('destroy', this);
+
+            for (var i = 0; i < this.tabs.length; i++) {
+                this.tabs[i]._$destroy();
+            }
+            $(document).off('mouseup', this.hideAdditionalTabsDropdown);
+            this.element.remove();
+        },
+
+		/**
+		 * get settings from header
+		 *
+		 * @returns {string} when exists
+		 */
+        _getHeaderSetting: function (name) {
+            if (name in this.parent._header)
+                return this.parent._header[name];
+        },
+		/**
+		 * Creates the popout, maximise and close buttons in the header's top right corner
+		 *
+		 * @returns {void}
+		 */
+        _createControls: function () {
+            var closeStack,
+                popout,
+                label,
+                maximiseLabel,
+                minimiseLabel,
+                maximise,
+                maximiseButton,
+                tabDropdownLabel,
+                showTabDropdown;
+
+			/**
+			 * Dropdown to show additional tabs.
+			 */
+            showTabDropdown = lm.utils.fnBind(this._showAdditionalTabsDropdown, this);
+            tabDropdownLabel = this.layoutManager.config.labels.tabDropdown;
+            this.tabDropdownButton = new lm.controls.HeaderButton(this, tabDropdownLabel, 'lm_tabdropdown', showTabDropdown);
+            this.tabDropdownButton.element.hide();
+
+			/**
+			 * Popout control to launch component in new window.
+			 */
+            if (this._getHeaderSetting('popout')) {
+                popout = lm.utils.fnBind(this._onPopoutClick, this);
+                label = this._getHeaderSetting('popout');
+                new lm.controls.HeaderButton(this, label, 'lm_popout', popout);
+            }
+
+			/**
+			 * Maximise control - set the component to the full size of the layout
+			 */
+            if (this._getHeaderSetting('maximise')) {
+                maximise = lm.utils.fnBind(this.parent.toggleMaximise, this.parent);
+                maximiseLabel = this._getHeaderSetting('maximise');
+                minimiseLabel = this._getHeaderSetting('minimise');
+                maximiseButton = new lm.controls.HeaderButton(this, maximiseLabel, 'lm_maximise', maximise);
+
+                this.parent.on('maximised', function () {
+                    maximiseButton.element.attr('title', minimiseLabel);
+                });
+
+                this.parent.on('minimised', function () {
+                    maximiseButton.element.attr('title', maximiseLabel);
+                });
+            }
+
+			/**
+			 * Close button
+			 */
+            if (this._isClosable()) {
+                closeStack = lm.utils.fnBind(this.parent.remove, this.parent);
+                label = this._getHeaderSetting('close');
+                this.closeButton = new lm.controls.HeaderButton(this, label, 'lm_close', closeStack);
+            }
+        },
+
+		/**
+		 * Shows drop down for additional tabs when there are too many to display.
+		 *
+		 * @returns {void}
+		 */
+        _showAdditionalTabsDropdown: function () {
+            this.tabDropdownContainer.show();
+        },
+
+		/**
+		 * Hides drop down for additional tabs when there are too many to display.
+		 *
+		 * @returns {void}
+		 */
+        _hideAdditionalTabsDropdown: function (e) {
+            this.tabDropdownContainer.hide();
+        },
+
+		/**
+		 * Checks whether the header is closable based on the parent config and
+		 * the global config.
+		 *
+		 * @returns {Boolean} Whether the header is closable.
+		 */
+        _isClosable: function () {
+            return this.parent.config.isClosable && this.layoutManager.config.settings.showCloseIcon;
+        },
+
+        _onPopoutClick: function () {
+            if (this.layoutManager.config.settings.popoutWholeStack === true) {
+                this.parent.popout();
+            } else {
+                this.activeContentItem.popout();
+            }
+        },
+
+
+		/**
+		 * Invoked when the header's background is clicked (not it's tabs or controls)
+		 *
+		 * @param    {jQuery DOM event} event
+		 *
+		 * @returns {void}
+		 */
+        _onHeaderClick: function (event) {
+            if (event.target === this.element[0]) {
+                this.parent.select();
+            }
+        },
+
+		/**
+		 * Pushes the tabs to the tab dropdown if the available space is not sufficient
+		 *
+		 * @returns {void}
+		 */
+        _updateTabSizes: function (showTabMenu) {
+            if (this.tabs.length === 0) {
+                return;
+            }
+
+            //Show the menu based on function argument
+            this.tabDropdownButton.element.toggle(showTabMenu === true);
+
+            var size = function (val) {
+                return val ? 'width' : 'height';
+            };
+            this.element.css(size(!this.parent._sided), '');
+            this.element[size(this.parent._sided)](this.layoutManager.config.dimensions.headerHeight);
+            var availableWidth = this.element.outerWidth() - this.controlsContainer.outerWidth() - this._tabControlOffset,
+                cumulativeTabWidth = 0,
+                visibleTabWidth = 0,
+                tabElement,
+                i,
+                j,
+                marginLeft,
+                overlap = 0,
+                tabWidth,
+                tabOverlapAllowance = this.layoutManager.config.settings.tabOverlapAllowance,
+                tabOverlapAllowanceExceeded = false,
+                activeIndex = (this.activeContentItem ? this.tabs.indexOf(this.activeContentItem.tab) : 0),
+                activeTab = this.tabs[activeIndex];
+            if (this.parent._sided)
+                availableWidth = this.element.outerHeight() - this.controlsContainer.outerHeight() - this._tabControlOffset;
+            this._lastVisibleTabIndex = -1;
+
+            for (i = 0; i < this.tabs.length; i++) {
+                tabElement = this.tabs[i].element;
+
+                //Put the tab in the tabContainer so its true width can be checked
+                this.tabsContainer.append(tabElement);
+                tabWidth = tabElement.outerWidth() + parseInt(tabElement.css('margin-right'), 10);
+
+                cumulativeTabWidth += tabWidth;
+
+                //Include the active tab's width if it isn't already
+                //This is to ensure there is room to show the active tab
+                if (activeIndex <= i) {
+                    visibleTabWidth = cumulativeTabWidth;
+                } else {
+                    visibleTabWidth = cumulativeTabWidth + activeTab.element.outerWidth() + parseInt(activeTab.element.css('margin-right'), 10);
+                }
+
+                // If the tabs won't fit, check the overlap allowance.
+                if (visibleTabWidth > availableWidth) {
+
+                    //Once allowance is exceeded, all remaining tabs go to menu.
+                    if (!tabOverlapAllowanceExceeded) {
+
+                        //No overlap for first tab or active tab
+                        //Overlap spreads among non-active, non-first tabs
+                        if (activeIndex > 0 && activeIndex <= i) {
+                            overlap = (visibleTabWidth - availableWidth) / (i - 1);
+                        } else {
+                            overlap = (visibleTabWidth - availableWidth) / i;
+                        }
+
+                        //Check overlap against allowance.
+                        if (overlap < tabOverlapAllowance) {
+                            for (j = 0; j <= i; j++) {
+                                marginLeft = (j !== activeIndex && j !== 0) ? '-' + overlap + 'px' : '';
+                                this.tabs[j].element.css({ 'z-index': i - j, 'margin-left': marginLeft });
+                            }
+                            this._lastVisibleTabIndex = i;
+                            this.tabsContainer.append(tabElement);
+                        } else {
+                            tabOverlapAllowanceExceeded = true;
+                        }
+
+                    } else if (i === activeIndex) {
+                        //Active tab should show even if allowance exceeded. (We left room.)
+                        tabElement.css({ 'z-index': 'auto', 'margin-left': '' });
+                        this.tabsContainer.append(tabElement);
+                    }
+
+                    if (tabOverlapAllowanceExceeded && i !== activeIndex) {
+                        if (showTabMenu) {
+                            //Tab menu already shown, so we just add to it.
+                            tabElement.css({ 'z-index': 'auto', 'margin-left': '' });
+                            this.tabDropdownContainer.append(tabElement);
+                        } else {
+                            //We now know the tab menu must be shown, so we have to recalculate everything.
+                            this._updateTabSizes(true);
+                            return;
+                        }
+                    }
+
+                }
+                else {
+                    this._lastVisibleTabIndex = i;
+                    tabElement.css({ 'z-index': 'auto', 'margin-left': '' });
+                    this.tabsContainer.append(tabElement);
+                }
+            }
+
+        }
+    });
+
+
+    lm.controls.HeaderButton = function (header, label, cssClass, action) {
+        this._header = header;
+        this.element = $('<li class="' + cssClass + '" title="' + label + '"></li>');
+        this._header.on('destroy', this._$destroy, this);
+        this._action = action;
+        this.element.on('click touchstart', this._action);
+        this._header.controlsContainer.append(this.element);
+    };
+
+    lm.utils.copy(lm.controls.HeaderButton.prototype, {
+        _$destroy: function () {
+            this.element.off();
+            this.element.remove();
+        }
+    });
+    lm.controls.Splitter = function (isVertical, size, grabSize) {
+        this._isVertical = isVertical;
+        this._size = size;
+        this._grabSize = grabSize < size ? size : grabSize;
+
+        this.element = this._createElement();
+        this._dragListener = new lm.utils.DragListener(this.element);
+    };
+
+    lm.utils.copy(lm.controls.Splitter.prototype, {
+        on: function (event, callback, context) {
+            this._dragListener.on(event, callback, context);
+        },
+
+        _$destroy: function () {
+            this.element.remove();
+        },
+
+        _createElement: function () {
+            var dragHandle = $('<div class="lm_drag_handle"></div>');
+            var element = $('<div class="lm_splitter"></div>');
+            element.append(dragHandle);
+
+            var handleExcessSize = this._grabSize - this._size;
+            var handleExcessPos = handleExcessSize / 2;
+
+            if (this._isVertical) {
+                dragHandle.css('top', -handleExcessPos);
+                dragHandle.css('height', this._size + handleExcessSize);
+                element.addClass('lm_vertical');
+                element['height'](this._size);
+            } else {
+                dragHandle.css('left', -handleExcessPos);
+                dragHandle.css('width', this._size + handleExcessSize);
+                element.addClass('lm_horizontal');
+                element['width'](this._size);
+            }
+
+            return element;
+        }
+    });
+
+	/**
+	 * Represents an individual tab within a Stack's header
+	 *
+	 * @param {lm.controls.Header} header
+	 * @param {lm.items.AbstractContentItem} contentItem
+	 *
+	 * @constructor
+	 */
+    lm.controls.Tab = function (header, contentItem) {
+        this.header = header;
+        this.contentItem = contentItem;
+        this.element = $(lm.controls.Tab._template);
+        this.titleElement = this.element.find('.lm_title');
+        this.closeElement = this.element.find('.lm_close_tab');
+        this.closeElement[contentItem.config.isClosable ? 'show' : 'hide']();
+        this.isActive = false;
+
+        this.setTitle(contentItem.config.title);
+        this.contentItem.on('titleChanged', this.setTitle, this);
+
+        this._layoutManager = this.contentItem.layoutManager;
+
+        if (
+            this._layoutManager.config.settings.reorderEnabled === true &&
+            contentItem.config.reorderEnabled === true
+        ) {
+            this._dragListener = new lm.utils.DragListener(this.element);
+            this._dragListener.on('dragStart', this._onDragStart, this);
+            this.contentItem.on('destroy', this._dragListener.destroy, this._dragListener);
+        }
+
+        this._onTabClickFn = lm.utils.fnBind(this._onTabClick, this);
+        this._onCloseClickFn = lm.utils.fnBind(this._onCloseClick, this);
+
+        this.element.on('mousedown touchstart', this._onTabClickFn);
+
+        if (this.contentItem.config.isClosable) {
+            this.closeElement.on('click touchstart', this._onCloseClickFn);
+            this.closeElement.on('mousedown', this._onCloseMousedown);
+        } else {
+            this.closeElement.remove();
+        }
+
+        this.contentItem.tab = this;
+        this.contentItem.emit('tab', this);
+        this.contentItem.layoutManager.emit('tabCreated', this);
+
+        if (this.contentItem.isComponent) {
+            this.contentItem.container.tab = this;
+            this.contentItem.container.emit('tab', this);
+        }
+    };
+
+	/**
+	 * The tab's html template
+	 *
+	 * @type {String}
+	 */
+    lm.controls.Tab._template = '<li class="lm_tab"><i class="lm_left"></i>' +
+        '<span class="lm_title"></span><div class="lm_close_tab"></div>' +
+        '<i class="lm_right"></i></li>';
+
+    lm.utils.copy(lm.controls.Tab.prototype, {
+
+		/**
+		 * Sets the tab's title to the provided string and sets
+		 * its title attribute to a pure text representation (without
+		 * html tags) of the same string.
+		 *
+		 * @public
+		 * @param {String} title can contain html
+		 */
+        setTitle: function (title) {
+            this.element.attr('title', lm.utils.stripTags(title));
+            this.titleElement.html(title);
+        },
+
+		/**
+		 * Sets this tab's active state. To programmatically
+		 * switch tabs, use header.setActiveContentItem( item ) instead.
+		 *
+		 * @public
+		 * @param {Boolean} isActive
+		 */
+        setActive: function (isActive) {
+            if (isActive === this.isActive) {
+                return;
+            }
+            this.isActive = isActive;
+
+            if (isActive) {
+                this.element.addClass('lm_active');
+            } else {
+                this.element.removeClass('lm_active');
+            }
+        },
+
+		/**
+		 * Destroys the tab
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _$destroy: function () {
+            this.element.off('mousedown touchstart', this._onTabClickFn);
+            this.closeElement.off('click touchstart', this._onCloseClickFn);
+            if (this._dragListener) {
+                this.contentItem.off('destroy', this._dragListener.destroy, this._dragListener);
+                this._dragListener.off('dragStart', this._onDragStart);
+                this._dragListener = null;
+            }
+            this.element.remove();
+        },
+
+		/**
+		 * Callback for the DragListener
+		 *
+		 * @param   {Number} x The tabs absolute x position
+		 * @param   {Number} y The tabs absolute y position
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _onDragStart: function (x, y) {
+            if (this.contentItem.parent.isMaximised === true) {
+                this.contentItem.parent.toggleMaximise();
+            }
+            new lm.controls.DragProxy(
+                x,
+                y,
+                this._dragListener,
+                this._layoutManager,
+                this.contentItem,
+                this.header.parent
+            );
+        },
+
+		/**
+		 * Callback when the tab is clicked
+		 *
+		 * @param {jQuery DOM event} event
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _onTabClick: function (event) {
+            // left mouse button or tap
+            if (event.button === 0 || event.type === 'touchstart') {
+                var activeContentItem = this.header.parent.getActiveContentItem();
+                if (this.contentItem !== activeContentItem) {
+                    this.header.parent.setActiveContentItem(this.contentItem);
+                }
+
+                // middle mouse button
+            } else if (event.button === 1 && this.contentItem.config.isClosable) {
+                this._onCloseClick(event);
+            }
+        },
+
+		/**
+		 * Callback when the tab's close button is
+		 * clicked
+		 *
+		 * @param   {jQuery DOM event} event
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _onCloseClick: function (event) {
+            event.stopPropagation();
+            this.header.parent.removeChild(this.contentItem);
+        },
+
+
+		/**
+		 * Callback to capture tab close button mousedown
+		 * to prevent tab from activating.
+		 *
+		 * @param (jQuery DOM event) event
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _onCloseMousedown: function (event) {
+            event.stopPropagation();
+        }
+    });
+
+    lm.controls.TransitionIndicator = function () {
+        this._element = $('<div class="lm_transition_indicator"></div>');
+        $(document.body).append(this._element);
+
+        this._toElement = null;
+        this._fromDimensions = null;
+        this._totalAnimationDuration = 200;
+        this._animationStartTime = null;
+    };
+
+    lm.utils.copy(lm.controls.TransitionIndicator.prototype, {
+        destroy: function () {
+            this._element.remove();
+        },
+
+        transitionElements: function (fromElement, toElement) {
+			/**
+			 * TODO - This is not quite as cool as expected. Review.
+			 */
+            return;
+            this._toElement = toElement;
+            this._animationStartTime = lm.utils.now();
+            this._fromDimensions = this._measure(fromElement);
+            this._fromDimensions.opacity = 0.8;
+            this._element.show().css(this._fromDimensions);
+            lm.utils.animFrame(lm.utils.fnBind(this._nextAnimationFrame, this));
+        },
+
+        _nextAnimationFrame: function () {
+            var toDimensions = this._measure(this._toElement),
+                animationProgress = (lm.utils.now() - this._animationStartTime) / this._totalAnimationDuration,
+                currentFrameStyles = {},
+                cssProperty;
+
+            if (animationProgress >= 1) {
+                this._element.hide();
+                return;
+            }
+
+            toDimensions.opacity = 0;
+
+            for (cssProperty in this._fromDimensions) {
+                currentFrameStyles[cssProperty] = this._fromDimensions[cssProperty] +
+                    (toDimensions[cssProperty] - this._fromDimensions[cssProperty]) *
+                    animationProgress;
+            }
+
+            this._element.css(currentFrameStyles);
+            lm.utils.animFrame(lm.utils.fnBind(this._nextAnimationFrame, this));
+        },
+
+        _measure: function (element) {
+            var offset = element.offset();
+
+            return {
+                left: offset.left,
+                top: offset.top,
+                width: element.outerWidth(),
+                height: element.outerHeight()
+            };
+        }
+    });
+    lm.errors.ConfigurationError = function (message, node) {
+        Error.call(this);
+
+        this.name = 'Configuration Error';
+        this.message = message;
+        this.node = node;
+    };
+
+    lm.errors.ConfigurationError.prototype = new Error();
+
+	/**
+	 * This is the baseclass that all content items inherit from.
+	 * Most methods provide a subset of what the sub-classes do.
+	 *
+	 * It also provides a number of functions for tree traversal
+	 *
+	 * @param {lm.LayoutManager} layoutManager
+	 * @param {item node configuration} config
+	 * @param {lm.item} parent
+	 *
+	 * @event stateChanged
+	 * @event beforeItemDestroyed
+	 * @event itemDestroyed
+	 * @event itemCreated
+	 * @event componentCreated
+	 * @event rowCreated
+	 * @event columnCreated
+	 * @event stackCreated
+	 *
+	 * @constructor
+	 */
+    lm.items.AbstractContentItem = function (layoutManager, config, parent) {
+        lm.utils.EventEmitter.call(this);
+
+        this.config = this._extendItemNode(config);
+        this.type = config.type;
+        this.contentItems = [];
+        this.parent = parent;
+
+        this.isInitialised = false;
+        this.isMaximised = false;
+        this.isRoot = false;
+        this.isRow = false;
+        this.isColumn = false;
+        this.isStack = false;
+        this.isComponent = false;
+
+        this.layoutManager = layoutManager;
+        this._pendingEventPropagations = {};
+        this._throttledEvents = ['stateChanged'];
+
+        this.on(lm.utils.EventEmitter.ALL_EVENT, this._propagateEvent, this);
+
+        if (config.content) {
+            this._createContentItems(config);
+        }
+    };
+
+    lm.utils.copy(lm.items.AbstractContentItem.prototype, {
+
+		/**
+		 * Set the size of the component and its children, called recursively
+		 *
+		 * @abstract
+		 * @returns void
+		 */
+        setSize: function () {
+            throw new Error('Abstract Method');
+        },
+
+		/**
+		 * Calls a method recursively downwards on the tree
+		 *
+		 * @param   {String} functionName      the name of the function to be called
+		 * @param   {[Array]}functionArguments optional arguments that are passed to every function
+		 * @param   {[bool]} bottomUp          Call methods from bottom to top, defaults to false
+		 * @param   {[bool]} skipSelf          Don't invoke the method on the class that calls it, defaults to false
+		 *
+		 * @returns {void}
+		 */
+        callDownwards: function (functionName, functionArguments, bottomUp, skipSelf) {
+            var i;
+
+            if (bottomUp !== true && skipSelf !== true) {
+                this[functionName].apply(this, functionArguments || []);
+            }
+            for (i = 0; i < this.contentItems.length; i++) {
+                this.contentItems[i].callDownwards(functionName, functionArguments, bottomUp);
+            }
+            if (bottomUp === true && skipSelf !== true) {
+                this[functionName].apply(this, functionArguments || []);
+            }
+        },
+
+		/**
+		 * Removes a child node (and its children) from the tree
+		 *
+		 * @param   {lm.items.ContentItem} contentItem
+		 *
+		 * @returns {void}
+		 */
+        removeChild: function (contentItem, keepChild) {
+
+			/*
+			 * Get the position of the item that's to be removed within all content items this node contains
+			 */
+            var index = lm.utils.indexOf(contentItem, this.contentItems);
+
+			/*
+			 * Make sure the content item to be removed is actually a child of this item
+			 */
+            if (index === -1) {
+                throw new Error('Can\'t remove child item. Unknown content item');
+            }
+
+			/**
+			 * Call ._$destroy on the content item. This also calls ._$destroy on all its children
+			 */
+            if (keepChild !== true) {
+                this.contentItems[index]._$destroy();
+            }
+
+			/**
+			 * Remove the content item from this nodes array of children
+			 */
+            this.contentItems.splice(index, 1);
+
+			/**
+			 * Remove the item from the configuration
+			 */
+            this.config.content.splice(index, 1);
+
+			/**
+			 * If this node still contains other content items, adjust their size
+			 */
+            if (this.contentItems.length > 0) {
+                this.callDownwards('setSize');
+
+				/**
+				 * If this was the last content item, remove this node as well
+				 */
+            } else if (!(this instanceof lm.items.Root) && this.config.isClosable === true) {
+                this.parent.removeChild(this);
+            }
+        },
+
+		/**
+		 * Sets up the tree structure for the newly added child
+		 * The responsibility for the actual DOM manipulations lies
+		 * with the concrete item
+		 *
+		 * @param {lm.items.AbstractContentItem} contentItem
+		 * @param {[Int]} index If omitted item will be appended
+		 */
+        addChild: function (contentItem, index) {
+            if (index === undefined) {
+                index = this.contentItems.length;
+            }
+
+            this.contentItems.splice(index, 0, contentItem);
+
+            if (this.config.content === undefined) {
+                this.config.content = [];
+            }
+
+            this.config.content.splice(index, 0, contentItem.config);
+            contentItem.parent = this;
+
+            if (contentItem.parent.isInitialised === true && contentItem.isInitialised === false) {
+                contentItem._$init();
+            }
+        },
+
+		/**
+		 * Replaces oldChild with newChild. This used to use jQuery.replaceWith... which for
+		 * some reason removes all event listeners, so isn't really an option.
+		 *
+		 * @param   {lm.item.AbstractContentItem} oldChild
+		 * @param   {lm.item.AbstractContentItem} newChild
+		 *
+		 * @returns {void}
+		 */
+        replaceChild: function (oldChild, newChild, _$destroyOldChild) {
+
+            newChild = this.layoutManager._$normalizeContentItem(newChild);
+
+            var index = lm.utils.indexOf(oldChild, this.contentItems),
+                parentNode = oldChild.element[0].parentNode;
+
+            if (index === -1) {
+                throw new Error('Can\'t replace child. oldChild is not child of this');
+            }
+
+            parentNode.replaceChild(newChild.element[0], oldChild.element[0]);
+
+			/*
+			 * Optionally destroy the old content item
+			 */
+            if (_$destroyOldChild === true) {
+                oldChild.parent = null;
+                oldChild._$destroy();
+            }
+
+			/*
+			 * Wire the new contentItem into the tree
+			 */
+            this.contentItems[index] = newChild;
+            newChild.parent = this;
+
+			/*
+			 * Update tab reference
+			 */
+            if (this.isStack) {
+                this.header.tabs[index].contentItem = newChild;
+            }
+
+            //TODO This doesn't update the config... refactor to leave item nodes untouched after creation
+            if (newChild.parent.isInitialised === true && newChild.isInitialised === false) {
+                newChild._$init();
+            }
+
+            this.callDownwards('setSize');
+        },
+
+		/**
+		 * Convenience method.
+		 * Shorthand for this.parent.removeChild( this )
+		 *
+		 * @returns {void}
+		 */
+        remove: function () {
+            this.parent.removeChild(this);
+        },
+
+		/**
+		 * Removes the component from the layout and creates a new
+		 * browser window with the component and its children inside
+		 *
+		 * @returns {lm.controls.BrowserPopout}
+		 */
+        popout: function () {
+            var browserPopout = this.layoutManager.createPopout(this);
+            this.emitBubblingEvent('stateChanged');
+            return browserPopout;
+        },
+
+		/**
+		 * Maximises the Item or minimises it if it is already maximised
+		 *
+		 * @returns {void}
+		 */
+        toggleMaximise: function (e) {
+            e && e.preventDefault();
+            if (this.isMaximised === true) {
+                this.layoutManager._$minimiseItem(this);
+            } else {
+                this.layoutManager._$maximiseItem(this);
+            }
+
+            this.isMaximised = !this.isMaximised;
+            this.emitBubblingEvent('stateChanged');
+        },
+
+		/**
+		 * Selects the item if it is not already selected
+		 *
+		 * @returns {void}
+		 */
+        select: function () {
+            if (this.layoutManager.selectedItem !== this) {
+                this.layoutManager.selectItem(this, true);
+                this.element.addClass('lm_selected');
+            }
+        },
+
+		/**
+		 * De-selects the item if it is selected
+		 *
+		 * @returns {void}
+		 */
+        deselect: function () {
+            if (this.layoutManager.selectedItem === this) {
+                this.layoutManager.selectedItem = null;
+                this.element.removeClass('lm_selected');
+            }
+        },
+
+		/**
+		 * Set this component's title
+		 *
+		 * @public
+		 * @param {String} title
+		 *
+		 * @returns {void}
+		 */
+        setTitle: function (title) {
+            this.config.title = title;
+            this.emit('titleChanged', title);
+            this.emit('stateChanged');
+        },
+
+		/**
+		 * Checks whether a provided id is present
+		 *
+		 * @public
+		 * @param   {String}  id
+		 *
+		 * @returns {Boolean} isPresent
+		 */
+        hasId: function (id) {
+            if (!this.config.id) {
+                return false;
+            } else if (typeof this.config.id === 'string') {
+                return this.config.id === id;
+            } else if (this.config.id instanceof Array) {
+                return lm.utils.indexOf(id, this.config.id) !== -1;
+            }
+        },
+
+		/**
+		 * Adds an id. Adds it as a string if the component doesn't
+		 * have an id yet or creates/uses an array
+		 *
+		 * @public
+		 * @param {String} id
+		 *
+		 * @returns {void}
+		 */
+        addId: function (id) {
+            if (this.hasId(id)) {
+                return;
+            }
+
+            if (!this.config.id) {
+                this.config.id = id;
+            } else if (typeof this.config.id === 'string') {
+                this.config.id = [this.config.id, id];
+            } else if (this.config.id instanceof Array) {
+                this.config.id.push(id);
+            }
+        },
+
+		/**
+		 * Removes an existing id. Throws an error
+		 * if the id is not present
+		 *
+		 * @public
+		 * @param   {String} id
+		 *
+		 * @returns {void}
+		 */
+        removeId: function (id) {
+            if (!this.hasId(id)) {
+                throw new Error('Id not found');
+            }
+
+            if (typeof this.config.id === 'string') {
+                delete this.config.id;
+            } else if (this.config.id instanceof Array) {
+                var index = lm.utils.indexOf(id, this.config.id);
+                this.config.id.splice(index, 1);
+            }
+        },
+
+		/****************************************
+		 * SELECTOR
+		 ****************************************/
+        getItemsByFilter: function (filter) {
+            var result = [],
+                next = function (contentItem) {
+                    for (var i = 0; i < contentItem.contentItems.length; i++) {
+
+                        if (filter(contentItem.contentItems[i]) === true) {
+                            result.push(contentItem.contentItems[i]);
+                        }
+
+                        next(contentItem.contentItems[i]);
+                    }
+                };
+
+            next(this);
+            return result;
+        },
+
+        getItemsById: function (id) {
+            return this.getItemsByFilter(function (item) {
+                if (item.config.id instanceof Array) {
+                    return lm.utils.indexOf(id, item.config.id) !== -1;
+                } else {
+                    return item.config.id === id;
+                }
+            });
+        },
+
+        getItemsByType: function (type) {
+            return this._$getItemsByProperty('type', type);
+        },
+
+        getComponentsByName: function (componentName) {
+            var components = this._$getItemsByProperty('componentName', componentName),
+                instances = [],
+                i;
+
+            for (i = 0; i < components.length; i++) {
+                instances.push(components[i].instance);
+            }
+
+            return instances;
+        },
+
+		/****************************************
+		 * PACKAGE PRIVATE
+		 ****************************************/
+        _$getItemsByProperty: function (key, value) {
+            return this.getItemsByFilter(function (item) {
+                return item[key] === value;
+            });
+        },
+
+        _$setParent: function (parent) {
+            this.parent = parent;
+        },
+
+        _$highlightDropZone: function (x, y, area) {
+            this.layoutManager.dropTargetIndicator.highlightArea(area);
+        },
+
+        _$onDrop: function (contentItem) {
+            this.addChild(contentItem);
+        },
+
+        _$hide: function () {
+            this._callOnActiveComponents('hide');
+            this.element.hide();
+            this.layoutManager.updateSize();
+        },
+
+        _$show: function () {
+            this._callOnActiveComponents('show');
+            this.element.show();
+            this.layoutManager.updateSize();
+        },
+
+        _callOnActiveComponents: function (methodName) {
+            var stacks = this.getItemsByType('stack'),
+                activeContentItem,
+                i;
+
+            for (i = 0; i < stacks.length; i++) {
+                activeContentItem = stacks[i].getActiveContentItem();
+
+                if (activeContentItem && activeContentItem.isComponent) {
+                    activeContentItem.container[methodName]();
+                }
+            }
+        },
+
+		/**
+		 * Destroys this item ands its children
+		 *
+		 * @returns {void}
+		 */
+        _$destroy: function () {
+            this.emitBubblingEvent('beforeItemDestroyed');
+            this.callDownwards('_$destroy', [], true, true);
+            this.element.remove();
+            this.emitBubblingEvent('itemDestroyed');
+        },
+
+		/**
+		 * Returns the area the component currently occupies in the format
+		 *
+		 * {
+		 *		x1: int
+		 *		xy: int
+		 *		y1: int
+		 *		y2: int
+		 *		contentItem: contentItem
+		 * }
+		 */
+        _$getArea: function (element) {
+            element = element || this.element;
+
+            var offset = element.offset(),
+                width = element.width(),
+                height = element.height();
+
+            return {
+                x1: offset.left,
+                y1: offset.top,
+                x2: offset.left + width,
+                y2: offset.top + height,
+                surface: width * height,
+                contentItem: this
+            };
+        },
+
+		/**
+		 * The tree of content items is created in two steps: First all content items are instantiated,
+		 * then init is called recursively from top to bottem. This is the basic init function,
+		 * it can be used, extended or overwritten by the content items
+		 *
+		 * Its behaviour depends on the content item
+		 *
+		 * @package private
+		 *
+		 * @returns {void}
+		 */
+        _$init: function () {
+            var i;
+            this.setSize();
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                this.childElementContainer.append(this.contentItems[i].element);
+            }
+
+            this.isInitialised = true;
+            this.emitBubblingEvent('itemCreated');
+            this.emitBubblingEvent(this.type + 'Created');
+        },
+
+		/**
+		 * Emit an event that bubbles up the item tree.
+		 *
+		 * @param   {String} name The name of the event
+		 *
+		 * @returns {void}
+		 */
+        emitBubblingEvent: function (name) {
+            var event = new lm.utils.BubblingEvent(name, this);
+            this.emit(name, event);
+        },
+
+		/**
+		 * Private method, creates all content items for this node at initialisation time
+		 * PLEASE NOTE, please see addChild for adding contentItems add runtime
+		 * @private
+		 * @param   {configuration item node} config
+		 *
+		 * @returns {void}
+		 */
+        _createContentItems: function (config) {
+            var oContentItem, i;
+
+            if (!(config.content instanceof Array)) {
+                throw new lm.errors.ConfigurationError('content must be an Array', config);
+            }
+
+            for (i = 0; i < config.content.length; i++) {
+                oContentItem = this.layoutManager.createContentItem(config.content[i], this);
+                this.contentItems.push(oContentItem);
+            }
+        },
+
+		/**
+		 * Extends an item configuration node with default settings
+		 * @private
+		 * @param   {configuration item node} config
+		 *
+		 * @returns {configuration item node} extended config
+		 */
+        _extendItemNode: function (config) {
+
+            for (var key in lm.config.itemDefaultConfig) {
+                if (config[key] === undefined) {
+                    config[key] = lm.config.itemDefaultConfig[key];
+                }
+            }
+
+            return config;
+        },
+
+		/**
+		 * Called for every event on the item tree. Decides whether the event is a bubbling
+		 * event and propagates it to its parent
+		 *
+		 * @param    {String} name the name of the event
+		 * @param   {lm.utils.BubblingEvent} event
+		 *
+		 * @returns {void}
+		 */
+        _propagateEvent: function (name, event) {
+            if (event instanceof lm.utils.BubblingEvent &&
+                event.isPropagationStopped === false &&
+                this.isInitialised === true) {
+
+				/**
+				 * In some cases (e.g. if an element is created from a DragSource) it
+				 * doesn't have a parent and is not below root. If that's the case
+				 * propagate the bubbling event from the top level of the substree directly
+				 * to the layoutManager
+				 */
+                if (this.isRoot === false && this.parent) {
+                    this.parent.emit.apply(this.parent, Array.prototype.slice.call(arguments, 0));
+                } else {
+                    this._scheduleEventPropagationToLayoutManager(name, event);
+                }
+            }
+        },
+
+		/**
+		 * All raw events bubble up to the root element. Some events that
+		 * are propagated to - and emitted by - the layoutManager however are
+		 * only string-based, batched and sanitized to make them more usable
+		 *
+		 * @param {String} name the name of the event
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _scheduleEventPropagationToLayoutManager: function (name, event) {
+            if (lm.utils.indexOf(name, this._throttledEvents) === -1) {
+                this.layoutManager.emit(name, event.origin);
+            } else {
+                if (this._pendingEventPropagations[name] !== true) {
+                    this._pendingEventPropagations[name] = true;
+                    lm.utils.animFrame(lm.utils.fnBind(this._propagateEventToLayoutManager, this, [name, event]));
+                }
+            }
+
+        },
+
+		/**
+		 * Callback for events scheduled by _scheduleEventPropagationToLayoutManager
+		 *
+		 * @param {String} name the name of the event
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _propagateEventToLayoutManager: function (name, event) {
+            this._pendingEventPropagations[name] = false;
+            this.layoutManager.emit(name, event);
+        }
+    });
+
+	/**
+	 * @param {[type]} layoutManager [description]
+	 * @param {[type]} config      [description]
+	 * @param {[type]} parent        [description]
+	 */
+    lm.items.Component = function (layoutManager, config, parent) {
+        lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
+
+        var ComponentConstructor = layoutManager.getComponent(this.config.componentName),
+            componentConfig = $.extend(true, {}, this.config.componentState || {});
+
+        componentConfig.componentName = this.config.componentName;
+        this.componentName = this.config.componentName;
+
+        if (this.config.title === '') {
+            this.config.title = this.config.componentName;
+        }
+
+        this.isComponent = true;
+        this.container = new lm.container.ItemContainer(this.config, this, layoutManager);
+        this.instance = new ComponentConstructor(this.container, componentConfig);
+        this.element = this.container._element;
+    };
+
+    lm.utils.extend(lm.items.Component, lm.items.AbstractContentItem);
+
+    lm.utils.copy(lm.items.Component.prototype, {
+
+        close: function () {
+            this.parent.removeChild(this);
+        },
+
+        setSize: function () {
+            if (this.element.is(':visible')) {
+                // Do not update size of hidden components to prevent unwanted reflows
+                this.container._$setSize(this.element.width(), this.element.height());
+            }
+        },
+
+        _$init: function () {
+            lm.items.AbstractContentItem.prototype._$init.call(this);
+            this.container.emit('open');
+        },
+
+        _$hide: function () {
+            this.container.hide();
+            lm.items.AbstractContentItem.prototype._$hide.call(this);
+        },
+
+        _$show: function () {
+            this.container.show();
+            lm.items.AbstractContentItem.prototype._$show.call(this);
+        },
+
+        _$shown: function () {
+            this.container.shown();
+            lm.items.AbstractContentItem.prototype._$shown.call(this);
+        },
+
+        _$destroy: function () {
+            this.container.emit('destroy', this);
+            lm.items.AbstractContentItem.prototype._$destroy.call(this);
+        },
+
+		/**
+		 * Dragging onto a component directly is not an option
+		 *
+		 * @returns null
+		 */
+        _$getArea: function () {
+            return null;
+        }
+    });
+
+    lm.items.Root = function (layoutManager, config, containerElement) {
+        lm.items.AbstractContentItem.call(this, layoutManager, config, null);
+        this.isRoot = true;
+        this.type = 'root';
+        this.element = $('<div class="lm_goldenlayout lm_item lm_root"></div>');
+        this.childElementContainer = this.element;
+        this._containerElement = containerElement;
+        this._containerElement.append(this.element);
+    };
+
+    lm.utils.extend(lm.items.Root, lm.items.AbstractContentItem);
+
+    lm.utils.copy(lm.items.Root.prototype, {
+        addChild: function (contentItem) {
+            if (this.contentItems.length > 0) {
+                throw new Error('Root node can only have a single child');
+            }
+
+            contentItem = this.layoutManager._$normalizeContentItem(contentItem, this);
+            this.childElementContainer.append(contentItem.element);
+            lm.items.AbstractContentItem.prototype.addChild.call(this, contentItem);
+
+            this.callDownwards('setSize');
+            this.emitBubblingEvent('stateChanged');
+        },
+
+        setSize: function (width, height) {
+            width = (typeof width === 'undefined') ? this._containerElement.width() : width;
+            height = (typeof height === 'undefined') ? this._containerElement.height() : height;
+
+            this.element.width(width);
+            this.element.height(height);
+
+			/*
+			 * Root can be empty
+			 */
+            if (this.contentItems[0]) {
+                this.contentItems[0].element.width(width);
+                this.contentItems[0].element.height(height);
+            }
+        },
+        _$highlightDropZone: function (x, y, area) {
+            this.layoutManager.tabDropPlaceholder.remove();
+            lm.items.AbstractContentItem.prototype._$highlightDropZone.apply(this, arguments);
+        },
+
+        _$onDrop: function (contentItem, area) {
+            var stack;
+
+            if (contentItem.isComponent) {
+                stack = this.layoutManager.createContentItem({
+                    type: 'stack',
+                    header: contentItem.config.header || {}
+                }, this);
+                stack._$init();
+                stack.addChild(contentItem);
+                contentItem = stack;
+            }
+
+            if (!this.contentItems.length) {
+                this.addChild(contentItem);
+            } else {
+                var type = area.side[0] == 'x' ? 'row' : 'column';
+                var dimension = area.side[0] == 'x' ? 'width' : 'height';
+                var insertBefore = area.side[1] == '2';
+                var column = this.contentItems[0];
+                if (!column instanceof lm.items.RowOrColumn || column.type != type) {
+                    var rowOrColumn = this.layoutManager.createContentItem({ type: type }, this);
+                    this.replaceChild(column, rowOrColumn);
+                    rowOrColumn.addChild(contentItem, insertBefore ? 0 : undefined, true);
+                    rowOrColumn.addChild(column, insertBefore ? undefined : 0, true);
+                    column.config[dimension] = 50;
+                    contentItem.config[dimension] = 50;
+                    rowOrColumn.callDownwards('setSize');
+                } else {
+                    var sibbling = column.contentItems[insertBefore ? 0 : column.contentItems.length - 1]
+                    column.addChild(contentItem, insertBefore ? 0 : undefined, true);
+                    sibbling.config[dimension] *= 0.5;
+                    contentItem.config[dimension] = sibbling.config[dimension];
+                    column.callDownwards('setSize');
+                }
+            }
+        }
+    });
+
+
+
+    lm.items.RowOrColumn = function (isColumn, layoutManager, config, parent) {
+        lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
+
+        this.isRow = !isColumn;
+        this.isColumn = isColumn;
+
+        this.element = $('<div class="lm_item lm_' + (isColumn ? 'column' : 'row') + '"></div>');
+        this.childElementContainer = this.element;
+        this._splitterSize = layoutManager.config.dimensions.borderWidth;
+        this._splitterGrabSize = layoutManager.config.dimensions.borderGrabWidth;
+        this._isColumn = isColumn;
+        this._dimension = isColumn ? 'height' : 'width';
+        this._splitter = [];
+        this._splitterPosition = null;
+        this._splitterMinPosition = null;
+        this._splitterMaxPosition = null;
+    };
+
+    lm.utils.extend(lm.items.RowOrColumn, lm.items.AbstractContentItem);
+
+    lm.utils.copy(lm.items.RowOrColumn.prototype, {
+
+		/**
+		 * Add a new contentItem to the Row or Column
+		 *
+		 * @param {lm.item.AbstractContentItem} contentItem
+		 * @param {[int]} index The position of the new item within the Row or Column.
+		 *                      If no index is provided the item will be added to the end
+		 * @param {[bool]} _$suspendResize If true the items won't be resized. This will leave the item in
+		 *                                 an inconsistent state and is only intended to be used if multiple
+		 *                                 children need to be added in one go and resize is called afterwards
+		 *
+		 * @returns {void}
+		 */
+        addChild: function (contentItem, index, _$suspendResize) {
+
+            var newItemSize, itemSize, i, splitterElement;
+
+            contentItem = this.layoutManager._$normalizeContentItem(contentItem, this);
+
+            if (index === undefined) {
+                index = this.contentItems.length;
+            }
+
+            if (this.contentItems.length > 0) {
+                splitterElement = this._createSplitter(Math.max(0, index - 1)).element;
+
+                if (index > 0) {
+                    this.contentItems[index - 1].element.after(splitterElement);
+                    splitterElement.after(contentItem.element);
+                } else {
+                    this.contentItems[0].element.before(splitterElement);
+                    splitterElement.before(contentItem.element);
+                }
+            } else {
+                this.childElementContainer.append(contentItem.element);
+            }
+
+            lm.items.AbstractContentItem.prototype.addChild.call(this, contentItem, index);
+
+            let fixedItemSize = 0;
+            let variableItemCount = 0;
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (this.contentItems[i].config.fixed)
+                    fixedItemSize += this.contentItems[i].config[this._dimension];
+                else variableItemCount++;
+            }
+
+            newItemSize = (1 / variableItemCount) * (100 - fixedItemSize);
+
+            if (_$suspendResize === true) {
+                this.emitBubblingEvent('stateChanged');
+                return;
+            }
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (this.contentItems[i].config.fixed)
+                    ;
+                else if (this.contentItems[i] === contentItem) {
+                    contentItem.config[this._dimension] = newItemSize;
+                } else {
+                    itemSize = this.contentItems[i].config[this._dimension] *= (100 - newItemSize - fixedItemSize) / (100 - fixedItemSize);
+                    this.contentItems[i].config[this._dimension] = itemSize;
+                }
+            }
+
+            this.callDownwards('setSize');
+            this.emitBubblingEvent('stateChanged');
+
+        },
+
+		/**
+		 * Removes a child of this element
+		 *
+		 * @param   {lm.items.AbstractContentItem} contentItem
+		 * @param   {boolean} keepChild   If true the child will be removed, but not destroyed
+		 *
+		 * @returns {void}
+		 */
+        removeChild: function (contentItem, keepChild) {
+            var removedItemSize = contentItem.config[this._dimension],
+                index = lm.utils.indexOf(contentItem, this.contentItems),
+                splitterIndex = Math.max(index - 1, 0),
+                i,
+                childItem;
+
+            if (index === -1) {
+                throw new Error('Can\'t remove child. ContentItem is not child of this Row or Column');
+            }
+
+			/**
+			 * Remove the splitter before the item or after if the item happens
+			 * to be the first in the row/column
+			 */
+            if (this._splitter[splitterIndex]) {
+                this._splitter[splitterIndex]._$destroy();
+                this._splitter.splice(splitterIndex, 1);
+            }
+
+            let fixedItemSize = 0;
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (this.contentItems[i].config.fixed)
+                    fixedItemSize += this.contentItems[i].config[this._dimension];
+            }
+			/**
+			 * Allocate the space that the removed item occupied to the remaining items
+			 */
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (this.contentItems[i].config.fixed)
+                    ;
+                else if (this.contentItems[i] !== contentItem) {
+                    this.contentItems[i].config[this._dimension] *= (100 - fixedItemSize) / (100 - removedItemSize - fixedItemSize);
+                }
+            }
+
+            lm.items.AbstractContentItem.prototype.removeChild.call(this, contentItem, keepChild);
+
+            if (this.contentItems.length === 1 && this.config.isClosable === true) {
+                childItem = this.contentItems[0];
+                this.contentItems = [];
+                this.parent.replaceChild(this, childItem, true);
+            } else {
+                this.callDownwards('setSize');
+                this.emitBubblingEvent('stateChanged');
+            }
+        },
+
+		/**
+		 * Replaces a child of this Row or Column with another contentItem
+		 *
+		 * @param   {lm.items.AbstractContentItem} oldChild
+		 * @param   {lm.items.AbstractContentItem} newChild
+		 *
+		 * @returns {void}
+		 */
+        replaceChild: function (oldChild, newChild) {
+            var size = oldChild.config[this._dimension];
+            lm.items.AbstractContentItem.prototype.replaceChild.call(this, oldChild, newChild);
+            newChild.config[this._dimension] = size;
+            this.callDownwards('setSize');
+            this.emitBubblingEvent('stateChanged');
+        },
+
+		/**
+		 * Called whenever the dimensions of this item or one of its parents change
+		 *
+		 * @returns {void}
+		 */
+        setSize: function () {
+            if (this.contentItems.length > 0) {
+                this._calculateRelativeSizes();
+                this._setAbsoluteSizes();
+            }
+            this.emitBubblingEvent('stateChanged');
+            this.emit('resize');
+        },
+
+		/**
+		 * Invoked recursively by the layout manager. AbstractContentItem.init appends
+		 * the contentItem's DOM elements to the container, RowOrColumn init adds splitters
+		 * in between them
+		 *
+		 * @package private
+		 * @override AbstractContentItem._$init
+		 * @returns {void}
+		 */
+        _$init: function () {
+            if (this.isInitialised === true) return;
+
+            var i;
+
+            lm.items.AbstractContentItem.prototype._$init.call(this);
+
+            for (i = 0; i < this.contentItems.length - 1; i++) {
+                this.contentItems[i].element.after(this._createSplitter(i).element);
+            }
+        },
+
+		/**
+		 * Turns the relative sizes calculated by _calculateRelativeSizes into
+		 * absolute pixel values and applies them to the children's DOM elements
+		 *
+		 * Assigns additional pixels to counteract Math.floor
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _setAbsoluteSizes: function () {
+            var i,
+                sizeData = this._calculateAbsoluteSizes();
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (sizeData.additionalPixel - i > 0) {
+                    sizeData.itemSizes[i]++;
+                }
+
+                if (this._isColumn) {
+                    this.contentItems[i].element.width(sizeData.totalWidth);
+                    this.contentItems[i].element.height(sizeData.itemSizes[i]);
+                } else {
+                    this.contentItems[i].element.width(sizeData.itemSizes[i]);
+                    this.contentItems[i].element.height(sizeData.totalHeight);
+                }
+            }
+        },
+
+		/**
+		 * Calculates the absolute sizes of all of the children of this Item.
+		 * @returns {object} - Set with absolute sizes and additional pixels.
+		 */
+        _calculateAbsoluteSizes: function () {
+            var i,
+                totalSplitterSize = (this.contentItems.length - 1) * this._splitterSize,
+                totalWidth = this.element.width(),
+                totalHeight = this.element.height(),
+                totalAssigned = 0,
+                additionalPixel,
+                itemSize,
+                itemSizes = [];
+
+            if (this._isColumn) {
+                totalHeight -= totalSplitterSize;
+            } else {
+                totalWidth -= totalSplitterSize;
+            }
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (this._isColumn) {
+                    itemSize = Math.floor(totalHeight * (this.contentItems[i].config.height / 100));
+                } else {
+                    itemSize = Math.floor(totalWidth * (this.contentItems[i].config.width / 100));
+                }
+
+                totalAssigned += itemSize;
+                itemSizes.push(itemSize);
+            }
+
+            additionalPixel = Math.floor((this._isColumn ? totalHeight : totalWidth) - totalAssigned);
+
+            return {
+                itemSizes: itemSizes,
+                additionalPixel: additionalPixel,
+                totalWidth: totalWidth,
+                totalHeight: totalHeight
+            };
+        },
+
+		/**
+		 * Calculates the relative sizes of all children of this Item. The logic
+		 * is as follows:
+		 *
+		 * - Add up the total size of all items that have a configured size
+		 *
+		 * - If the total == 100 (check for floating point errors)
+		 *        Excellent, job done
+		 *
+		 * - If the total is > 100,
+		 *        set the size of items without set dimensions to 1/3 and add this to the total
+		 *        set the size off all items so that the total is hundred relative to their original size
+		 *
+		 * - If the total is < 100
+		 *        If there are items without set dimensions, distribute the remainder to 100 evenly between them
+		 *        If there are no items without set dimensions, increase all items sizes relative to
+		 *        their original size so that they add up to 100
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _calculateRelativeSizes: function () {
+
+            var i,
+                total = 0,
+                itemsWithoutSetDimension = [],
+                dimension = this._isColumn ? 'height' : 'width';
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (this.contentItems[i].config[dimension] !== undefined) {
+                    total += this.contentItems[i].config[dimension];
+                } else {
+                    itemsWithoutSetDimension.push(this.contentItems[i]);
+                }
+            }
+
+			/**
+			 * Everything adds up to hundred, all good :-)
+			 */
+            if (Math.round(total) === 100) {
+                this._respectMinItemWidth();
+                return;
+            }
+
+			/**
+			 * Allocate the remaining size to the items without a set dimension
+			 */
+            if (Math.round(total) < 100 && itemsWithoutSetDimension.length > 0) {
+                for (i = 0; i < itemsWithoutSetDimension.length; i++) {
+                    itemsWithoutSetDimension[i].config[dimension] = (100 - total) / itemsWithoutSetDimension.length;
+                }
+                this._respectMinItemWidth();
+                return;
+            }
+
+			/**
+			 * If the total is > 100, but there are also items without a set dimension left, assing 50
+			 * as their dimension and add it to the total
+			 *
+			 * This will be reset in the next step
+			 */
+            if (Math.round(total) > 100) {
+                for (i = 0; i < itemsWithoutSetDimension.length; i++) {
+                    itemsWithoutSetDimension[i].config[dimension] = 50;
+                    total += 50;
+                }
+            }
+
+			/**
+			 * Set every items size relative to 100 relative to its size to total
+			 */
+            for (i = 0; i < this.contentItems.length; i++) {
+                this.contentItems[i].config[dimension] = (this.contentItems[i].config[dimension] / total) * 100;
+            }
+
+            this._respectMinItemWidth();
+        },
+
+		/**
+		 * Adjusts the column widths to respect the dimensions minItemWidth if set.
+		 * @returns {}
+		 */
+        _respectMinItemWidth: function () {
+            var minItemWidth = this.layoutManager.config.dimensions ? (this.layoutManager.config.dimensions.minItemWidth || 0) : 0,
+                sizeData = null,
+                entriesOverMin = [],
+                totalOverMin = 0,
+                totalUnderMin = 0,
+                remainingWidth = 0,
+                itemSize = 0,
+                contentItem = null,
+                reducePercent,
+                reducedWidth,
+                allEntries = [],
+                entry;
+
+            if (this._isColumn || !minItemWidth || this.contentItems.length <= 1) {
+                return;
+            }
+
+            sizeData = this._calculateAbsoluteSizes();
+
+			/**
+			 * Figure out how much we are under the min item size total and how much room we have to use.
+			 */
+            for (var i = 0; i < this.contentItems.length; i++) {
+
+                contentItem = this.contentItems[i];
+                itemSize = sizeData.itemSizes[i];
+
+                if (itemSize < minItemWidth) {
+                    totalUnderMin += minItemWidth - itemSize;
+                    entry = { width: minItemWidth };
+
+                }
+                else {
+                    totalOverMin += itemSize - minItemWidth;
+                    entry = { width: itemSize };
+                    entriesOverMin.push(entry);
+                }
+
+                allEntries.push(entry);
+            }
+
+			/**
+			 * If there is nothing under min, or there is not enough over to make up the difference, do nothing.
+			 */
+            if (totalUnderMin === 0 || totalUnderMin > totalOverMin) {
+                return;
+            }
+
+			/**
+			 * Evenly reduce all columns that are over the min item width to make up the difference.
+			 */
+            reducePercent = totalUnderMin / totalOverMin;
+            remainingWidth = totalUnderMin;
+            for (i = 0; i < entriesOverMin.length; i++) {
+                entry = entriesOverMin[i];
+                reducedWidth = Math.round((entry.width - minItemWidth) * reducePercent);
+                remainingWidth -= reducedWidth;
+                entry.width -= reducedWidth;
+            }
+
+			/**
+			 * Take anything remaining from the last item.
+			 */
+            if (remainingWidth !== 0) {
+                allEntries[allEntries.length - 1].width -= remainingWidth;
+            }
+
+			/**
+			 * Set every items size relative to 100 relative to its size to total
+			 */
+            for (i = 0; i < this.contentItems.length; i++) {
+                this.contentItems[i].config.width = (allEntries[i].width / sizeData.totalWidth) * 100;
+            }
+        },
+
+		/**
+		 * Instantiates a new lm.controls.Splitter, binds events to it and adds
+		 * it to the array of splitters at the position specified as the index argument
+		 *
+		 * What it doesn't do though is append the splitter to the DOM
+		 *
+		 * @param   {Int} index The position of the splitter
+		 *
+		 * @returns {lm.controls.Splitter}
+		 */
+        _createSplitter: function (index) {
+            var splitter;
+            splitter = new lm.controls.Splitter(this._isColumn, this._splitterSize, this._splitterGrabSize);
+            splitter.on('drag', lm.utils.fnBind(this._onSplitterDrag, this, [splitter]), this);
+            splitter.on('dragStop', lm.utils.fnBind(this._onSplitterDragStop, this, [splitter]), this);
+            splitter.on('dragStart', lm.utils.fnBind(this._onSplitterDragStart, this, [splitter]), this);
+            this._splitter.splice(index, 0, splitter);
+            return splitter;
+        },
+
+		/**
+		 * Locates the instance of lm.controls.Splitter in the array of
+		 * registered splitters and returns a map containing the contentItem
+		 * before and after the splitters, both of which are affected if the
+		 * splitter is moved
+		 *
+		 * @param   {lm.controls.Splitter} splitter
+		 *
+		 * @returns {Object} A map of contentItems that the splitter affects
+		 */
+        _getItemsForSplitter: function (splitter) {
+            var index = lm.utils.indexOf(splitter, this._splitter);
+
+            return {
+                before: this.contentItems[index],
+                after: this.contentItems[index + 1]
+            };
+        },
+
+		/**
+		 * Gets the minimum dimensions for the given item configuration array
+		 * @param item
+		 * @private
+		 */
+        _getMinimumDimensions: function (arr) {
+            var minWidth = 0, minHeight = 0;
+
+            for (var i = 0; i < arr.length; ++i) {
+                minWidth = Math.max(arr[i].minWidth || 0, minWidth);
+                minHeight = Math.max(arr[i].minHeight || 0, minHeight);
+            }
+
+            return { horizontal: minWidth, vertical: minHeight };
+        },
+
+		/**
+		 * Invoked when a splitter's dragListener fires dragStart. Calculates the splitters
+		 * movement area once (so that it doesn't need calculating on every mousemove event)
+		 *
+		 * @param   {lm.controls.Splitter} splitter
+		 *
+		 * @returns {void}
+		 */
+        _onSplitterDragStart: function (splitter) {
+            var items = this._getItemsForSplitter(splitter),
+                minSize = this.layoutManager.config.dimensions[this._isColumn ? 'minItemHeight' : 'minItemWidth'];
+
+            var beforeMinDim = this._getMinimumDimensions(items.before.config.content);
+            var beforeMinSize = this._isColumn ? beforeMinDim.vertical : beforeMinDim.horizontal;
+
+            var afterMinDim = this._getMinimumDimensions(items.after.config.content);
+            var afterMinSize = this._isColumn ? afterMinDim.vertical : afterMinDim.horizontal;
+
+            this._splitterPosition = 0;
+            this._splitterMinPosition = -1 * (items.before.element[this._dimension]() - (beforeMinSize || minSize));
+            this._splitterMaxPosition = items.after.element[this._dimension]() - (afterMinSize || minSize);
+        },
+
+		/**
+		 * Invoked when a splitter's DragListener fires drag. Updates the splitters DOM position,
+		 * but not the sizes of the elements the splitter controls in order to minimize resize events
+		 *
+		 * @param   {lm.controls.Splitter} splitter
+		 * @param   {Int} offsetX  Relative pixel values to the splitters original position. Can be negative
+		 * @param   {Int} offsetY  Relative pixel values to the splitters original position. Can be negative
+		 *
+		 * @returns {void}
+		 */
+        _onSplitterDrag: function (splitter, offsetX, offsetY) {
+            var offset = this._isColumn ? offsetY : offsetX;
+
+            if (offset > this._splitterMinPosition && offset < this._splitterMaxPosition) {
+                this._splitterPosition = offset;
+                splitter.element.css(this._isColumn ? 'top' : 'left', offset);
+            }
+        },
+
+		/**
+		 * Invoked when a splitter's DragListener fires dragStop. Resets the splitters DOM position,
+		 * and applies the new sizes to the elements before and after the splitter and their children
+		 * on the next animation frame
+		 *
+		 * @param   {lm.controls.Splitter} splitter
+		 *
+		 * @returns {void}
+		 */
+        _onSplitterDragStop: function (splitter) {
+
+            var items = this._getItemsForSplitter(splitter),
+                sizeBefore = items.before.element[this._dimension](),
+                sizeAfter = items.after.element[this._dimension](),
+                splitterPositionInRange = (this._splitterPosition + sizeBefore) / (sizeBefore + sizeAfter),
+                totalRelativeSize = items.before.config[this._dimension] + items.after.config[this._dimension];
+
+            items.before.config[this._dimension] = splitterPositionInRange * totalRelativeSize;
+            items.after.config[this._dimension] = (1 - splitterPositionInRange) * totalRelativeSize;
+
+            splitter.element.css({
+                'top': 0,
+                'left': 0
+            });
+
+            lm.utils.animFrame(lm.utils.fnBind(this.callDownwards, this, ['setSize']));
+        }
+    });
+
+    lm.items.Stack = function (layoutManager, config, parent) {
+        lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
+
+        this.element = $('<div class="lm_item lm_stack"></div>');
+        this._activeContentItem = null;
+        var cfg = layoutManager.config;
+        this._header = { // defaults' reconstruction from old configuration style
+            show: cfg.settings.hasHeaders === true && config.hasHeaders !== false,
+            popout: cfg.settings.showPopoutIcon && cfg.labels.popout,
+            maximise: cfg.settings.showMaximiseIcon && cfg.labels.maximise,
+            close: cfg.settings.showCloseIcon && cfg.labels.close,
+            minimise: cfg.labels.minimise,
+        };
+        if (cfg.header) // load simplified version of header configuration (https://github.com/deepstreamIO/golden-layout/pull/245)
+            lm.utils.copy(this._header, cfg.header);
+        if (config.header) // load from stack
+            lm.utils.copy(this._header, config.header);
+        if (config.content && config.content[0] && config.content[0].header) // load from component if stack omitted
+            lm.utils.copy(this._header, config.content[0].header);
+
+        this._dropZones = {};
+        this._dropSegment = null;
+        this._contentAreaDimensions = null;
+        this._dropIndex = null;
+
+        this.isStack = true;
+
+        this.childElementContainer = $('<div class="lm_items"></div>');
+        this.header = new lm.controls.Header(layoutManager, this);
+
+        this.element.append(this.header.element);
+        this.element.append(this.childElementContainer);
+        this._setupHeaderPosition();
+        this._$validateClosability();
+    };
+
+    lm.utils.extend(lm.items.Stack, lm.items.AbstractContentItem);
+
+    lm.utils.copy(lm.items.Stack.prototype, {
+
+        setSize: function () {
+            var i,
+                headerSize = this._header.show ? this.layoutManager.config.dimensions.headerHeight : 0,
+                contentWidth = this.element.width() - (this._sided ? headerSize : 0),
+                contentHeight = this.element.height() - (!this._sided ? headerSize : 0);
+
+            this.childElementContainer.width(contentWidth);
+            this.childElementContainer.height(contentHeight);
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                this.contentItems[i].element.width(contentWidth).height(contentHeight);
+            }
+            this.emit('resize');
+            this.emitBubblingEvent('stateChanged');
+        },
+
+        _$init: function () {
+            var i, initialItem;
+
+            if (this.isInitialised === true) return;
+
+            lm.items.AbstractContentItem.prototype._$init.call(this);
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                this.header.createTab(this.contentItems[i]);
+                this.contentItems[i]._$hide();
+            }
+
+            if (this.contentItems.length > 0) {
+                initialItem = this.contentItems[this.config.activeItemIndex || 0];
+
+                if (!initialItem) {
+                    throw new Error('Configured activeItemIndex out of bounds');
+                }
+
+                this.setActiveContentItem(initialItem);
+            }
+        },
+
+        setActiveContentItem: function (contentItem) {
+            if (lm.utils.indexOf(contentItem, this.contentItems) === -1) {
+                throw new Error('contentItem is not a child of this stack');
+            }
+
+            if (this._activeContentItem !== null) {
+                this._activeContentItem._$hide();
+            }
+
+            this._activeContentItem = contentItem;
+            this.header.setActiveContentItem(contentItem);
+            contentItem._$show();
+            this.emit('activeContentItemChanged', contentItem);
+            this.layoutManager.emit('activeContentItemChanged', contentItem);
+            this.emitBubblingEvent('stateChanged');
+        },
+
+        getActiveContentItem: function () {
+            return this.header.activeContentItem;
+        },
+
+        addChild: function (contentItem, index) {
+            contentItem = this.layoutManager._$normalizeContentItem(contentItem, this);
+            lm.items.AbstractContentItem.prototype.addChild.call(this, contentItem, index);
+            this.childElementContainer.append(contentItem.element);
+            this.header.createTab(contentItem, index);
+            this.setActiveContentItem(contentItem);
+            this.callDownwards('setSize');
+            this._$validateClosability();
+            this.emitBubblingEvent('stateChanged');
+        },
+
+        removeChild: function (contentItem, keepChild) {
+            var index = lm.utils.indexOf(contentItem, this.contentItems);
+            lm.items.AbstractContentItem.prototype.removeChild.call(this, contentItem, keepChild);
+            this.header.removeTab(contentItem);
+            if (this.header.activeContentItem === contentItem) {
+                if (this.contentItems.length > 0) {
+                    this.setActiveContentItem(this.contentItems[Math.max(index - 1, 0)]);
+                } else {
+                    this._activeContentItem = null;
+                }
+            }
+
+            this._$validateClosability();
+            this.emitBubblingEvent('stateChanged');
+        },
+
+		/**
+		 * Validates that the stack is still closable or not. If a stack is able
+		 * to close, but has a non closable component added to it, the stack is no
+		 * longer closable until all components are closable.
+		 *
+		 * @returns {void}
+		 */
+        _$validateClosability: function () {
+            var contentItem,
+                isClosable,
+                len,
+                i;
+
+            isClosable = this.header._isClosable();
+
+            for (i = 0, len = this.contentItems.length; i < len; i++) {
+                if (!isClosable) {
+                    break;
+                }
+
+                isClosable = this.contentItems[i].config.isClosable;
+            }
+
+            this.header._$setClosable(isClosable);
+        },
+
+        _$destroy: function () {
+            lm.items.AbstractContentItem.prototype._$destroy.call(this);
+            this.header._$destroy();
+        },
+
+
+		/**
+		 * Ok, this one is going to be the tricky one: The user has dropped {contentItem} onto this stack.
+		 *
+		 * It was dropped on either the stacks header or the top, right, bottom or left bit of the content area
+		 * (which one of those is stored in this._dropSegment). Now, if the user has dropped on the header the case
+		 * is relatively clear: We add the item to the existing stack... job done (might be good to have
+		 * tab reordering at some point, but lets not sweat it right now)
+		 *
+		 * If the item was dropped on the content part things are a bit more complicated. If it was dropped on either the
+		 * top or bottom region we need to create a new column and place the items accordingly.
+		 * Unless, of course if the stack is already within a column... in which case we want
+		 * to add the newly created item to the existing column...
+		 * either prepend or append it, depending on wether its top or bottom.
+		 *
+		 * Same thing for rows and left / right drop segments... so in total there are 9 things that can potentially happen
+		 * (left, top, right, bottom) * is child of the right parent (row, column) + header drop
+		 *
+		 * @param    {lm.item} contentItem
+		 *
+		 * @returns {void}
+		 */
+        _$onDrop: function (contentItem) {
+
+			/*
+			 * The item was dropped on the header area. Just add it as a child of this stack and
+			 * get the hell out of this logic
+			 */
+            if (this._dropSegment === 'header') {
+                this._resetHeaderDropZone();
+                this.addChild(contentItem, this._dropIndex);
+                return;
+            }
+
+			/*
+			 * The stack is empty. Let's just add the element.
+			 */
+            if (this._dropSegment === 'body') {
+                this.addChild(contentItem);
+                return;
+            }
+
+			/*
+			 * The item was dropped on the top-, left-, bottom- or right- part of the content. Let's
+			 * aggregate some conditions to make the if statements later on more readable
+			 */
+            var isVertical = this._dropSegment === 'top' || this._dropSegment === 'bottom',
+                isHorizontal = this._dropSegment === 'left' || this._dropSegment === 'right',
+                insertBefore = this._dropSegment === 'top' || this._dropSegment === 'left',
+                hasCorrectParent = (isVertical && this.parent.isColumn) || (isHorizontal && this.parent.isRow),
+                type = isVertical ? 'column' : 'row',
+                dimension = isVertical ? 'height' : 'width',
+                index,
+                stack,
+                rowOrColumn;
+
+			/*
+			 * The content item can be either a component or a stack. If it is a component, wrap it into a stack
+			 */
+            if (contentItem.isComponent) {
+                stack = this.layoutManager.createContentItem({
+                    type: 'stack',
+                    header: contentItem.config.header || {}
+                }, this);
+                stack._$init();
+                stack.addChild(contentItem);
+                contentItem = stack;
+            }
+
+			/*
+			 * If the item is dropped on top or bottom of a column or left and right of a row, it's already
+			 * layd out in the correct way. Just add it as a child
+			 */
+            if (hasCorrectParent) {
+                index = lm.utils.indexOf(this, this.parent.contentItems);
+                this.parent.addChild(contentItem, insertBefore ? index : index + 1, true);
+                this.config[dimension] *= 0.5;
+                contentItem.config[dimension] = this.config[dimension];
+                this.parent.callDownwards('setSize');
+				/*
+				 * This handles items that are dropped on top or bottom of a row or left / right of a column. We need
+				 * to create the appropriate contentItem for them to live in
+				 */
+            } else {
+                type = isVertical ? 'column' : 'row';
+                rowOrColumn = this.layoutManager.createContentItem({ type: type }, this);
+                this.parent.replaceChild(this, rowOrColumn);
+
+                rowOrColumn.addChild(contentItem, insertBefore ? 0 : undefined, true);
+                rowOrColumn.addChild(this, insertBefore ? undefined : 0, true);
+
+                this.config[dimension] = 50;
+                contentItem.config[dimension] = 50;
+                rowOrColumn.callDownwards('setSize');
+            }
+        },
+
+		/**
+		 * If the user hovers above the header part of the stack, indicate drop positions for tabs.
+		 * otherwise indicate which segment of the body the dragged item would be dropped on
+		 *
+		 * @param    {Int} x Absolute Screen X
+		 * @param    {Int} y Absolute Screen Y
+		 *
+		 * @returns {void}
+		 */
+        _$highlightDropZone: function (x, y) {
+            var segment, area;
+
+            for (segment in this._contentAreaDimensions) {
+                area = this._contentAreaDimensions[segment].hoverArea;
+
+                if (area.x1 < x && area.x2 > x && area.y1 < y && area.y2 > y) {
+
+                    if (segment === 'header') {
+                        this._dropSegment = 'header';
+                        this._highlightHeaderDropZone(this._sided ? y : x);
+                    } else {
+                        this._resetHeaderDropZone();
+                        this._highlightBodyDropZone(segment);
+                    }
+
+                    return;
+                }
+            }
+        },
+
+        _$getArea: function () {
+            if (this.element.is(':visible') === false) {
+                return null;
+            }
+
+            var getArea = lm.items.AbstractContentItem.prototype._$getArea,
+                headerArea = getArea.call(this, this.header.element),
+                contentArea = getArea.call(this, this.childElementContainer),
+                contentWidth = contentArea.x2 - contentArea.x1,
+                contentHeight = contentArea.y2 - contentArea.y1;
+
+            this._contentAreaDimensions = {
+                header: {
+                    hoverArea: {
+                        x1: headerArea.x1,
+                        y1: headerArea.y1,
+                        x2: headerArea.x2,
+                        y2: headerArea.y2
+                    },
+                    highlightArea: {
+                        x1: headerArea.x1,
+                        y1: headerArea.y1,
+                        x2: headerArea.x2,
+                        y2: headerArea.y2
+                    }
+                }
+            };
+
+			/**
+			 * If this Stack is a parent to rows, columns or other stacks only its
+			 * header is a valid dropzone.
+			 */
+            if (this._activeContentItem && this._activeContentItem.isComponent === false) {
+                return headerArea;
+            }
+
+			/**
+			 * Highlight the entire body if the stack is empty
+			 */
+            if (this.contentItems.length === 0) {
+
+                this._contentAreaDimensions.body = {
+                    hoverArea: {
+                        x1: contentArea.x1,
+                        y1: contentArea.y1,
+                        x2: contentArea.x2,
+                        y2: contentArea.y2
+                    },
+                    highlightArea: {
+                        x1: contentArea.x1,
+                        y1: contentArea.y1,
+                        x2: contentArea.x2,
+                        y2: contentArea.y2
+                    }
+                };
+
+                return getArea.call(this, this.element);
+            }
+
+            this._contentAreaDimensions.left = {
+                hoverArea: {
+                    x1: contentArea.x1,
+                    y1: contentArea.y1,
+                    x2: contentArea.x1 + contentWidth * 0.25,
+                    y2: contentArea.y2
+                },
+                highlightArea: {
+                    x1: contentArea.x1,
+                    y1: contentArea.y1,
+                    x2: contentArea.x1 + contentWidth * 0.5,
+                    y2: contentArea.y2
+                }
+            };
+
+            this._contentAreaDimensions.top = {
+                hoverArea: {
+                    x1: contentArea.x1 + contentWidth * 0.25,
+                    y1: contentArea.y1,
+                    x2: contentArea.x1 + contentWidth * 0.75,
+                    y2: contentArea.y1 + contentHeight * 0.5
+                },
+                highlightArea: {
+                    x1: contentArea.x1,
+                    y1: contentArea.y1,
+                    x2: contentArea.x2,
+                    y2: contentArea.y1 + contentHeight * 0.5
+                }
+            };
+
+            this._contentAreaDimensions.right = {
+                hoverArea: {
+                    x1: contentArea.x1 + contentWidth * 0.75,
+                    y1: contentArea.y1,
+                    x2: contentArea.x2,
+                    y2: contentArea.y2
+                },
+                highlightArea: {
+                    x1: contentArea.x1 + contentWidth * 0.5,
+                    y1: contentArea.y1,
+                    x2: contentArea.x2,
+                    y2: contentArea.y2
+                }
+            };
+
+            this._contentAreaDimensions.bottom = {
+                hoverArea: {
+                    x1: contentArea.x1 + contentWidth * 0.25,
+                    y1: contentArea.y1 + contentHeight * 0.5,
+                    x2: contentArea.x1 + contentWidth * 0.75,
+                    y2: contentArea.y2
+                },
+                highlightArea: {
+                    x1: contentArea.x1,
+                    y1: contentArea.y1 + contentHeight * 0.5,
+                    x2: contentArea.x2,
+                    y2: contentArea.y2
+                }
+            };
+
+            return getArea.call(this, this.element);
+        },
+
+        _highlightHeaderDropZone: function (x) {
+            var i,
+                tabElement,
+                tabsLength = this.header.tabs.length,
+                isAboveTab = false,
+                tabTop,
+                tabLeft,
+                offset,
+                placeHolderLeft,
+                headerOffset,
+                tabWidth,
+                halfX;
+
+            // Empty stack
+            if (tabsLength === 0) {
+                headerOffset = this.header.element.offset();
+
+                this.layoutManager.dropTargetIndicator.highlightArea({
+                    x1: headerOffset.left,
+                    x2: headerOffset.left + 100,
+                    y1: headerOffset.top + this.header.element.height() - 20,
+                    y2: headerOffset.top + this.header.element.height()
+                });
+
+                return;
+            }
+
+            for (i = 0; i < tabsLength; i++) {
+                tabElement = this.header.tabs[i].element;
+                offset = tabElement.offset();
+                if (this._sided) {
+                    tabLeft = offset.top;
+                    tabTop = offset.left;
+                    tabWidth = tabElement.height();
+                } else {
+                    tabLeft = offset.left;
+                    tabTop = offset.top;
+                    tabWidth = tabElement.width();
+                }
+
+                if (x > tabLeft && x < tabLeft + tabWidth) {
+                    isAboveTab = true;
+                    break;
+                }
+            }
+
+            if (isAboveTab === false && x < tabLeft) {
+                return;
+            }
+
+            halfX = tabLeft + tabWidth / 2;
+
+            if (x < halfX) {
+                this._dropIndex = i;
+                tabElement.before(this.layoutManager.tabDropPlaceholder);
+            } else {
+                this._dropIndex = Math.min(i + 1, tabsLength);
+                tabElement.after(this.layoutManager.tabDropPlaceholder);
+            }
+
+
+            if (this._sided) {
+                placeHolderTop = this.layoutManager.tabDropPlaceholder.offset().top;
+                this.layoutManager.dropTargetIndicator.highlightArea({
+                    x1: tabTop,
+                    x2: tabTop + tabElement.innerHeight(),
+                    y1: placeHolderTop,
+                    y2: placeHolderTop + this.layoutManager.tabDropPlaceholder.width()
+                });
+                return;
+            }
+            placeHolderLeft = this.layoutManager.tabDropPlaceholder.offset().left;
+
+            this.layoutManager.dropTargetIndicator.highlightArea({
+                x1: placeHolderLeft,
+                x2: placeHolderLeft + this.layoutManager.tabDropPlaceholder.width(),
+                y1: tabTop,
+                y2: tabTop + tabElement.innerHeight()
+            });
+        },
+
+        _resetHeaderDropZone: function () {
+            this.layoutManager.tabDropPlaceholder.remove();
+        },
+
+        _setupHeaderPosition: function () {
+            var side = ['right', 'left', 'bottom'].indexOf(this._header.show) >= 0 && this._header.show;
+            this.header.element.toggle(!!this._header.show);
+            this._side = side;
+            this._sided = ['right', 'left'].indexOf(this._side) >= 0;
+            this.element.removeClass('lm_left lm_right lm_bottom');
+            if (this._side)
+                this.element.addClass('lm_' + this._side);
+            if (this.element.find('.lm_header').length && this.childElementContainer) {
+                var headerPosition = ['right', 'bottom'].indexOf(this._side) >= 0 ? 'before' : 'after';
+                this.header.element[headerPosition](this.childElementContainer);
+                this.callDownwards('setSize');
+            }
+        },
+
+        _highlightBodyDropZone: function (segment) {
+            var highlightArea = this._contentAreaDimensions[segment].highlightArea;
+            this.layoutManager.dropTargetIndicator.highlightArea(highlightArea);
+            this._dropSegment = segment;
+        }
+    });
+
+    lm.utils.BubblingEvent = function (name, origin) {
+        this.name = name;
+        this.origin = origin;
+        this.isPropagationStopped = false;
+    };
+
+    lm.utils.BubblingEvent.prototype.stopPropagation = function () {
+        this.isPropagationStopped = true;
+    };
+	/**
+	 * Minifies and unminifies configs by replacing frequent keys
+	 * and values with one letter substitutes. Config options must
+	 * retain array position/index, add new options at the end.
+	 *
+	 * @constructor
+	 */
+    lm.utils.ConfigMinifier = function () {
+        this._keys = [
+            'settings',
+            'hasHeaders',
+            'constrainDragToContainer',
+            'selectionEnabled',
+            'dimensions',
+            'borderWidth',
+            'minItemHeight',
+            'minItemWidth',
+            'headerHeight',
+            'dragProxyWidth',
+            'dragProxyHeight',
+            'labels',
+            'close',
+            'maximise',
+            'minimise',
+            'popout',
+            'content',
+            'componentName',
+            'componentState',
+            'id',
+            'width',
+            'type',
+            'height',
+            'isClosable',
+            'title',
+            'popoutWholeStack',
+            'openPopouts',
+            'parentId',
+            'activeItemIndex',
+            'reorderEnabled',
+            'borderGrabWidth',
+
+
+
+
+            //Maximum 36 entries, do not cross this line!
+        ];
+        if (this._keys.length > 36) {
+            throw new Error('Too many keys in config minifier map');
+        }
+
+        this._values = [
+            true,
+            false,
+            'row',
+            'column',
+            'stack',
+            'component',
+            'close',
+            'maximise',
+            'minimise',
+            'open in new window'
+        ];
+    };
+
+    lm.utils.copy(lm.utils.ConfigMinifier.prototype, {
+
+		/**
+		 * Takes a GoldenLayout configuration object and
+		 * replaces its keys and values recursively with
+		 * one letter counterparts
+		 *
+		 * @param   {Object} config A GoldenLayout config object
+		 *
+		 * @returns {Object} minified config
+		 */
+        minifyConfig: function (config) {
+            var min = {};
+            this._nextLevel(config, min, '_min');
+            return min;
+        },
+
+		/**
+		 * Takes a configuration Object that was previously minified
+		 * using minifyConfig and returns its original version
+		 *
+		 * @param   {Object} minifiedConfig
+		 *
+		 * @returns {Object} the original configuration
+		 */
+        unminifyConfig: function (minifiedConfig) {
+            var orig = {};
+            this._nextLevel(minifiedConfig, orig, '_max');
+            return orig;
+        },
+
+		/**
+		 * Recursive function, called for every level of the config structure
+		 *
+		 * @param   {Array|Object} orig
+		 * @param   {Array|Object} min
+		 * @param    {String} translationFn
+		 *
+		 * @returns {void}
+		 */
+        _nextLevel: function (from, to, translationFn) {
+            var key, minKey;
+
+            for (key in from) {
+
+				/**
+				 * For in returns array indices as keys, so let's cast them to numbers
+				 */
+                if (from instanceof Array) key = parseInt(key, 10);
+
+				/**
+				 * In case something has extended Object prototypes
+				 */
+                if (!from.hasOwnProperty(key)) continue;
+
+				/**
+				 * Translate the key to a one letter substitute
+				 */
+                minKey = this[translationFn](key, this._keys);
+
+				/**
+				 * For Arrays and Objects, create a new Array/Object
+				 * on the minified object and recurse into it
+				 */
+                if (typeof from[key] === 'object') {
+                    to[minKey] = from[key] instanceof Array ? [] : {};
+                    this._nextLevel(from[key], to[minKey], translationFn);
+
+					/**
+					 * For primitive values (Strings, Numbers, Boolean etc.)
+					 * minify the value
+					 */
+                } else {
+                    to[minKey] = this[translationFn](from[key], this._values);
+                }
+            }
+        },
+
+		/**
+		 * Minifies value based on a dictionary
+		 *
+		 * @param   {String|Boolean} value
+		 * @param   {Array<String|Boolean>} dictionary
+		 *
+		 * @returns {String} The minified version
+		 */
+        _min: function (value, dictionary) {
+			/**
+			 * If a value actually is a single character, prefix it
+			 * with ___ to avoid mistaking it for a minification code
+			 */
+            if (typeof value === 'string' && value.length === 1) {
+                return '___' + value;
+            }
+
+            var index = lm.utils.indexOf(value, dictionary);
+
+			/**
+			 * value not found in the dictionary, return it unmodified
+			 */
+            if (index === -1) {
+                return value;
+
+				/**
+				 * value found in dictionary, return its base36 counterpart
+				 */
+            } else {
+                return index.toString(36);
+            }
+        },
+
+        _max: function (value, dictionary) {
+			/**
+			 * value is a single character. Assume that it's a translation
+			 * and return the original value from the dictionary
+			 */
+            if (typeof value === 'string' && value.length === 1) {
+                return dictionary[parseInt(value, 36)];
+            }
+
+			/**
+			 * value originally was a single character and was prefixed with ___
+			 * to avoid mistaking it for a translation. Remove the prefix
+			 * and return the original character
+			 */
+            if (typeof value === 'string' && value.substr(0, 3) === '___') {
+                return value[3];
+            }
+			/**
+			 * value was not minified
+			 */
+            return value;
+        }
+    });
+
+	/**
+	 * An EventEmitter singleton that propagates events
+	 * across multiple windows. This is a little bit trickier since
+	 * windows are allowed to open childWindows in their own right
+	 *
+	 * This means that we deal with a tree of windows. Hence the rules for event propagation are:
+	 *
+	 * - Propagate events from this layout to both parents and children
+	 * - Propagate events from parent to this and children
+	 * - Propagate events from children to the other children (but not the emitting one) and the parent
+	 *
+	 * @constructor
+	 *
+	 * @param {lm.LayoutManager} layoutManager
+	 */
+    lm.utils.EventHub = function (layoutManager) {
+        lm.utils.EventEmitter.call(this);
+        this._layoutManager = layoutManager;
+        this._dontPropagateToParent = null;
+        this._childEventSource = null;
+        this.on(lm.utils.EventEmitter.ALL_EVENT, lm.utils.fnBind(this._onEventFromThis, this));
+        this._boundOnEventFromChild = lm.utils.fnBind(this._onEventFromChild, this);
+        $(window).on('gl_child_event', this._boundOnEventFromChild);
+    };
+
+	/**
+	 * Called on every event emitted on this eventHub, regardles of origin.
+	 *
+	 * @private
+	 *
+	 * @param {Mixed}
+	 *
+	 * @returns {void}
+	 */
+    lm.utils.EventHub.prototype._onEventFromThis = function () {
+        var args = Array.prototype.slice.call(arguments);
+
+        if (this._layoutManager.isSubWindow && args[0] !== this._dontPropagateToParent) {
+            this._propagateToParent(args);
+        }
+        this._propagateToChildren(args);
+
+        //Reset
+        this._dontPropagateToParent = null;
+        this._childEventSource = null;
+    };
+
+	/**
+	 * Called by the parent layout.
+	 *
+	 * @param   {Array} args Event name + arguments
+	 *
+	 * @returns {void}
+	 */
+    lm.utils.EventHub.prototype._$onEventFromParent = function (args) {
+        this._dontPropagateToParent = args[0];
+        this.emit.apply(this, args);
+    };
+
+	/**
+	 * Callback for child events raised on the window
+	 *
+	 * @param   {DOMEvent} event
+	 * @private
+	 *
+	 * @returns {void}
+	 */
+    lm.utils.EventHub.prototype._onEventFromChild = function (event) {
+        this._childEventSource = event.originalEvent.__gl;
+        this.emit.apply(this, event.originalEvent.__glArgs);
+    };
+
+	/**
+	 * Propagates the event to the parent by emitting
+	 * it on the parent's DOM window
+	 *
+	 * @param   {Array} args Event name + arguments
+	 * @private
+	 *
+	 * @returns {void}
+	 */
+    lm.utils.EventHub.prototype._propagateToParent = function (args) {
+        var event,
+            eventName = 'gl_child_event';
+
+        if (document.createEvent) {
+            event = window.opener.document.createEvent('HTMLEvents');
+            event.initEvent(eventName, true, true);
+        } else {
+            event = window.opener.document.createEventObject();
+            event.eventType = eventName;
+        }
+
+        event.eventName = eventName;
+        event.__glArgs = args;
+        event.__gl = this._layoutManager;
+
+        if (document.createEvent) {
+            window.opener.dispatchEvent(event);
+        } else {
+            window.opener.fireEvent('on' + event.eventType, event);
+        }
+    };
+
+	/**
+	 * Propagate events to children
+	 *
+	 * @param   {Array} args Event name + arguments
+	 * @private
+	 *
+	 * @returns {void}
+	 */
+    lm.utils.EventHub.prototype._propagateToChildren = function (args) {
+        var childGl, i;
+
+        for (i = 0; i < this._layoutManager.openPopouts.length; i++) {
+            childGl = this._layoutManager.openPopouts[i].getGlInstance();
+
+            if (childGl && childGl !== this._childEventSource) {
+                childGl.eventHub._$onEventFromParent(args);
+            }
+        }
+    };
+
+
+	/**
+	 * Destroys the EventHub
+	 *
+	 * @public
+	 * @returns {void}
+	 */
+
+    lm.utils.EventHub.prototype.destroy = function () {
+        $(window).off('gl_child_event', this._boundOnEventFromChild);
+    };
+	/**
+	 * A specialised GoldenLayout component that binds GoldenLayout container
+	 * lifecycle events to react components
+	 *
+	 * @constructor
+	 *
+	 * @param {lm.container.ItemContainer} container
+	 * @param {Object} state state is not required for react components
+	 */
+    lm.utils.ReactComponentHandler = function (container, state) {
+        this._reactComponent = null;
+        this._originalComponentWillUpdate = null;
+        this._container = container;
+        this._initialState = state;
+        this._reactClass = this._getReactClass();
+        this._container.on('open', this._render, this);
+        this._container.on('destroy', this._destroy, this);
+    };
+
+    lm.utils.copy(lm.utils.ReactComponentHandler.prototype, {
+
+		/**
+		 * Creates the react class and component and hydrates it with
+		 * the initial state - if one is present
+		 *
+		 * By default, react's getInitialState will be used
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _render: function () {
+            this._reactComponent = ReactDOM.render(this._getReactComponent(), this._container.getElement()[0]);
+            this._originalComponentWillUpdate = this._reactComponent.componentWillUpdate || function () {
+            };
+            this._reactComponent.componentWillUpdate = this._onUpdate.bind(this);
+            if (this._container.getState()) {
+                this._reactComponent.setState(this._container.getState());
+            }
+        },
+
+		/**
+		 * Removes the component from the DOM and thus invokes React's unmount lifecycle
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _destroy: function () {
+            ReactDOM.unmountComponentAtNode(this._container.getElement()[0]);
+            this._container.off('open', this._render, this);
+            this._container.off('destroy', this._destroy, this);
+        },
+
+		/**
+		 * Hooks into React's state management and applies the componentstate
+		 * to GoldenLayout
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _onUpdate: function (nextProps, nextState) {
+            this._container.setState(nextState);
+            this._originalComponentWillUpdate.call(this._reactComponent, nextProps, nextState);
+        },
+
+		/**
+		 * Retrieves the react class from GoldenLayout's registry
+		 *
+		 * @private
+		 * @returns {React.Class}
+		 */
+        _getReactClass: function () {
+            var componentName = this._container._config.component;
+            var reactClass;
+
+            if (!componentName) {
+                throw new Error('No react component name. type: react-component needs a field `component`');
+            }
+
+            reactClass = this._container.layoutManager.getComponent(componentName);
+
+            if (!reactClass) {
+                throw new Error('React component "' + componentName + '" not found. ' +
+                    'Please register all components with GoldenLayout using `registerComponent(name, component)`');
+            }
+
+            return reactClass;
+        },
+
+		/**
+		 * Copies and extends the properties array and returns the React element
+		 *
+		 * @private
+		 * @returns {React.Element}
+		 */
+        _getReactComponent: function () {
+            var defaultProps = {
+                glEventHub: this._container.layoutManager.eventHub,
+                glContainer: this._container,
+            };
+            var props = $.extend(defaultProps, this._container._config.props);
+            return React.createElement(this._reactClass, props);
+        }
+    });
+})(window.$);
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 21ca08471..6f721a0c8 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,25 +1,23 @@
-import * as GoldenLayout from "golden-layout";
 import 'golden-layout/src/css/goldenlayout-base.css';
 import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, observable, reaction, trace, runInAction } from "mobx";
+import { action, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
 import Measure from "react-measure";
-import { Utils, returnTrue, emptyFunction, returnOne, returnZero } from "../../../Utils";
+import * as GoldenLayout from "../../../client/goldenLayout";
+import { Doc, Field, Opt } from "../../../new_fields/Doc";
+import { FieldId, Id } from "../../../new_fields/RefField";
+import { listSpec } from "../../../new_fields/Schema";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, returnTrue, Utils } from "../../../Utils";
+import { DocServer } from "../../DocServer";
+import { DragLinksAsDocuments, DragManager } from "../../util/DragManager";
+import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionDockingView.scss";
-import React = require("react");
 import { SubCollectionViewProps } from "./CollectionSubView";
-import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
-import { Transform } from '../../util/Transform';
-import { Doc, Opt, Field } from "../../../new_fields/Doc";
-import { Cast, NumCast, StrCast, PromiseValue } from "../../../new_fields/Types";
-import { List } from "../../../new_fields/List";
-import { DocServer } from "../../DocServer";
-import { listSpec } from "../../../new_fields/Schema";
-import { Id, FieldId } from "../../../new_fields/RefField";
-import { faSignInAlt } from "@fortawesome/free-solid-svg-icons";
+import React = require("react");
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -286,6 +284,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     tabCreated = async (tab: any) => {
         if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
+            if (tab.contentItem.config.fixed) {
+                tab.contentItem.parent.config.fixed = true;
+            }
             DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async doc => {
                 if (doc instanceof Doc) {
                     let counter: any = this.htmlToElement(`<div class="messageCounter">0</div>`);
@@ -325,16 +326,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             .click(action(function () {
                 //if (confirm('really close this?')) {
                 stack.remove();
-                //}
-            }));
-        stack.header.controlsContainer.find('.lm_popout') //get the close icon
-            .off('click') //unbind the current click handler
-            .click(action(function () {
-                var url = DocServer.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
-                let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");
-            }));
-        stack.header.controlsContainer.find('.lm_close') //unbind the current click handler
-            .click(async function () {
                 stack.contentItems.map(async (contentItem: any) => {
                     let doc = await DocServer.GetRefField(contentItem.config.props.documentId);
                     if (doc instanceof Doc) {
@@ -342,7 +333,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         CollectionDockingView.Instance._removedDocs.push(theDoc);
                     }
                 });
-            });
+                //}
+            }));
+        stack.header.controlsContainer.find('.lm_popout') //get the close icon
+            .off('click') //unbind the current click handler
+            .click(action(function () {
+                stack.config.fixed = !stack.config.fixed;
+                // var url = DocServer.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
+                // let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");
+            }));
     }
 
     render() {
@@ -351,6 +350,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
         );
     }
+
 }
 
 interface DockedFrameProps {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 947a066d9..429d0f047 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -20,6 +20,7 @@ import { CollectionDockingView } from './CollectionDockingView';
 import { DocumentManager } from '../../util/DocumentManager';
 import { Utils } from '../../../Utils';
 import { List } from '../../../new_fields/List';
+import { indexOf } from 'typescript-collections/dist/lib/arrays';
 
 
 export interface TreeViewProps {
@@ -48,8 +49,15 @@ class TreeView extends React.Component<TreeViewProps> {
 
     @observable _collapsed: boolean = true;
 
-    delete = () => this.props.deleteDoc(this.props.document);
-    openRight = () => CollectionDockingView.Instance.AddRightSplit(this.props.document);
+    @undoBatch delete = () => this.props.deleteDoc(this.props.document);
+
+    @undoBatch openRight = () => {
+        if (this.props.document.dockingConfig) {
+            Main.Instance.openWorkspace(this.props.document);
+        } else {
+            CollectionDockingView.Instance.AddRightSplit(this.props.document);
+        }
+    };
 
     get children() {
         return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed?    .filter(doc => FieldValue(doc));
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index cf08c1bc4..2c8e6aef3 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -125,15 +125,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public toggleIcon = async (): Promise<void> => {
         SelectionManager.DeselectAll();
         let isMinimized: boolean | undefined;
-        let maximizedDocs = await Cast(this.props.Document.maximizedDocs, listSpec(Doc));
+        let maximizedDocs = Cast(this.props.Document.maximizedDocs, listSpec(Doc));
         let minimizedDoc: Doc | undefined = this.props.Document;
         if (!maximizedDocs) {
             minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
-            if (minimizedDoc) maximizedDocs = await Cast(minimizedDoc.maximizedDocs, listSpec(Doc));
+            if (minimizedDoc) maximizedDocs = Cast(minimizedDoc.maximizedDocs, listSpec(Doc));
         }
-        if (minimizedDoc && maximizedDocs && maximizedDocs instanceof List) {
+        if (minimizedDoc && maximizedDocs) {
             let minimizedTarget = minimizedDoc;
-            maximizedDocs.map(maximizedDoc => {
+            (await Promise.all(maximizedDocs)).forEach(maximizedDoc => {
                 let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
                 if (!iconAnimating || (Date.now() - iconAnimating[6] > 1000)) {
                     if (isMinimized === undefined) {
@@ -166,26 +166,25 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         let metaKey = e.metaKey;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            if (await BoolCast(this.props.Document.isButton, false)) {
+            if (BoolCast(this.props.Document.isButton, false)) {
                 let maximizedDocs = await Cast(this.props.Document.maximizedDocs, listSpec(Doc));
                 if (maximizedDocs) {   // bcz: need a better way to associate behaviors with click events on widget-documents
                     if ((metaKey && !this.props.Document.maximizeOnRight) || (!metaKey && this.props.Document.maximizeOnRight)) {
-                        SelectionManager.DeselectAll();
-                        maximizedDocs.map(async mdoc => {
-                            let maxDoc = await mdoc;
-                            let dataDocs = await Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc));
-                            if (dataDocs) {
-                                Promise.all(dataDocs.map(async doc => await doc)).then(docs => {
-                                    if (!docs || docs.indexOf(maxDoc) == -1) {
-                                        CollectionDockingView.Instance.AddRightSplit(maxDoc);
-                                    } else {
-                                        CollectionDockingView.Instance.CloseRightSplit(maxDoc);
-                                    }
-                                })
-                            }
-                        });
+                        let dataDocs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc));
+                        if (dataDocs) {
+                            const docs = await Promise.all(dataDocs);
+                            const maxDocs = await Promise.all(maximizedDocs);
+                            SelectionManager.DeselectAll();
+                            maxDocs.forEach(maxDoc => {
+                                if (!docs || docs.indexOf(maxDoc) == -1) {
+                                    CollectionDockingView.Instance.AddRightSplit(maxDoc);
+                                } else {
+                                    CollectionDockingView.Instance.CloseRightSplit(maxDoc);
+                                }
+                            });
+                        }
                     } else {
-                        this.props.addDocument && maximizedDocs.map(async maxDoc => this.props.addDocument!(await maxDoc, false));
+                        this.props.addDocument && maximizedDocs.forEach(async maxDoc => this.props.addDocument!(await maxDoc, false));
                         this.toggleIcon();
                     }
                 }
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index 96018dafa..213ed21aa 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -206,7 +206,7 @@ class ListImpl<T extends Field> extends ObjectField {
         return list;
     }
 
-    [key: number]: FieldResult<T>;
+    [key: number]: T | (T extends RefField ? Promise<T> : never);
 
     @serializable(alias("fields", list(autoObject())))
     private get __fields() {
-- 
cgit v1.2.3-70-g09d2


From 684c8e190098dee8c285665ebf1e2c598bd5cf4c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 6 May 2019 15:28:59 -0400
Subject: fixed a few golden layout and tree view issues.

---
 src/client/views/collections/CollectionDockingView.tsx |  4 +++-
 src/client/views/collections/CollectionTreeView.tsx    | 18 ++++++++++++++----
 src/client/views/nodes/IconBox.tsx                     | 14 +++++++++++++-
 src/new_fields/Doc.ts                                  |  1 -
 4 files changed, 30 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6f721a0c8..05c467763 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -78,14 +78,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
                     child.contentItems[0].config.props.documentId == document[Id]) {
                     child.contentItems[0].remove();
-                    //this._goldenLayout.root.contentItems[0].contentItems.splice(i, 1);
                     this.layoutChanged(document);
+                    this.stateChanged();
                 } else
                     child.contentItems.map((tab: any, j: number) => {
                         if (tab.config.component === "DocumentFrameRenderer" && tab.config.props.documentId === document[Id]) {
                             child.contentItems[j].remove();
+                            child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
                             let docs = Cast(this.props.Document.data, listSpec(Doc));
                             docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1);
+                            this.stateChanged();
                         }
                     });
             })
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 429d0f047..6fa374464 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -10,7 +10,7 @@ import "./CollectionTreeView.scss";
 import React = require("react");
 import { Document, listSpec } from '../../../new_fields/Schema';
 import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types';
-import { Doc } from '../../../new_fields/Doc';
+import { Doc, DocListCast } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/RefField';
 import { ContextMenu } from '../ContextMenu';
 import { undoBatch } from '../../util/UndoManager';
@@ -51,7 +51,7 @@ class TreeView extends React.Component<TreeViewProps> {
 
     @undoBatch delete = () => this.props.deleteDoc(this.props.document);
 
-    @undoBatch openRight = () => {
+    @undoBatch openRight = async () => {
         if (this.props.document.dockingConfig) {
             Main.Instance.openWorkspace(this.props.document);
         } else {
@@ -63,6 +63,10 @@ class TreeView extends React.Component<TreeViewProps> {
         return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed?    .filter(doc => FieldValue(doc));
     }
 
+    onPointerDown = (e: React.PointerEvent) => {
+        e.stopPropagation();
+    }
+
     @action
     remove = (document: Document) => {
         let children = Cast(this.props.document.data, listSpec(Doc), []);
@@ -109,11 +113,18 @@ class TreeView extends React.Component<TreeViewProps> {
                     return true;
                 }}
             />);
+        let dataDocs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []);
+        let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : (
+            <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
+                <FontAwesomeIcon icon="angle-right" size="lg" />
+                <FontAwesomeIcon icon="angle-right" size="lg" />
+            </div>);
         return (
             <div className="docContainer" ref={reference} onPointerDown={onItemDown}
+                style={{ background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
                 onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
                 {editableView(StrCast(this.props.document.title))}
-                <div className="treeViewItem-openRight" onClick={this.openRight}><FontAwesomeIcon icon="angle-right" size="lg" /><FontAwesomeIcon icon="angle-right" size="lg" /></div>
+                {openRight}
                 {/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */}
             </div >);
     }
@@ -156,7 +167,6 @@ class TreeView extends React.Component<TreeViewProps> {
             }
         });
         return <div className="treeViewItem-container"
-            style={{ background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
             onContextMenu={this.onWorkspaceContextMenu}>
             <li className="collection-child">
                 {this.renderBullet(bulletType)}
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 3fab10df4..b521d5ce6 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -2,7 +2,7 @@ import React = require("react");
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
+import { action, computed, observable, runInAction, reaction, IReactionDisposer } from "mobx";
 import { observer } from "mobx-react";
 import { SelectionManager } from "../../util/SelectionManager";
 import { FieldView, FieldViewProps } from './FieldView';
@@ -25,6 +25,17 @@ library.add(faFilm);
 @observer
 export class IconBox extends React.Component<FieldViewProps> {
     public static LayoutString() { return FieldView.LayoutString(IconBox); }
+    _reactionDisposer?: IReactionDisposer;
+    componentDidMount() {
+        this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs],
+            () => {
+                let maxDoc = Cast(this.props.Document.maximizedDocs, listSpec(Doc), []);
+                this.props.Document.title = maxDoc && (maxDoc.length === 1 ? maxDoc[0].title + ".icon" : "");
+            }, { fireImmediately: true });
+    }
+    componentWillUnmount() {
+        if (this._reactionDisposer) this._reactionDisposer();
+    }
 
     @computed get layout(): string { const field = Cast(this.props.Document[this.props.fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
     @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); }
@@ -52,6 +63,7 @@ export class IconBox extends React.Component<FieldViewProps> {
     @observable _panelWidth: number = 0;
     @observable _panelHeight: number = 0;
     render() {
+        let title = this._title;
         let labelField = StrCast(this.props.Document.labelField);
         let hideLabel = BoolCast(this.props.Document.hideLabel);
         let maxDoc = Cast(this.props.Document.maximizedDocs, listSpec(Doc), []);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 70dd8361b..38c220bc8 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -220,7 +220,6 @@ export namespace Doc {
             return undefined;
         }
         const delegate = new Doc();
-        //TODO Does this need to be doc[Self]?
         delegate.proto = doc;
         return delegate;
     }
-- 
cgit v1.2.3-70-g09d2


From a573867d2443a806f174db58e2a920db3405934c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 8 May 2019 10:23:28 -0400
Subject: added delete from non-data keys in tree view.  fixed undo
 iconanimating problem

---
 src/client/views/collections/CollectionTreeView.tsx  | 20 ++++++++++++--------
 .../views/nodes/CollectionFreeFormDocumentView.tsx   |  4 +++-
 2 files changed, 15 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 6fa374464..6e690236f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -68,8 +68,8 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     @action
-    remove = (document: Document) => {
-        let children = Cast(this.props.document.data, listSpec(Doc), []);
+    remove = (document: Document, key: string) => {
+        let children = Cast(this.props.document[key], listSpec(Doc), []);
         if (children) {
             children.splice(children.indexOf(document), 1);
         }
@@ -81,7 +81,7 @@ class TreeView extends React.Component<TreeViewProps> {
             return true;
         }
         //TODO This should check if it was removed
-        this.remove(document);
+        this.remove(document, "data");
         return addDoc(document);
     }
 
@@ -136,7 +136,11 @@ class TreeView extends React.Component<TreeViewProps> {
             if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
                 ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
             }
-            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
+            ContextMenu.Instance.addItem({
+                description: "Delete", event: undoBatch(() => {
+                    this.props.deleteDoc(this.props.document);
+                })
+            });
             ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
             e.stopPropagation();
         }
@@ -160,7 +164,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     contentElement.push(<ul key={key + "more"}>
                         {(key === "data") ? (null) :
                             <span className="collectionTreeView-keyHeader" key={key}>{key}</span>}
-                        {TreeView.GetChildElements(docList, key !== "data", this.remove, this.move, this.props.dropAction)}
+                        {TreeView.GetChildElements(docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction)}
                     </ul >);
                 } else
                     bulletType = BulletType.Collapsed;
@@ -175,9 +179,9 @@ class TreeView extends React.Component<TreeViewProps> {
             </li>
         </div>;
     }
-    public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
-        return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).filter(doc => FieldValue(doc)).map(child =>
-            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
+    public static GetChildElements(docs: (Doc | Promise<Doc>)[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
+        return docs.filter(child => child instanceof Doc && !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).filter(doc => FieldValue(doc)).map(child =>
+            <TreeView document={child as Doc} key={(child as Doc)[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
     }
 }
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 0e34cb626..5efc75793 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -138,7 +138,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         }
         if (minimizedDoc && maximizedDocs) {
             let minimizedTarget = minimizedDoc;
-            CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
+            if (!CollectionFreeFormDocumentView._undoBatch) {
+                CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
+            }
             maximizedDocs.forEach(maximizedDoc => {
                 let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
                 if (!iconAnimating || (Date.now() - iconAnimating[6] > 1000)) {
-- 
cgit v1.2.3-70-g09d2


From 7da76d2538ebde21d7a878b5096d5a673e5d6375 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 10 May 2019 23:01:08 -0400
Subject: added summary screen grab icon as a template view of a text document.
  converted Cast(...ListSpec(Doc)) pattern to DocListCast

---
 src/client/northstar/dash-nodes/HistogramBox.tsx   | 10 +++----
 src/client/util/DocumentManager.ts                 |  6 ++--
 src/client/util/DragManager.ts                     |  6 ++--
 src/client/views/PresentationView.tsx              | 11 +++++--
 src/client/views/Templates.tsx                     | 35 +++++++++++++++-------
 .../views/collections/CollectionBaseView.tsx       |  8 ++---
 .../views/collections/CollectionSchemaView.tsx     |  6 ++--
 src/client/views/collections/CollectionSubView.tsx |  4 +--
 .../views/collections/CollectionTreeView.tsx       | 22 +++++++-------
 .../CollectionFreeFormLinksView.tsx                | 12 ++++----
 .../collections/collectionFreeForm/MarqueeView.tsx | 24 +++++++++++----
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 28 +++++++++--------
 src/client/views/nodes/DocumentView.tsx            | 23 +++++++-------
 src/client/views/nodes/IconBox.tsx                 |  4 +--
 src/client/views/nodes/ImageBox.tsx                | 14 ++++++---
 src/client/views/nodes/LinkMenu.tsx                |  6 ++--
 src/new_fields/Doc.ts                              | 10 +++++--
 17 files changed, 137 insertions(+), 92 deletions(-)

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

diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 5e7b867b3..ed556cf45 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -19,7 +19,7 @@ import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
 import { StyleConstants } from "../utils/StyleContants";
 import { NumCast, Cast } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/RefField";
 
 
@@ -117,15 +117,15 @@ export class HistogramBox extends React.Component<FieldViewProps> {
             runInAction(() => {
                 this.HistoOp = histoOp ? histoOp.HistoOp : HistogramOperation.Empty;
                 if (this.HistoOp !== HistogramOperation.Empty) {
-                    reaction(() => Cast(this.props.Document.linkedFromDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc), (docs) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
-                    reaction(() => Cast(this.props.Document.brushingDocs, listSpec(Doc), []).length,
+                    reaction(() => DocListCast(this.props.Document.linkedFromDocs), (docs) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+                    reaction(() => DocListCast(this.props.Document.brushingDocs).length,
                         () => {
-                            let brushingDocs = Cast(this.props.Document.brushingDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+                            let brushingDocs = DocListCast(this.props.Document.brushingDocs);
                             const proto = this.props.Document.proto;
                             if (proto) {
                                 this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => {
                                     brush.backgroundColor = StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length];
-                                    let brushed = Cast(brush.brushingDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+                                    let brushed = DocListCast(brush.brushingDocs);
                                     return { l: brush, b: brushed[0][Id] === proto[Id] ? brushed[1] : brushed[0] };
                                 }));
                             }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index a8b643d4d..9a7a94228 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,6 +1,6 @@
 import { computed, observable } from 'mobx';
 import { DocumentView } from '../views/nodes/DocumentView';
-import { Doc } from '../../new_fields/Doc';
+import { Doc, DocListCast } from '../../new_fields/Doc';
 import { FieldValue, Cast, NumCast, BoolCast } from '../../new_fields/Types';
 import { listSpec } from '../../new_fields/Schema';
 import { undoBatch } from './UndoManager';
@@ -73,7 +73,7 @@ export class DocumentManager {
     @computed
     public get LinkedDocumentViews() {
         return DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false)).reduce((pairs, dv) => {
-            let linksList = Cast(dv.props.Document.linkedToDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+            let linksList = DocListCast(dv.props.Document.linkedToDocs);
             if (linksList && linksList.length) {
                 pairs.push(...linksList.reduce((pairs, link) => {
                     if (link) {
@@ -86,7 +86,7 @@ export class DocumentManager {
                     return pairs;
                 }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
             }
-            linksList = Cast(dv.props.Document.linkedFromDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+            linksList = DocListCast(dv.props.Document.linkedFromDocs);
             if (linksList && linksList.length) {
                 pairs.push(...linksList.reduce((pairs, link) => {
                     if (link) {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index c0402f0c9..2da0d5b51 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,5 +1,5 @@
 import { action, runInAction } from "mobx";
-import { Doc, DocListCast } from "../../new_fields/Doc";
+import { Doc, DocListCastAsync } from "../../new_fields/Doc";
 import { Cast } from "../../new_fields/Types";
 import { emptyFunction } from "../../Utils";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
@@ -43,8 +43,8 @@ export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: n
     let draggedDocs: Doc[] = [];
     let draggedFromDocs: Doc[] = []
     if (srcTarg) {
-        let linkToDocs = await DocListCast(srcTarg.linkedToDocs);
-        let linkFromDocs = await DocListCast(srcTarg.linkedFromDocs);
+        let linkToDocs = await DocListCastAsync(srcTarg.linkedToDocs);
+        let linkFromDocs = await DocListCastAsync(srcTarg.linkedFromDocs);
         if (linkToDocs) draggedDocs = linkToDocs.map(linkDoc => Cast(linkDoc.linkedTo, Doc) as Doc);
         if (linkFromDocs) draggedFromDocs = linkFromDocs.map(linkDoc => Cast(linkDoc.linkedFrom, Doc) as Doc);
     }
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index 4853eb151..3fb24a339 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -141,8 +141,15 @@ export class PresentationView extends React.Component<PresViewProps>  {
             (activeW) => {
                 if (activeW && activeW instanceof Doc) {
                     PromiseValue(Cast(activeW.presentationView, Doc)).
-                        then(pv => runInAction(() =>
-                            self.Document = pv ? pv : (activeW.presentationView = new Doc())))
+                        then(pv => runInAction(() => {
+                            if (pv) self.Document = pv;
+                            else {
+                                pv = new Doc();
+                                pv.title = "Presentation Doc";
+                                activeW.presentationView = pv;
+                                self.Document = pv;
+                            }
+                        }))
                 }
             },
             { fireImmediately: true });
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index bbedc95f1..a98870b04 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -5,6 +5,7 @@ export enum TemplatePosition {
     InnerBottom,
     InnerRight,
     InnerLeft,
+    TopRight,
     OutterTop,
     OutterBottom,
     OutterRight,
@@ -39,30 +40,42 @@ export namespace Templates {
     // export const BasicLayout = new Template("Basic layout", "{layout}");
 
     export const Caption = new Template("Caption", TemplatePosition.OutterBottom,
-        `<div id="screenSpace" style="top: 100%; font-size:14px; background:yellow; width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={"caption"} /></div>`
-    );
+        `<div id="screenSpace" style="top: 100%; font-size:14px; background:yellow; width:100%; position:absolute">
+            <FormattedTextBox {...props} fieldKey={"caption"} />
+        </div>` );
+
     export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop,
-        `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div>
-        <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; ">
-        <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
-        </div></div>`
-    );
+        `<div>
+            <div style="height:100%; width:100%;position:absolute;">{layout}</div>
+            <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; ">
+                <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
+            </div>
+        </div>` );
+
     export const Title = new Template("Title", TemplatePosition.InnerTop,
         `<div><div style="height:calc(100% - 25px); margin-top: 25px; width:100%;position:absolute;">{layout}</div>
         <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; ">
             <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
-        </div></div>`
-    );
+        </div></div>` );
 
     export const Bullet = new Template("Bullet", TemplatePosition.InnerTop,
         `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div>
-        <div id="isBullet" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;">
-        <img  id="isBullet"  src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAZlBMVEX///8AAABmZmb7+/tYWFhgYGBFRUVSUlL4+Pg/Pz9jY2N5eXmcnJyioqKBgYFzc3NtbW1LS0s3NzfW1taWlpaOjo6IiIgvLy9WVlampqZcXFw5OTlvb28mJiYxMTHe3t7l5eUjIyMY8kIZAAAD2UlEQVR4nO2d61biMBRGW1FBEVHxfp15/5ecOVa5lHxtArmck/Xtn1BotjtNoXQtm4YQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEFIrX6UHEA1gsmrneceRjHm7cj28attKFOf/TRyKIliH4vzbZE+xE2zbZYkxRWX5Y9JT/BW0X3G+NtlR3Ahar7jcMtlS3Ba0XXG+Y7JW3BW0XHHZM/lR7AvaVewL/ijuC1pV3Bf8VnQJ2lR0CYriq/Nxg4puwfa1aZ7dz9yUHnEgN26NZ3luWkPFd7fEtHsWVDwpO+YgTgYKCuYn6tAU7TBecaygcGpZEQie7m5luKJPQQFUvCwx5iAuvQoK4KShvSIoOHVtCz7dnOUecxBn7kG/urc2eCz6T9EOcxXDCgpAUetyAwoOCBqrGF5QMKR4mCA8L+pTBIJwkRl95eifJjPHTDYTFQ8vePyrs3BsBfXLzfFHkvKKMY4j1ctNnCmmuGKslfCQT0RZiPdFVmnFmOcy36sDWYn7DU9hxdifRkKuEGQh/pWW0K/QiUlxtUxVxTTXyhQtN6kuI6mpmO5qpxJFIBjl1yMVimmvV4PfrnIq3iYsKICTRj7F9L84gIq38fYwCCj4HnMfRY/FPL8ZFayYo6BQbLlJeZrYpVDFXAUFcMtKWkUgmOhmnwKKOQsK4NaxdIp5CwqZj8X8gv27jNecJ9nZuXtnie/SzjhRQcHkt6Fnq1imoAAUY1csVVDIUrFcQSGDIhC8jriLQZIrli0oXKdVLF1QSFqxfEEBVLyI8NYXCgoKySaqhinakajimxrBRBX1FBQSVNRyDP4SXVGbYHRFfYJN8xhTESwyj5HHHEjEihoLCqDiXfAb3aksKESqCAoqEIxUUW9BAS03E+93mOhcZDYcXVF3QeHBPcI3v4qo4EPiUQcBKr75vHaiv6AAKt6NV0SCqgoKqOKYovpFZgOo+DmsOHkyUlA4ZKKamaIdQPEJK5oqKKCKM7D9zFZBIayiuYICWm5cFWef7o3vs486CP8VdQIEVRcU7sFE7VecgSmqvKDgVxEJqi8ogIof2xVnH2YLCuMT1fAU7RirOPtrXHCsovmCwlDFCgoKWNH4IrMBTdQ/NUzRjiu3CeCq9HAPAVSspaDgX9FkQcG3ollB34qGBf0UTQv6KBoXHFc0LzimWIFg0ywGBBelBxcHXLGKggKqWElBwV2xIkF3xaoEXYqVCe4rVifYV3wpPZwULOouKLzUXVBY1F1QeKm7oLCoXVAqVi7YNM7/F0YIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCG+/ANh4i1CHdc63QAAAABJRU5ErkJggg==" 
+        <div id="isExpander" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;">
+        <img  id="isExpander"  src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAZlBMVEX///8AAABmZmb7+/tYWFhgYGBFRUVSUlL4+Pg/Pz9jY2N5eXmcnJyioqKBgYFzc3NtbW1LS0s3NzfW1taWlpaOjo6IiIgvLy9WVlampqZcXFw5OTlvb28mJiYxMTHe3t7l5eUjIyMY8kIZAAAD2UlEQVR4nO2d61biMBRGW1FBEVHxfp15/5ecOVa5lHxtArmck/Xtn1BotjtNoXQtm4YQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEFIrX6UHEA1gsmrneceRjHm7cj28attKFOf/TRyKIliH4vzbZE+xE2zbZYkxRWX5Y9JT/BW0X3G+NtlR3Ahar7jcMtlS3Ba0XXG+Y7JW3BW0XHHZM/lR7AvaVewL/ijuC1pV3Bf8VnQJ2lR0CYriq/Nxg4puwfa1aZ7dz9yUHnEgN26NZ3luWkPFd7fEtHsWVDwpO+YgTgYKCuYn6tAU7TBecaygcGpZEQie7m5luKJPQQFUvCwx5iAuvQoK4KShvSIoOHVtCz7dnOUecxBn7kG/urc2eCz6T9EOcxXDCgpAUetyAwoOCBqrGF5QMKR4mCA8L+pTBIJwkRl95eifJjPHTDYTFQ8vePyrs3BsBfXLzfFHkvKKMY4j1ctNnCmmuGKslfCQT0RZiPdFVmnFmOcy36sDWYn7DU9hxdifRkKuEGQh/pWW0K/QiUlxtUxVxTTXyhQtN6kuI6mpmO5qpxJFIBjl1yMVimmvV4PfrnIq3iYsKICTRj7F9L84gIq38fYwCCj4HnMfRY/FPL8ZFayYo6BQbLlJeZrYpVDFXAUFcMtKWkUgmOhmnwKKOQsK4NaxdIp5CwqZj8X8gv27jNecJ9nZuXtnie/SzjhRQcHkt6Fnq1imoAAUY1csVVDIUrFcQSGDIhC8jriLQZIrli0oXKdVLF1QSFqxfEEBVLyI8NYXCgoKySaqhinakajimxrBRBX1FBQSVNRyDP4SXVGbYHRFfYJN8xhTESwyj5HHHEjEihoLCqDiXfAb3aksKESqCAoqEIxUUW9BAS03E+93mOhcZDYcXVF3QeHBPcI3v4qo4EPiUQcBKr75vHaiv6AAKt6NV0SCqgoKqOKYovpFZgOo+DmsOHkyUlA4ZKKamaIdQPEJK5oqKKCKM7D9zFZBIayiuYICWm5cFWef7o3vs486CP8VdQIEVRcU7sFE7VecgSmqvKDgVxEJqi8ogIof2xVnH2YLCuMT1fAU7RirOPtrXHCsovmCwlDFCgoKWNH4IrMBTdQ/NUzRjiu3CeCq9HAPAVSspaDgX9FkQcG3ollB34qGBf0UTQv6KBoXHFc0LzimWIFg0ywGBBelBxcHXLGKggKqWElBwV2xIkF3xaoEXYqVCe4rVifYV3wpPZwULOouKLzUXVBY1F1QeKm7oLCoXVAqVi7YNM7/F0YIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCG+/ANh4i1CHdc63QAAAABJRU5ErkJggg==" 
          width="15px" height="15px"/>
          </div>
         </div>`
     );
 
+    export function ImageOverlay(width: number, height: number, field: string = "thumbnail") {
+        return (`<div>
+                <div style="height:100%; width:100%;position:absolute;">{layout}</div>
+                <div style="width:${width}px; height:${height}px; top:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;">
+                    <ImageBox id="isExpander" {...props} PanelWidth={${width}} fieldKey={"${field}"} />
+                </div>
+            </div>`);
+    }
+
     export const TemplateList: Template[] = [Title, TitleOverlay, Caption, Bullet];
 
     export function sortTemplates(a: Template, b: Template) {
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 2b1f7bb37..645296d27 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { ContextMenu } from '../ContextMenu';
 import { FieldViewProps } from '../nodes/FieldView';
 import { Cast, FieldValue, PromiseValue, NumCast } from '../../../new_fields/Types';
-import { Doc, FieldResult, Opt } from '../../../new_fields/Doc';
+import { Doc, FieldResult, Opt, DocListCast } from '../../../new_fields/Doc';
 import { listSpec } from '../../../new_fields/Schema';
 import { List } from '../../../new_fields/List';
 import { Id } from '../../../new_fields/RefField';
@@ -63,13 +63,13 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         if (!(documentToAdd instanceof Doc)) {
             return false;
         }
-        let data = Cast(documentToAdd.data, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
-        for (const doc of data.filter(d => d instanceof Document)) {
+        let data = DocListCast(documentToAdd.data);
+        for (const doc of data) {
             if (this.createsCycle(doc, containerDocument)) {
                 return true;
             }
         }
-        let annots = Cast(documentToAdd.annotations, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+        let annots = DocListCast(documentToAdd.annotations);
         for (const annot of annots) {
             if (this.createsCycle(annot, containerDocument)) {
                 return true;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index ae949b2ed..506e60a65 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -19,7 +19,7 @@ import { DocumentView } from "../nodes/DocumentView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import "./CollectionSchemaView.scss";
 import { CollectionSubView } from "./CollectionSubView";
-import { Opt, Field, Doc, DocListCast } from "../../../new_fields/Doc";
+import { Opt, Field, Doc, DocListCastAsync, DocListCast } from "../../../new_fields/Doc";
 import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
 import { List } from "../../../new_fields/List";
@@ -118,7 +118,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                         }
                         const run = script.run;
                         //TODO This should be able to be refactored to compile the script once
-                        const val = await DocListCast(this.props.Document[this.props.fieldKey])
+                        const val = await DocListCastAsync(this.props.Document[this.props.fieldKey])
                         val && val.forEach(doc => applyToDoc(doc, run));
                     }}>
                 </EditableView>
@@ -276,7 +276,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     }
 
     get documentKeysCheckList() {
-        const docs = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+        const docs = DocListCast(this.props.Document[this.props.fieldKey]);
         let keys: { [key: string]: boolean } = {};
         // bcz: ugh.  this is untracked since otherwise a large collection of documents will blast the server for all their fields.
         //  then as each document's fields come back, we update the documents _proxies.  Each time we do this, the whole schema will be
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 0b08e150a..a86d250bd 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -10,7 +10,7 @@ import * as rp from 'request-promise';
 import { CollectionView } from "./CollectionView";
 import { CollectionPDFView } from "./CollectionPDFView";
 import { CollectionVideoView } from "./CollectionVideoView";
-import { Doc, Opt, FieldResult } from "../../../new_fields/Doc";
+import { Doc, Opt, FieldResult, DocListCast } from "../../../new_fields/Doc";
 import { DocComponent } from "../DocComponent";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, PromiseValue, FieldValue, ListSpec } from "../../../new_fields/Types";
@@ -49,7 +49,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         get children() {
             //TODO tfs: This might not be what we want?
             //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue)
-            return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(doc => FieldValue(doc)).map(doc => doc as Doc);
+            return DocListCast(this.props.Document[this.props.fieldKey]);
         }
 
         @action
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 33787f06b..78c84cc89 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -18,9 +18,7 @@ import { Main } from '../Main';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { CollectionDockingView } from './CollectionDockingView';
 import { DocumentManager } from '../../util/DocumentManager';
-import { Utils } from '../../../Utils';
 import { List } from '../../../new_fields/List';
-import { indexOf } from 'typescript-collections/dist/lib/arrays';
 
 
 export interface TreeViewProps {
@@ -155,16 +153,21 @@ class TreeView extends React.Component<TreeViewProps> {
         let keys = Array.from(Object.keys(this.props.document));
         if (this.props.document.proto instanceof Doc) {
             keys.push(...Array.from(Object.keys(this.props.document.proto)));
+            while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
         }
         keys.map(key => {
-            let docList = Cast(this.props.document[key], listSpec(Doc));
-            if (docList instanceof List && docList.length && docList[0] instanceof Doc) {
+            let docList = DocListCast(this.props.document[key]);
+            let doc = Cast(this.props.document[key], Doc);
+            if (doc instanceof Doc || docList.length) {
                 if (!this._collapsed) {
                     bulletType = BulletType.Collapsible;
+                    let spacing = (key === "data") ? 0 : -10;
                     contentElement.push(<ul key={key + "more"}>
                         {(key === "data") ? (null) :
-                            <span className="collectionTreeView-keyHeader" key={key}>{key}</span>}
-                        {TreeView.GetChildElements(docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction)}
+                            <span className="collectionTreeView-keyHeader" style={{ display: "block", marginTop: "7px" }} key={key}>{key}</span>}
+                        <div style={{ display: "block", marginTop: `${spacing}px` }}>
+                            {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction)}
+                        </div>
                     </ul >);
                 } else
                     bulletType = BulletType.Collapsed;
@@ -179,7 +182,7 @@ class TreeView extends React.Component<TreeViewProps> {
             </li>
         </div>;
     }
-    public static GetChildElements(docs: (Doc | Promise<Doc>)[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
+    public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
         return docs.filter(child => child instanceof Doc && !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).filter(doc => FieldValue(doc)).map(child =>
             <TreeView document={child as Doc} key={(child as Doc)[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
     }
@@ -203,12 +206,11 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         }
     }
     render() {
-        const children = this.children;
         let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType;
-        if (!children) {
+        if (!this.children) {
             return (null);
         }
-        let childElements = TreeView.GetChildElements(children, false, this.remove, this.props.moveDocument, dropAction);
+        let childElements = TreeView.GetChildElements(this.children, false, this.remove, this.props.moveDocument, dropAction);
 
         return (
             <div id="body" className="collectionTreeView-dropTarget"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 62b1456cf..d5ce4e1e7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -7,7 +7,7 @@ import { CollectionViewProps } from "../CollectionSubView";
 import "./CollectionFreeFormLinksView.scss";
 import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
 import React = require("react");
-import { Doc, DocListCast } from "../../../../new_fields/Doc";
+import { Doc, DocListCastAsync, DocListCast } from "../../../../new_fields/Doc";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
 import { listSpec } from "../../../../new_fields/Schema";
 import { List } from "../../../../new_fields/List";
@@ -20,11 +20,11 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
     componentDidMount() {
         this._brushReactionDisposer = reaction(
             () => {
-                let doclist = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []);
-                return { doclist: doclist ? doclist : [], xs: doclist instanceof List ? doclist.map(d => d instanceof Doc && d.x) : [] };
+                let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
+                return { doclist: doclist ? doclist : [], xs: doclist.map(d => d.x) };
             },
-            async () => {
-                let doclist = await DocListCast(this.props.Document[this.props.fieldKey]);
+            () => {
+                let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
                 let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : [];
                 views.forEach((dstDoc, i) => {
                     views.forEach((srcDoc, j) => {
@@ -84,7 +84,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
         }
         if (view.props.ContainingCollectionView) {
             let collid = view.props.ContainingCollectionView.props.Document[Id];
-            Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(d => d).map(d => d as Doc).
+            DocListCast(this.props.Document[this.props.fieldKey]).
                 filter(child =>
                     child[Id] === collid).map(view =>
                         DocumentManager.Instance.getDocumentViews(view).map(view =>
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 1bf39e335..9ace0272a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,3 +1,4 @@
+import * as htmlToImage from "html-to-image";
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Docs } from "../../../documents/Documents";
@@ -14,6 +15,8 @@ import { Doc } from "../../../../new_fields/Doc";
 import { NumCast, Cast } from "../../../../new_fields/Types";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
+import { ImageField } from "../../../../new_fields/URLField";
+import { Template, Templates } from "../../Templates";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -30,6 +33,7 @@ interface MarqueeViewProps {
 @observer
 export class MarqueeView extends React.Component<MarqueeViewProps>
 {
+    private _mainCont = React.createRef<HTMLDivElement>();
     @observable _lastX: number = 0;
     @observable _lastY: number = 0;
     @observable _downX: number = 0;
@@ -166,7 +170,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
 
     @undoBatch
     @action
-    marqueeCommand = (e: KeyboardEvent) => {
+    marqueeCommand = async (e: KeyboardEvent) => {
         if (this._commandExecuted) {
             return;
         }
@@ -224,13 +228,17 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 let scrpt = this.props.getTransform().inverse().transformPoint(bounds.left, bounds.top);
                 let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
 
+                let dataUrl = await htmlToImage.toPng(this._mainCont.current!, { width: bounds.width, height: bounds.height, quality: 1 });
+                summary.proto!.thumbnail = new ImageField(new URL(dataUrl));
+
+                summary.proto!.templates = new List<string>([Templates.ImageOverlay(Math.min(50, bounds.width), bounds.height * Math.min(50, bounds.width) / bounds.width, "thumbnail")]);
                 if (e.key === "s" || e.key === "p") {
                     summary.proto!.maximizeOnRight = true;
                     newCollection.proto!.summaryDoc = summary;
                     selected = [newCollection];
                 }
                 summary.proto!.summarizedDocs = new List<Doc>(selected);
-                summary.proto!.isButton = true;
+                //summary.proto!.isButton = true;
                 selected.map(summarizedDoc => {
                     let maxx = NumCast(summarizedDoc.x, undefined);
                     let maxy = NumCast(summarizedDoc.y, undefined);
@@ -313,17 +321,21 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
 
     @computed
     get marqueeDiv() {
-        let p = this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
         let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
-        return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} >
+        return <div className="marquee" style={{ width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} >
             <span className="marquee-legend" />
         </div>;
     }
 
     render() {
+        let p = this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
         return <div className="marqueeView" style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
-            {this.props.children}
-            {!this._visible ? (null) : this.marqueeDiv}
+            <div style={{ position: "absolute", transform: `translate(${p[0]}px, ${p[1]}px)` }} >
+                <div ref={this._mainCont} style={{ position: "absolute", transform: `translate(${-p[0]}px, ${-p[1]}px)` }} >
+                    {this.props.children}
+                </div>
+                {!this._visible ? null : this.marqueeDiv}
+            </div>
         </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index b05f2eea2..39d216da0 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -9,10 +9,10 @@ import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schem
 import { FieldValue, Cast, NumCast, BoolCast, StrCast } from "../../../new_fields/Types";
 import { OmitKeys, Utils } from "../../../Utils";
 import { SelectionManager } from "../../util/SelectionManager";
-import { Doc, DocListCast, HeightSym } from "../../../new_fields/Doc";
+import { Doc, DocListCastAsync, DocListCast, } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { UndoManager } from "../../util/UndoManager";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 }
@@ -65,7 +65,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
     panelWidth = () => this.props.PanelWidth();
     panelHeight = () => this.props.PanelHeight();
-    toggleMinimized = async () => this.toggleIcon(await DocListCast(this.props.Document.maximizedDocs));
+    toggleMinimized = async () => this.toggleIcon(await DocListCastAsync(this.props.Document.maximizedDocs));
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
         .scale(1 / this.contentScaling()).scale(1 / this.zoom)
@@ -132,7 +132,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         let minimizedDoc: Doc | undefined = this.props.Document;
         if (!maximizedDocs) {
             minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
-            if (minimizedDoc) maximizedDocs = await DocListCast(minimizedDoc.maximizedDocs);
+            if (minimizedDoc) maximizedDocs = await DocListCastAsync(minimizedDoc.maximizedDocs);
         }
         if (minimizedDoc && maximizedDocs) {
             let minimizedTarget = minimizedDoc;
@@ -176,16 +176,18 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         let altKey = e.altKey;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            let isBullet = (e.target as any).id === "isBullet";
-            let isIcon = StrCast(this.props.Document.layout).indexOf("IconBox") !== -1;
-            if (BoolCast(this.props.Document.isButton, false) || isBullet) {
-                let maximizedDocs = await DocListCast(isBullet ? this.props.Document.subBulletDocs : isIcon ? this.props.Document.maximizedDocs : this.props.Document.summarizedDocs);
-                if (maximizedDocs) {   // bcz: need a better way to associate behaviors with click events on widget-documents
+            let isExpander = (e.target as any).id === "isExpander";
+            if (BoolCast(this.props.Document.isButton, false) || isExpander) {
+                let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
+                let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
+                let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
+                let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : [])];
+                if (expandedDocs) {   // bcz: need a better way to associate behaviors with click events on widget-documents
                     if ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight)) {
-                        let dataDocs = await DocListCast(CollectionDockingView.Instance.props.Document.data);
+                        let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
                         if (dataDocs) {
                             SelectionManager.DeselectAll();
-                            maximizedDocs.forEach(maxDoc => {
+                            expandedDocs.forEach(maxDoc => {
                                 maxDoc.isMinimized = false;
                                 if (!dataDocs || dataDocs.indexOf(maxDoc) == -1) {
                                     CollectionDockingView.Instance.AddRightSplit(maxDoc);
@@ -195,8 +197,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                             });
                         }
                     } else {
-                        this.props.addDocument && maximizedDocs.forEach(async maxDoc => this.props.addDocument!(await maxDoc, false));
-                        this.toggleIcon(maximizedDocs);
+                        this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(await maxDoc, false));
+                        this.toggleIcon(expandedDocs);
                     }
                 }
             }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5aa74c703..90f67db7c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -12,13 +12,13 @@ import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
-import { Template, Templates } from "./../Templates";
+import { Template } from "./../Templates";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
-import { Opt, Doc, WidthSym, HeightSym, DocListCast } from "../../../new_fields/Doc";
+import { Opt, Doc, WidthSym, HeightSym, DocListCastAsync, DocListCast } from "../../../new_fields/Doc";
 import { DocComponent } from "../DocComponent";
-import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
+import { createSchema, makeInterface } from "../../../new_fields/Schema";
 import { FieldValue, StrCast, BoolCast, Cast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
@@ -109,8 +109,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         // bcz: kind of ugly .. setup a reaction to update the title of a summary document's target (maximizedDocs) whenver the summary doc's title changes
         this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
-            async () => {
-                let maxDoc = await DocListCast(this.props.Document.maximizedDocs);
+            () => {
+                let maxDoc = DocListCast(this.props.Document.maximizedDocs);
                 if (maxDoc && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
                     this.props.Document.title = (maxDoc && maxDoc.length === 1 ? maxDoc[0].title + ".icon" : "");
                 }
@@ -145,7 +145,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) {
         if (this._mainCont.current) {
-            let allConnected = dragSubBullets ? [this.props.Document, ...Cast(this.props.Document.subBulletDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc)] : [this.props.Document];
+            let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
             let dragData = new DragManager.DocumentDragData(allConnected);
             const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
@@ -169,17 +169,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             SelectionManager.SelectDoc(this, e.ctrlKey);
         }
     }
-    _hitIsBullet = false;
+    _hitExpander = false;
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
         this._downY = e.clientY;
         if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) {
             return;
         }
-        this._hitIsBullet = (e.target && (e.target as any).id === "isBullet") || Cast(this.props.Document.subBulletDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc).length > 0;
+        this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
         if (e.shiftKey && e.buttons === 1) {
             if (this.props.isTopMost) {
-                this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined, this._hitIsBullet);
+                this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined, this._hitExpander);
             } else if (this.props.Document) {
                 CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
             }
@@ -197,7 +197,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 document.removeEventListener("pointermove", this.onPointerMove);
                 document.removeEventListener("pointerup", this.onPointerUp);
                 if (!e.altKey && !this.topMost && (!CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 1) || (CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 2)) {
-                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitIsBullet);
+                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander);
                 }
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
@@ -232,6 +232,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         ContextMenu.Instance.clearItems();
         SelectionManager.DeselectAll();
     }
+
     @undoBatch
     @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
@@ -259,7 +260,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-
     @action
     addTemplate = (template: Template) => {
         this.templates.push(template.Layout);
@@ -307,7 +307,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-
     isSelected = () => SelectionManager.IsSelected(this);
     select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed);
 
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 4bcb4c636..48aecdcd0 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -2,12 +2,12 @@ import React = require("react");
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { computed, observable, runInAction, reaction, IReactionDisposer } from "mobx";
+import { computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./IconBox.scss";
 import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Doc } from "../../../new_fields/Doc";
 import { IconField } from "../../../new_fields/IconField";
 import { ContextMenu } from "../ContextMenu";
 import Measure from "react-measure";
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 0e9e904a8..f9659a4b2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,5 +1,5 @@
 
-import { action, observable } from 'mobx';
+import { action, observable, trace } from 'mobx';
 import { observer } from "mobx-react";
 import Lightbox from 'react-image-lightbox';
 import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
@@ -157,15 +157,21 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     }
 
     render() {
+        trace();
         let field = this.Document[this.props.fieldKey];
         let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"];
         if (field instanceof ImageField) paths = [field.url.href];
         else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => (p as ImageField).url.href);
-        let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
+        let nativeWidth = FieldValue(this.Document.nativeWidth, (this.props.PanelWidth as any) as string ? Number((this.props.PanelWidth as any) as string) : 50);
         let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
+        let id = this.props.id;
         return (
-            <div className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
-                <img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
+            <div id={id} className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+                <img id={id} src={paths[Math.min(paths.length, this._photoIndex)]}
+                    style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }}
+                    width={nativeWidth}
+                    ref={this._imgRef}
+                    onLoad={this.onLoad} />
                 {paths.length > 1 ? this.dots(paths) : (null)}
                 {this.lightbox(paths)}
             </div>);
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 24901913d..7bf13d5f9 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -5,7 +5,7 @@ import { LinkBox } from "./LinkBox";
 import { LinkEditor } from "./LinkEditor";
 import './LinkMenu.scss';
 import React = require("react");
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { Cast, FieldValue } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
 import { Id } from "../../../new_fields/RefField";
@@ -31,8 +31,8 @@ export class LinkMenu extends React.Component<Props> {
 
     render() {
         //get list of links from document
-        let linkFrom = Cast(this.props.docView.props.Document.linkedFromDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
-        let linkTo = Cast(this.props.docView.props.Document.linkedToDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+        let linkFrom = DocListCast(this.props.docView.props.Document.linkedFromDocs);
+        let linkTo = DocListCast(this.props.docView.props.Document.linkedToDoc);
         if (this._editingLink === undefined) {
             return (
                 <div id="linkMenu-container">
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 2ab145fa3..46ccb3e90 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -29,13 +29,17 @@ export const SelfProxy = Symbol("SelfProxy");
 export const WidthSym = Symbol("Width");
 export const HeightSym = Symbol("Height");
 
-export function DocListCast(field: FieldResult): Promise<Doc[] | undefined>;
-export function DocListCast(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>;
-export function DocListCast(field: FieldResult, defaultValue?: Doc[]) {
+export function DocListCastAsync(field: FieldResult): Promise<Doc[] | undefined>;
+export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>;
+export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
     const list = Cast(field, listSpec(Doc));
     return list ? Promise.all(list) : Promise.resolve(defaultValue);
 }
 
+export function DocListCast(field: FieldResult) {
+    return Cast(field, listSpec(Doc), []).filter(d => d && d instanceof Doc).map(d => d as Doc)
+}
+
 @Deserializable("doc").withFields(["id"])
 export class Doc extends RefField {
     constructor(id?: FieldId, forceSave?: boolean) {
-- 
cgit v1.2.3-70-g09d2


From 0e457c6f8268c70408d3740984f3976437535f51 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 12 May 2019 09:12:50 -0400
Subject: adjustments to treeview and ctrl-q pasting.

---
 src/client/views/DocumentDecorations.scss               |  2 +-
 src/client/views/EditableView.scss                      | 17 ++++++++++++++++-
 src/client/views/EditableView.tsx                       |  7 ++++---
 src/client/views/collections/CollectionTreeView.tsx     | 14 ++++++++++++--
 .../collections/collectionFreeForm/MarqueeView.tsx      |  8 ++------
 src/client/views/globalCssVariables.scss                |  6 ++++--
 6 files changed, 39 insertions(+), 15 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 6a2e33836..ba9f32d7d 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -65,7 +65,7 @@ $linkGap : 3px;
         cursor: ew-resize;
     }
     .title{
-        background: lightblue;
+        background: $alt-accent;
         grid-column-start: 3;
         grid-column-end: 4;
         pointer-events: auto;
diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
index ea401eaf9..dfa110f8d 100644
--- a/src/client/views/EditableView.scss
+++ b/src/client/views/EditableView.scss
@@ -1,5 +1,20 @@
-.editableView-container-editing {
+.editableView-container-editing, .editableView-container-editing-oneLine {
     overflow-wrap: break-word;
     word-wrap: break-word;
     hyphens: auto;
+    overflow: hidden;
+}
+.editableView-container-editing-oneLine {
+    span {
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        display:block;
+    }
+    input {
+        display:block;
+    }
+}
+.editableView-input {
+    width: 100%;
 }
\ No newline at end of file
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 73467eb9d..78143ccda 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -24,6 +24,7 @@ export interface EditableProps {
     contents: any;
     height: number;
     display?: string;
+    oneLine?: boolean;
 }
 
 /**
@@ -54,11 +55,11 @@ export class EditableView extends React.Component<EditableProps> {
 
     render() {
         if (this.editing) {
-            return <input defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
-                style={{ display: this.props.display }}></input>;
+            return <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
+                style={{ display: this.props.display }} />;
         } else {
             return (
-                <div className="editableView-container-editing" style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
+                <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`} style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
                     onClick={action(() => this.editing = true)} >
                     <span>{this.props.contents}</span>
                 </div>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 78c84cc89..9bd32a89f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -93,6 +93,15 @@ class TreeView extends React.Component<TreeViewProps> {
         return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>;
     }
 
+    @action
+    onMouseEnter = () => {
+        this._isOver = true;
+    }
+    @observable _isOver: boolean = false;
+    @action
+    onMouseLeave = () => {
+        this._isOver = false;
+    }
     /**
      * Renders the EditableView title element for placement into the tree.
      */
@@ -101,7 +110,8 @@ class TreeView extends React.Component<TreeViewProps> {
         let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.dropAction);
         let editableView = (titleString: string) =>
             (<EditableView
-                display={"inline"}
+                oneLine={!this._isOver ? true : false}
+                display={"block"}
                 contents={titleString}
                 height={36}
                 GetValue={() => StrCast(this.props.document.title)}
@@ -118,7 +128,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 <FontAwesomeIcon icon="angle-right" size="lg" />
             </div>);
         return (
-            <div className="docContainer" ref={reference} onPointerDown={onItemDown}
+            <div className="docContainer" ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
                 style={{ background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
                 onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
                 {editableView(StrCast(this.props.document.title))}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 9ace0272a..718a75770 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -59,13 +59,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         if (e.key === "q" && e.ctrlKey) {
             e.preventDefault();
             (async () => {
-                let text = await navigator.clipboard.readText();
-                let ns = text.split("\n").filter(t => t != "\r");
+                let text: string = await navigator.clipboard.readText();
+                let ns = text.split("\n").filter(t => t.trim() != "\r" && t.trim() != "");
                 for (let i = 0; i < ns.length - 1; i++) {
-                    if (ns[i].trim() === "") {
-                        ns.splice(i, 1);
-                        continue;
-                    }
                     while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") ||
                         ns[i].endsWith(";\r") || ns[i].endsWith(";") ||
                         ns[i].endsWith(".\r") || ns[i].endsWith(".") ||
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index cb4d1ad87..838d4d9ac 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -2,10 +2,12 @@
 // colors
 $light-color: #fcfbf7;
 $light-color-secondary:#f1efeb;
-$main-accent: #61aaa3;
+//$main-accent: #61aaa3;
+$main-accent: #aaaaa3;
 // $alt-accent: #cdd5ec;
 // $alt-accent: #cdeceb;
-$alt-accent: #59dff7;
+//$alt-accent: #59dff7;
+$alt-accent: #c2c2c5;
 $lighter-alt-accent: rgb(207, 220, 240);
 $intermediate-color: #9c9396;
 $dark-color: #121721;
-- 
cgit v1.2.3-70-g09d2


From b9d32e8e07137484c57f3d1ab89090b27d56cfe6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 12 May 2019 21:52:04 -0400
Subject: finally fixed disappearing videobox and changed marquee div structure

---
 src/client/views/collections/CollectionTreeView.tsx             | 5 +++++
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 8 ++++----
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx       | 2 +-
 3 files changed, 10 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 9bd32a89f..d22418b2c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -19,6 +19,7 @@ import { CurrentUserUtils } from '../../../server/authentication/models/current_
 import { CollectionDockingView } from './CollectionDockingView';
 import { DocumentManager } from '../../util/DocumentManager';
 import { List } from '../../../new_fields/List';
+import { Docs } from '../../documents/Documents';
 
 
 export interface TreeViewProps {
@@ -141,6 +142,10 @@ class TreeView extends React.Component<TreeViewProps> {
         if (!e.isPropagationStopped() && this.props.document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => Main.Instance.openWorkspace(this.props.document)) });
             ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.document) });
+            ContextMenu.Instance.addItem({
+                description: "Open Fields", event: () => CollectionDockingView.Instance.AddRightSplit(Docs.KVPDocument(this.props.document,
+                    { title: this.props.document.title + ".kvp", width: 300, height: 300 }))
+            });
             if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
                 ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
             }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 611ee7fd6..080c484f4 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -358,7 +358,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     @computed
     get marqueeDiv() {
         let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
-        return <div className="marquee" style={{ width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} >
+        return <div className="marquee" style={{ width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}`, zIndex: 2000 }} >
             <span className="marquee-legend" />
         </div>;
     }
@@ -366,11 +366,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     render() {
         let p = this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
         return <div className="marqueeView" style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
-            <div style={{ position: "absolute", transform: `translate(${p[0]}px, ${p[1]}px)` }} >
-                <div ref={this._mainCont} style={{ position: "absolute", transform: `translate(${-p[0]}px, ${-p[1]}px)` }} >
+            <div style={{ position: "relative", transform: `translate(${p[0]}px, ${p[1]}px)` }} >
+                {!this._visible ? null : this.marqueeDiv}
+                <div ref={this._mainCont} style={{ transform: `translate(${-p[0]}px, ${-p[1]}px)` }} >
                     {this.props.children}
                 </div>
-                {!this._visible ? null : this.marqueeDiv}
             </div>
         </div>;
     }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 39d216da0..817a23ce8 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -228,7 +228,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         const screenWidth = Math.min(50 * NumCast(this.props.Document.nativeWidth, 0), 1800);
         let fadeUp = .75 * screenWidth;
         let fadeDown = (maximizedDoc ? .0075 : .075) * screenWidth;
-        zoomFade = w < fadeDown  /* || w > fadeUp */ ? Math.max(0.1, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
+        zoomFade = w < fadeDown  /* || w > fadeUp */ ? Math.max(0.1, Math.min(1, 2 - (w < fadeDown ? Math.sqrt(Math.sqrt(fadeDown / w)) : w / fadeUp))) : 1;
 
         return (
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
-- 
cgit v1.2.3-70-g09d2


From dae85deb62168cbfae4557aa8632896592d71cf9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 15 May 2019 09:10:41 -0400
Subject: set hover highlight for search results

---
 src/client/views/SearchItem.tsx                           | 12 +++++++++++-
 src/client/views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx |  4 +++-
 src/new_fields/Doc.ts                                     |  2 +-
 4 files changed, 16 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
index 0da0bdae8..01c7316d6 100644
--- a/src/client/views/SearchItem.tsx
+++ b/src/client/views/SearchItem.tsx
@@ -40,6 +40,14 @@ export class SearchItem extends React.Component<SearchProps> {
                             faCaretUp;
         return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
     }
+    onPointerEnter = (e: React.PointerEvent) => {
+        this.props.doc.libraryBrush = true;
+        Doc.SetOnPrototype(this.props.doc, "protoBrush", true);
+    }
+    onPointerLeave = (e: React.PointerEvent) => {
+        this.props.doc.libraryBrush = false;
+        Doc.SetOnPrototype(this.props.doc, "protoBrush", false);
+    }
 
     collectionRef = React.createRef<HTMLDivElement>();
     startDocDrag = () => {
@@ -53,7 +61,9 @@ export class SearchItem extends React.Component<SearchProps> {
     }
     render() {
         return (
-            <div className="search-item" ref={this.collectionRef} id="result" onClick={this.onClick} onPointerDown={SetupDrag(this.collectionRef, this.startDocDrag)} >
+            <div className="search-item" ref={this.collectionRef} id="result"
+                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
+                onClick={this.onClick} onPointerDown={SetupDrag(this.collectionRef, this.startDocDrag)} >
                 <div className="search-title" id="result" >title: {this.props.doc.title}</div>
                 {/* <div className="search-type" id="result" >Type: {this.props.doc.layout}</div> */}
                 {/* <div className="search-type" >{SearchItem.DocumentIcon(this.layout)}</div> */}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d22418b2c..70c09d97c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -130,7 +130,7 @@ class TreeView extends React.Component<TreeViewProps> {
             </div>);
         return (
             <div className="docContainer" ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
-                style={{ background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
+                style={{ background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
                 onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
                 {editableView(StrCast(this.props.document.title))}
                 {openRight}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index fc1dc2b1e..925945b17 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -247,7 +247,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 style={{
                     outlineColor: "black",
                     outlineStyle: "dashed",
-                    outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ? `${0.5 / this.contentScaling()}px` : "0px",
+                    outlineWidth:
+                        BoolCast(this.props.Document.protoBrush, false) ? `${1 / this.contentScaling()}px` :
+                            BoolCast(this.props.Document.libraryBrush, false) ? `${0.5 / this.contentScaling()}px` : "0px",
                     opacity: zoomFade,
                     borderRadius: `${this.borderRounding()}px`,
                     transformOrigin: "left top",
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index d6043ef7a..2ae816da4 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -140,7 +140,7 @@ export namespace Doc {
         return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
     }
     export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
-        const proto = Object.getOwnPropertyNames(doc).indexOf("isProto") == -1 ? doc.proto : doc;
+        const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") == -1 ? doc.proto : doc;
 
         if (proto) {
             proto[key] = value;
-- 
cgit v1.2.3-70-g09d2


From b29a0d1cef60b55f609fcd94ad38232ae557e681 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Thu, 16 May 2019 18:32:57 -0400
Subject: Fixed linter errors

---
 src/client/goldenLayout.d.ts                       |  3 +++
 src/client/northstar/dash-fields/HistogramField.ts |  2 +-
 src/client/northstar/model/ModelHelpers.ts         |  9 +++----
 src/client/util/DragManager.ts                     |  2 +-
 src/client/util/RichTextSchema.tsx                 | 16 +++++------
 src/client/util/TooltipTextMenu.tsx                | 13 ++++-----
 src/client/views/DocumentDecorations.tsx           | 31 +++++++++++-----------
 src/client/views/PresentationView.tsx              |  4 +--
 src/client/views/TemplateMenu.tsx                  |  4 +--
 .../views/collections/CollectionDockingView.tsx    |  5 ++--
 .../views/collections/CollectionSchemaView.tsx     | 10 +++----
 .../views/collections/CollectionTreeView.tsx       |  9 ++++---
 .../collections/collectionFreeForm/MarqueeView.tsx | 13 ++++-----
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  8 +++---
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/client/views/nodes/KeyValuePair.tsx            |  3 ++-
 src/client/views/nodes/VideoBox.tsx                | 22 +++++++--------
 src/new_fields/CursorField.ts                      |  6 ++---
 src/new_fields/Doc.ts                              |  4 +--
 src/new_fields/ObjectField.ts                      |  2 +-
 .../authentication/models/current_user_utils.ts    |  2 +-
 24 files changed, 91 insertions(+), 85 deletions(-)
 create mode 100644 src/client/goldenLayout.d.ts

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

diff --git a/src/client/goldenLayout.d.ts b/src/client/goldenLayout.d.ts
new file mode 100644
index 000000000..b50240563
--- /dev/null
+++ b/src/client/goldenLayout.d.ts
@@ -0,0 +1,3 @@
+
+declare const GoldenLayout: any;
+export = GoldenLayout;
\ No newline at end of file
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index aabc77bb2..1ee2189b9 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -52,7 +52,7 @@ export class HistogramField extends ObjectField {
 
     [Copy]() {
         let y = this.HistoOp;
-        let z = this.HistoOp["Copy"];
+        let z = this.HistoOp.Copy;
         return new HistogramField(HistogramOperation.Duplicate(this.HistoOp));
     }
 }
\ No newline at end of file
diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts
index 80bb71224..88e6e72b8 100644
--- a/src/client/northstar/model/ModelHelpers.ts
+++ b/src/client/northstar/model/ModelHelpers.ts
@@ -32,12 +32,9 @@ export class ModelHelpers {
 
     public static GetAggregateParametersIndex(histogramResult: HistogramResult, aggParameters?: AggregateParameters): number {
         return Array.from(histogramResult.aggregateParameters!).findIndex((value, i, set) => {
-            if (set[i] instanceof CountAggregateParameters && value instanceof CountAggregateParameters)
-                return true;
-            if (set[i] instanceof MarginAggregateParameters && value instanceof MarginAggregateParameters)
-                return true;
-            if (set[i] instanceof SumAggregateParameters && value instanceof SumAggregateParameters)
-                return true;
+            if (set[i] instanceof CountAggregateParameters && value instanceof CountAggregateParameters) return true;
+            if (set[i] instanceof MarginAggregateParameters && value instanceof MarginAggregateParameters) return true;
+            if (set[i] instanceof SumAggregateParameters && value instanceof SumAggregateParameters) return true;
             return false;
         });
     }
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 26da34e67..05eb5c38a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -41,7 +41,7 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
 export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc) {
     let srcTarg = sourceDoc.proto;
     let draggedDocs: Doc[] = [];
-    let draggedFromDocs: Doc[] = []
+    let draggedFromDocs: Doc[] = [];
     if (srcTarg) {
         let linkToDocs = await DocListCastAsync(srcTarg.linkedToDocs);
         let linkFromDocs = await DocListCastAsync(srcTarg.linkedFromDocs);
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index c0e6f7899..3e3e98206 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -97,13 +97,13 @@ export const nodes: { [index: string]: NodeSpec } = {
                     title: dom.getAttribute("title"),
                     alt: dom.getAttribute("alt"),
                     width: Math.min(100, Number(dom.getAttribute("width"))),
-                }
+                };
             }
         }],
         // TODO if we don't define toDom, something weird happens: dragging the image will not move it but clone it. Why?
         toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}` }
-            return ["img", { ...node.attrs, ...attrs }]
+            const attrs = { style: `width: ${node.attrs.width}` };
+            return ["img", { ...node.attrs, ...attrs }];
         }
     },
 
@@ -375,7 +375,7 @@ export class ImageResizeView {
                 const currentX = e.pageX;
                 const diffInPx = currentX - startX;
                 self._outer.style.width = `${startWidth + diffInPx}`;
-            }
+            };
 
             const onpointerup = () => {
                 document.removeEventListener("pointermove", onpointermove);
@@ -384,11 +384,11 @@ export class ImageResizeView {
                     view.state.tr.setNodeMarkup(getPos(), null,
                         { src: node.attrs.src, width: self._outer.style.width })
                         .setSelection(view.state.selection));
-            }
+            };
 
-            document.addEventListener("pointermove", onpointermove)
-            document.addEventListener("pointerup", onpointerup)
-        }
+            document.addEventListener("pointermove", onpointermove);
+            document.addEventListener("pointerup", onpointerup);
+        };
 
         this._outer.appendChild(this._handle);
         this._outer.appendChild(this._img);
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 6eb654319..223921428 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -175,7 +175,7 @@ export class TooltipTextMenu {
             this.linkText.style.width = "150px";
             this.linkText.style.overflow = "hidden";
             this.linkText.style.color = "white";
-            this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }
+            this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); };
             let linkBtn = document.createElement("div");
             linkBtn.textContent = ">>";
             linkBtn.style.width = "20px";
@@ -192,8 +192,9 @@ export class TooltipTextMenu {
                         let docid = href.replace(DocServer.prepend("/doc/"), "");
                         DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
                             if (f instanceof Doc) {
-                                if (DocumentManager.Instance.getDocumentView(f))
+                                if (DocumentManager.Instance.getDocumentView(f)) {
                                     DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
+                                }
                                 else CollectionDockingView.Instance.AddRightSplit(f);
                             }
                         }));
@@ -201,7 +202,7 @@ export class TooltipTextMenu {
                     e.stopPropagation();
                     e.preventDefault();
                 }
-            }
+            };
             this.linkDrag = document.createElement("img");
             this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
             this.linkDrag.style.width = "20px";
@@ -216,12 +217,12 @@ export class TooltipTextMenu {
                     {
                         handlers: {
                             dragComplete: action(() => {
-                                let m = dragData.droppedDocuments as Doc[];
+                                let m = dragData.droppedDocuments;
                                 this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
                             }),
                         },
                         hideSource: false
-                    })
+                    });
             };
             this.linkEditor.appendChild(this.linkDrag);
             this.linkEditor.appendChild(this.linkText);
@@ -239,7 +240,7 @@ export class TooltipTextMenu {
                 e.stopPropagation();
                 e.preventDefault();
             }
-        }
+        };
         this.tooltip.appendChild(this.linkEditor);
     }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b2c65a31f..7083b1003 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -250,22 +250,21 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 selectedDocs[0].props.removeDocument && selectedDocs[0].props.removeDocument(this._iconDoc);
             }
             if (!this._removeIcon) {
-                if (selectedDocs.length === 1)
+                if (selectedDocs.length === 1) {
                     this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
-                else
-                    if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD &&
-                        Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
-                        let docViews = SelectionManager.ViewsSortedVertically();
-                        let topDocView = docViews[0];
-                        let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
-                        if (ind !== -1) {
-                            topDocView.templates.splice(ind, 1);
-                            topDocView.props.Document.subBulletDocs = undefined;
-                        } else {
-                            topDocView.addTemplate(Templates.Bullet);
-                            topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
-                        }
+                } else if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD &&
+                    Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
+                    let docViews = SelectionManager.ViewsSortedVertically();
+                    let topDocView = docViews[0];
+                    let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
+                    if (ind !== -1) {
+                        topDocView.templates.splice(ind, 1);
+                        topDocView.props.Document.subBulletDocs = undefined;
+                    } else {
+                        topDocView.addTemplate(Templates.Bullet);
+                        topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
                     }
+                }
             }
             this._removeIcon = false;
         }
@@ -537,9 +536,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                         if (temp !== Templates.Bullet.Layout || i === 0) {
                             res.push(temp);
                         }
-                    })
+                    });
                 }
-                return res
+                return res;
             }, [] as string[]);
             let checked = false;
             docTemps.forEach(temp => {
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index 098e725c7..7d0dc2913 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -1,7 +1,7 @@
 import { observer } from "mobx-react";
-import React = require("react")
+import React = require("react");
 import { observable, action, runInAction, reaction } from "mobx";
-import "./PresentationView.scss"
+import "./PresentationView.scss";
 import "./Main.tsx";
 import { DocumentManager } from "../util/DocumentManager";
 import { Utils } from "../../Utils";
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index cfe1b0663..e5b679e24 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -43,7 +43,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
         if (event.target.checked) {
-            if (template.Name == "Bullet") {
+            if (template.Name === "Bullet") {
                 let topDocView = this.props.docs[0];
                 topDocView.addTemplate(template);
                 topDocView.props.Document.subBulletDocs = new List<Doc>(this.props.docs.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
@@ -52,7 +52,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             }
             this.props.templates.set(template, true);
         } else {
-            if (template.Name == "Bullet") {
+            if (template.Name === "Bullet") {
                 let topDocView = this.props.docs[0];
                 topDocView.removeTemplate(template);
                 topDocView.props.Document.subBulletDocs = undefined;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 58f1e33a1..deec64225 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -83,7 +83,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     child.contentItems[0].remove();
                     this.layoutChanged(document);
                     return true;
-                } else
+                } else {
                     Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
                         if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {
                             child.contentItems[j].remove();
@@ -94,8 +94,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         }
                         return false;
                     });
+                }
                 return false;
-            })
+            });
         }
         if (retVal) {
             this.stateChanged();
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index f4ad5b357..497dfde88 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -90,7 +90,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
         let fieldContentView = <FieldView {...props} />;
         let reference = React.createRef<HTMLDivElement>();
         let onItemDown = (e: React.PointerEvent) =>
-            (this.props.CollectionView!.props.isSelected() ?
+            (this.props.CollectionView.props.isSelected() ?
                 SetupDrag(reference, () => props.Document, this.props.moveDocument)(e) : undefined);
         let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
             const res = run({ this: doc });
@@ -127,7 +127,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                         }
                         const run = script.run;
                         //TODO This should be able to be refactored to compile the script once
-                        const val = await DocListCastAsync(this.props.Document[this.props.fieldKey])
+                        const val = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
                         val && val.forEach(doc => applyToDoc(doc, run));
                     }}>
                 </EditableView>
@@ -230,14 +230,14 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
         DocListCast(this.props.Document.data).map(doc => {
             csv += self.columns.reduce((val, col) => val + (doc[col] ? doc[col]!.toString() : "") + ",", "");
             csv = csv.substr(0, csv.length - 1) + "\n";
-        })
+        });
         csv.substring(0, csv.length - 1);
         let dbName = StrCast(this.props.Document.title);
         let res = await Gateway.Instance.PostSchema(csv, dbName);
         if (self.props.CollectionView.props.addDocument) {
             let schemaDoc = await Docs.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName });
             if (schemaDoc) {
-                self.props.CollectionView.props.addDocument(schemaDoc, false); 
+                self.props.CollectionView.props.addDocument(schemaDoc, false);
             }
         }
     }
@@ -263,7 +263,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     get previewDocument(): Doc | undefined {
         const children = DocListCast(this.props.Document[this.props.fieldKey]);
         const selected = children.length > this._selectedIndex ? FieldValue(children[this._selectedIndex]) : undefined;
-        return selected ? (this.previewScript && this.previewScript != "this" ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
+        return selected ? (this.previewScript && this.previewScript !== "this" ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
     }
     get tableWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * (1 - this.splitPercentage / 100); }
     get previewRegionHeight() { return this.props.PanelHeight() - 2 * this.borderWidth; }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 70c09d97c..6acef434e 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -56,7 +56,7 @@ class TreeView extends React.Component<TreeViewProps> {
         } else {
             CollectionDockingView.Instance.AddRightSplit(this.props.document);
         }
-    };
+    }
 
     get children() {
         return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed?    .filter(doc => FieldValue(doc));
@@ -184,8 +184,9 @@ class TreeView extends React.Component<TreeViewProps> {
                             {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction)}
                         </div>
                     </ul >);
-                } else
+                } else {
                     bulletType = BulletType.Collapsed;
+                }
             }
         });
         return <div className="treeViewItem-container"
@@ -198,8 +199,8 @@ class TreeView extends React.Component<TreeViewProps> {
         </div>;
     }
     public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
-        return docs.filter(child => child instanceof Doc && !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).filter(doc => FieldValue(doc)).map(child =>
-            <TreeView document={child as Doc} key={(child as Doc)[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
+        return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).map(child =>
+            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
     }
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 12edb2c2a..c3c4115b8 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -63,7 +63,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             e.preventDefault();
             (async () => {
                 let text: string = await navigator.clipboard.readText();
-                let ns = text.split("\n").filter(t => t.trim() != "\r" && t.trim() != "");
+                let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
                 for (let i = 0; i < ns.length - 1; i++) {
                     while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") ||
                         ns[i].endsWith(";\r") || ns[i].endsWith(";") ||
@@ -80,7 +80,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     let newBox = Docs.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line });
                     this.props.addDocument(newBox, false);
                     y += 40 * this.props.getTransform().Scale;
-                })
+                });
             })();
         } else if (e.key === "b" && e.ctrlKey) {
             //heuristically converts pasted text into a table.
@@ -93,9 +93,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             e.preventDefault();
             (async () => {
                 let text: string = await navigator.clipboard.readText();
-                let ns = text.split("\n").filter(t => t.trim() != "\r" && t.trim() != "");
-                while (ns.length > 0 && ns[0].split("\t").length < 2)
+                let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
+                while (ns.length > 0 && ns[0].split("\t").length < 2) {
                     ns.splice(0, 1);
+                }
                 if (ns.length > 0) {
                     let columns = ns[0].split("\t");
                     let docList: Doc[] = [];
@@ -109,7 +110,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                         let doc = new Doc();
                         columns.forEach((col, i) => doc[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined));
                         if (groupAttr) {
-                            doc["_group"] = groupAttr;
+                            doc._group = groupAttr;
                         }
                         doc.title = i.toString();
                         docList.push(doc);
@@ -284,7 +285,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     //     summarizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0]);
                     // });
                     this.props.addLiveTextDocument(summary);
-                })
+                });
             }
             else {
                 this.props.addDocument(newCollection, false);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7c7ca9e25..d4f660b3f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -149,7 +149,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                         let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny);
                         maximizedDoc.willMaximize = isMinimized;
                         maximizedDoc.isMinimized = false;
-                        maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0])
+                        maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
                     }
                 }
             });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cf8bcbb42..428dd9b36 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -244,7 +244,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
             const protoDest = destDoc.proto;
             const protoSrc = sourceDoc.proto;
-            if (de.mods == "Control") {
+            if (de.mods === "Control") {
                 let src = protoSrc ? protoSrc : sourceDoc;
                 let dst = protoDest ? protoDest : destDoc;
                 dst.data = src;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f24d4ae88..f30022508 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -184,7 +184,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
                 dispatchTransaction: this.dispatchTransaction,
                 nodeViews: {
-                    image(node, view, getPos) { return new ImageResizeView(node, view, getPos) }
+                    image(node, view, getPos) { return new ImageResizeView(node, view, getPos); }
                 }
             });
             let text = StrCast(this.props.Document.documentText);
@@ -232,9 +232,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
                     DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
                         if (f instanceof Doc) {
-                            if (DocumentManager.Instance.getDocumentView(f))
+                            if (DocumentManager.Instance.getDocumentView(f)) {
                                 DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
-                            else CollectionDockingView.Instance.AddRightSplit(f);
+                            } else {
+                                CollectionDockingView.Instance.AddRightSplit(f);
+                            }
                         }
                     }));
                 }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 6472ae711..3cc60a6c5 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -165,7 +165,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => (p as ImageField).url.href);
         let nativeWidth = FieldValue(this.Document.nativeWidth, (this.props.PanelWidth as any) as string ? Number((this.props.PanelWidth as any) as string) : 50);
         let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
-        let id = this.props.id; // bcz: used to set id = "isExpander" in templates.tsx
+        let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx
         return (
             <div id={id} className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
                 <img id={id} src={paths[Math.min(paths.length, this._photoIndex)]}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index c9d665ceb..86437a6c1 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -18,7 +18,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     @observable private _keyInput: string = "";
     @observable private _valueInput: string = "";
     @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
-    get fieldDocToLayout() { return this.props.fieldKey ? FieldValue(Cast(this.props.Document[this.props.fieldKey], Doc)) : this.props.Document }
+    get fieldDocToLayout() { return this.props.fieldKey ? FieldValue(Cast(this.props.Document[this.props.fieldKey], Doc)) : this.props.Document; }
 
     constructor(props: FieldViewProps) {
         super(props);
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 5de660d57..4f7919f50 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -46,8 +46,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                 <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
                     <div className="keyValuePair-td-key-container">
                         <button className="keyValuePair-td-key-delete" onClick={() => {
-                            if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1)
+                            if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
                                 props.Document[props.fieldKey] = undefined;
+                            }
                             else props.Document.proto![props.fieldKey] = undefined;
                         }}>
                             X
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 96dc884c8..81c429a02 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -70,7 +70,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
     }
 
     @action
-    updateTimecode = () => this.player && (this.props.Document.curPage = this.player.currentTime);
+    updateTimecode = () => this.player && (this.props.Document.curPage = this.player.currentTime)
 
     componentDidMount() {
         if (this.props.setVideoBox) this.props.setVideoBox(this);
@@ -106,31 +106,31 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
                     "user-agent": 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:43.0) Gecko/20100101 Firefox/46.0',
                 },
 
-            }
+            };
             try {
                 let responseSchema: any = {};
-                const videoInfoResponse = await rp.get(DocServer.prepend(RouteStore.corsProxy + "/" + `https://www.youtube.com/watch?v=${videoId}`), videoInfoRequestConfig)
+                const videoInfoResponse = await rp.get(DocServer.prepend(RouteStore.corsProxy + "/" + `https://www.youtube.com/watch?v=${videoId}`), videoInfoRequestConfig);
                 const dataHtml = videoInfoResponse;
                 const start = dataHtml.indexOf('ytplayer.config = ') + 18;
                 const end = dataHtml.indexOf(';ytplayer.load');
-                const subString = dataHtml.substring(start, end)
+                const subString = dataHtml.substring(start, end);
                 const subJson = JSON.parse(subString);
                 const stringSub = subJson.args.player_response;
                 const stringSubJson = JSON.parse(stringSub);
                 const adaptiveFormats = stringSubJson.streamingData.adaptiveFormats;
-                const videoDetails = stringSubJson.videoDetails
-                responseSchema["adaptiveFormats"] = adaptiveFormats;
-                responseSchema["videoDetails"] = videoDetails;
-                resolve(responseSchema)
+                const videoDetails = stringSubJson.videoDetails;
+                responseSchema.adaptiveFormats = adaptiveFormats;
+                responseSchema.videoDetails = videoDetails;
+                resolve(responseSchema);
             }
             catch (err) {
                 console.log(`
                 --- Youtube ---
                 Function: getMp4ForVideo
-                Error: `, err)
-                reject(err)
+                Error: `, err);
+                reject(err);
             }
-        })
+        });
     }
     onPointerDown = (e: React.PointerEvent) => {
         e.preventDefault();
diff --git a/src/new_fields/CursorField.ts b/src/new_fields/CursorField.ts
index 7fd326a5f..fc144222c 100644
--- a/src/new_fields/CursorField.ts
+++ b/src/new_fields/CursorField.ts
@@ -6,17 +6,17 @@ import { serializable, createSimpleSchema, object } from "serializr";
 export type CursorPosition = {
     x: number,
     y: number
-}
+};
 
 export type CursorMetadata = {
     id: string,
     identifier: string
-}
+};
 
 export type CursorData = {
     metadata: CursorMetadata,
     position: CursorPosition
-}
+};
 
 const PositionSchema = createSimpleSchema({
     x: true,
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index de72be3ea..c898f697b 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -42,8 +42,8 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
     return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
 }
 
-export function DocListCast(field: FieldResult) {
-    return Cast(field, listSpec(Doc), []).filter(d => d && d instanceof Doc).map(d => d as Doc);
+export function DocListCast(field: FieldResult): Doc[] {
+    return Cast(field, listSpec(Doc), []).filter(d => d && d instanceof Doc) as Doc[];
 }
 
 @Deserializable("doc").withFields(["id"])
diff --git a/src/new_fields/ObjectField.ts b/src/new_fields/ObjectField.ts
index f276bfa67..51768c6db 100644
--- a/src/new_fields/ObjectField.ts
+++ b/src/new_fields/ObjectField.ts
@@ -6,7 +6,7 @@ export const Parent = Symbol("Parent");
 export const Copy = Symbol("Copy");
 
 export abstract class ObjectField {
-    protected [OnUpdate](diff?: any) { };
+    protected [OnUpdate](diff?: any) { }
     private [Parent]?: RefField | ObjectField;
     abstract [Copy](): ObjectField;
 }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 5b63ac356..aef2d3f4a 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -66,7 +66,7 @@ export class CurrentUserUtils {
             NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
             await Gateway.Instance.ClearCatalog();
             const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
-            let extras = await Promise.all(extraSchemas.map(async sc => await Gateway.Instance.GetSchema("", sc)));
+            let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc)));
             let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras);
             if (catprom) await Promise.all(catprom);
         } catch (e) {
-- 
cgit v1.2.3-70-g09d2

