From a37faf30652c6c593923198ad34e54377e670caf Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 5 Jun 2019 08:09:10 -0400
Subject: fixed full screen text bug.  cleaned up imports

---
 src/client/views/collections/CollectionDockingView.tsx | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index dcc1bd95d..8f6c9b1fc 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,28 +1,28 @@
 import 'golden-layout/src/css/goldenlayout-base.css';
 import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, observable, reaction, Lambda } from "mobx";
+import { action, Lambda, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
 import Measure from "react-measure";
 import * as GoldenLayout from "../../../client/goldenLayout";
-import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";
+import { Id } from '../../../new_fields/FieldSymbols';
 import { FieldId } from "../../../new_fields/RefField";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { emptyFunction, returnTrue, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
+import { DocumentManager } from '../../util/DocumentManager';
 import { DragLinksAsDocuments, DragManager } from "../../util/DragManager";
+import { SelectionManager } from '../../util/SelectionManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocumentView } from "../nodes/DocumentView";
+import { CollectionViewType } from './CollectionBaseView';
 import "./CollectionDockingView.scss";
 import { SubCollectionViewProps } from "./CollectionSubView";
-import React = require("react");
 import { ParentDocSelector } from './ParentDocumentSelector';
-import { DocumentManager } from '../../util/DocumentManager';
-import { CollectionViewType } from './CollectionBaseView';
-import { Id } from '../../../new_fields/FieldSymbols';
-import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import React = require("react");
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -357,6 +357,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 if (doc instanceof Doc) {
                     let theDoc = doc;
                     CollectionDockingView.Instance._removedDocs.push(theDoc);
+                    SelectionManager.DeselectAll();
                 }
                 tab.contentItem.remove();
             });
-- 
cgit v1.2.3-70-g09d2


From a30cbfd90f3b5207fc790a1c8dc61e58f69f4e38 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samuel_wilkins@brown.edu>
Date: Wed, 5 Jun 2019 22:13:34 -0400
Subject: tab focus shifting on tab drag over and beginnings of global key
 handling, including splitting with control + rightarrow

---
 src/client/views/MainView.tsx                      | 36 +++++++++++++++++++---
 .../views/collections/CollectionDockingView.tsx    | 20 ++++++++++++
 2 files changed, 52 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 408d454f4..67a026897 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -26,13 +26,14 @@ import { PreviewCursor } from './PreviewCursor';
 import { SearchBox } from './SearchBox';
 import { SelectionManager } from '../util/SelectionManager';
 import { FieldResult, Field, Doc, Opt, DocListCast } from '../../new_fields/Doc';
-import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
+import { Cast, FieldValue, StrCast, PromiseValue } from '../../new_fields/Types';
 import { DocServer } from '../DocServer';
 import { listSpec } from '../../new_fields/Schema';
 import { Id } from '../../new_fields/FieldSymbols';
 import { HistoryUtil } from '../util/History';
 import { CollectionBaseView } from './collections/CollectionBaseView';
-
+import { timingSafeEqual } from 'crypto';
+import * as _ from "lodash";
 
 @observer
 export class MainView extends React.Component {
@@ -43,6 +44,14 @@ export class MainView extends React.Component {
     @computed private get mainContainer(): Opt<Doc> {
         return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc));
     }
+    @computed private get mainFreeform(): Opt<Doc> {
+        let docs = DocListCast(this.mainContainer!.data);
+        return (docs && docs.length > 1) ? docs[1] : undefined;
+    }
+    private globalDisplayFlags = observable({
+        jumpToVisible: false
+    });
+
     private set mainContainer(doc: Opt<Doc>) {
         if (doc) {
             if (!("presentationView" in doc)) {
@@ -308,6 +317,7 @@ export class MainView extends React.Component {
         this.isSearchVisible = !this.isSearchVisible;
     }
 
+    @action
     globalKeyHandler = (e: KeyboardEvent) => {
         if (e.key === "Control" || !e.ctrlKey) return;
 
@@ -316,11 +326,28 @@ export class MainView extends React.Component {
 
         switch (e.key) {
             case "ArrowRight":
-                CollectionDockingView.Instance.AddRightSplit(this.mainContainer!);
-                console.log("split screen right");
+                if (this.mainFreeform) {
+                    CollectionDockingView.Instance.AddRightSplit(this.mainFreeform!);
+                }
+                break;
+            case "ArrowLeft":
+                if (this.mainFreeform) {
+                    CollectionDockingView.Instance.CloseRightSplit(this.mainFreeform!);
+                }
+                break;
+            case "o":
+                this.globalDisplayFlags.jumpToVisible = true;
+                break;
+            case "escape":
+                _.mapValues(this.globalDisplayFlags, () => false)
+                break;
         }
     }
 
+    renderJumpTo = () => {
+        return <div>JUMP TO</div>;
+    }
+
     render() {
         return (
             <div id="main-div">
@@ -332,6 +359,7 @@ export class MainView extends React.Component {
                 {this.miscButtons}
                 <InkingControl />
                 <MainOverlayTextBox />
+                {this.globalDisplayFlags.jumpToVisible ? this.renderJumpTo() : (null)}
             </div>
         );
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index dcc1bd95d..4b7868e81 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -44,6 +44,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     private _containerRef = React.createRef<HTMLDivElement>();
     private _flush: boolean = false;
     private _ignoreStateChange = "";
+    private _isPointerDown = false;
 
     constructor(props: SubCollectionViewProps) {
         super(props);
@@ -247,6 +248,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     @action
     onPointerUp = (e: React.PointerEvent): void => {
+        this._isPointerDown = false;
         if (this._flush) {
             this._flush = false;
             setTimeout(() => this.stateChanged(), 10);
@@ -254,6 +256,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
     @action
     onPointerDown = (e: React.PointerEvent): void => {
+        this._isPointerDown = true;
         var className = (e.target as any).className;
         if (className === "messageCounter") {
             e.stopPropagation();
@@ -334,6 +337,23 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     tab.element.append(counter);
                     let upDiv = document.createElement("span");
                     const stack = tab.contentItem.parent;
+                    // console.log("TAB: ", tab);
+                    tab.element[0].onmouseenter = (e: any) => {
+                        if (!this._isPointerDown) return;
+                        var activeContentItem = tab.header.parent.getActiveContentItem();
+                        if (tab.contentItem !== activeContentItem) {
+                            tab.header.parent.setActiveContentItem(tab.contentItem);
+                        }
+                        tab.setActive(true);
+                    }
+                    // tab.element[0].ondragenter = (e: any) => {
+                    //     console.log("DRAGGING OVER DETECTED!");
+                    //     console.log(e);
+                    // }
+                    // tab.element[0].ondrag = (e: any) => {
+                    //     console.log("DRAGGING!");
+                    //     console.log(e);
+                    // }
                     ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv);
                     tab.reactComponents = [upDiv];
                     tab.element.append(upDiv);
-- 
cgit v1.2.3-70-g09d2


From 79b37db46fda36cd779645256b03d9d074141eb6 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samuel_wilkins@brown.edu>
Date: Wed, 5 Jun 2019 22:13:49 -0400
Subject: cleanup and documentation

---
 src/client/views/collections/CollectionDockingView.tsx | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4b7868e81..1adb73bcf 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -337,7 +337,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     tab.element.append(counter);
                     let upDiv = document.createElement("span");
                     const stack = tab.contentItem.parent;
-                    // console.log("TAB: ", tab);
+                    // shifts the focus to this tab when another tab is dragged over it
                     tab.element[0].onmouseenter = (e: any) => {
                         if (!this._isPointerDown) return;
                         var activeContentItem = tab.header.parent.getActiveContentItem();
@@ -346,14 +346,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         }
                         tab.setActive(true);
                     }
-                    // tab.element[0].ondragenter = (e: any) => {
-                    //     console.log("DRAGGING OVER DETECTED!");
-                    //     console.log(e);
-                    // }
-                    // tab.element[0].ondrag = (e: any) => {
-                    //     console.log("DRAGGING!");
-                    //     console.log(e);
-                    // }
                     ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv);
                     tab.reactComponents = [upDiv];
                     tab.element.append(upDiv);
-- 
cgit v1.2.3-70-g09d2


From e1b7feda380f540e677e69e306d91d6b57ce03e7 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 7 Jun 2019 19:42:43 -0400
Subject: tree view reordering.

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/DragManager.ts                     | 16 +++--
 .../views/collections/CollectionDockingView.tsx    |  5 +-
 .../views/collections/CollectionStackingView.tsx   |  1 -
 src/client/views/collections/CollectionSubView.tsx | 24 ++-----
 .../views/collections/CollectionTreeView.scss      | 79 ++++++++++++----------
 src/new_fields/Doc.ts                              |  6 +-
 .../authentication/models/current_user_utils.ts    |  1 +
 8 files changed, 70 insertions(+), 64 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ab61b915c..5f2d729cf 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -30,7 +30,7 @@ import { Cast, NumCast } from "../../new_fields/Types";
 import { IconField } from "../../new_fields/IconField";
 import { listSpec } from "../../new_fields/Schema";
 import { DocServer } from "../DocServer";
-import { StrokeData, InkField } from "../../new_fields/InkField";
+import { InkField } from "../../new_fields/InkField";
 import { dropActionType } from "../util/DragManager";
 import { DateField } from "../../new_fields/DateField";
 import { UndoManager } from "../util/UndoManager";
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 1e84a0db0..32476b785 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -6,7 +6,7 @@ import { CollectionDockingView } from "../views/collections/CollectionDockingVie
 import * as globalCssVariables from "../views/globalCssVariables.scss";
 
 export type dropActionType = "alias" | "copy" | undefined;
-export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc>, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType) {
+export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc>, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, options?: any, dontHideOnDrop?: boolean) {
     let onRowMove = async (e: PointerEvent) => {
         e.stopPropagation();
         e.preventDefault();
@@ -16,6 +16,8 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
         var dragData = new DragManager.DocumentDragData([await docFunc()]);
         dragData.dropAction = dropAction;
         dragData.moveDocument = moveFunc;
+        dragData.options = options;
+        dragData.dontHideOnDrop = dontHideOnDrop;
         DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
     };
     let onRowUp = (): void => {
@@ -185,6 +187,7 @@ export namespace DragManager {
     export let AbortDrag: () => void = emptyFunction;
 
     function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
+        eles = eles.filter(e => e);
         if (!dragDiv) {
             dragDiv = document.createElement("div");
             dragDiv.className = "dragManager-dragDiv";
@@ -241,6 +244,13 @@ export namespace DragManager {
             //         pdfBox.replaceChild(img, pdfBox.children[0])
             //     }
             // }
+            let set = dragElement.getElementsByTagName('*');
+            for (let i = 0; i < set.length; i++)
+                if (set[i].hasAttribute("style")) {
+                    let s = set[i];
+                    (s as any).style.pointerEvents = "none";
+                }
+
 
             dragDiv.appendChild(dragElement);
             return dragElement;
@@ -259,8 +269,6 @@ export namespace DragManager {
         let lastX = downX;
         let lastY = downY;
         const moveHandler = (e: PointerEvent) => {
-            e.stopPropagation();
-            e.preventDefault();
             if (dragData instanceof DocumentDragData) {
                 dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
             }
@@ -309,7 +317,7 @@ 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 removed = dragData.dontHideOnDrop ? [] : dragEles.map(dragEle => {
             // let parent = dragEle.parentElement;
             // if (parent) parent.removeChild(dragEle);
             let ret = [dragEle, dragEle.style.width, dragEle.style.height];
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 8f6c9b1fc..51e29cb54 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -23,6 +23,7 @@ import "./CollectionDockingView.scss";
 import { SubCollectionViewProps } from "./CollectionSubView";
 import { ParentDocSelector } from './ParentDocumentSelector';
 import React = require("react");
+import { MainView } from '../MainView';
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -457,7 +458,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
 
     addDocTab = (doc: Doc, location: string) => {
-        if (location === "onRight") {
+        if (doc.dockingConfig) {
+            MainView.Instance.openWorkspace(doc);
+        } else if (location === "onRight") {
             CollectionDockingView.Instance.AddRightSplit(doc);
         } else {
             CollectionDockingView.Instance.AddTab(this._stack, doc);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index b8eb4ac84..0a119a3b4 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -109,7 +109,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     @computed
     get children() {
-        trace();
         return this.childDocs.filter(d => !d.isMinimized).map((d, i) => {
             let dref = React.createRef<HTMLDivElement>();
             let dxf = () => this.getDocTransform(d, dref.current!);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 06d77fec5..762955a08 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -18,6 +18,7 @@ import { CollectionVideoView } from "./CollectionVideoView";
 import { CollectionView } from "./CollectionView";
 import React = require("react");
 import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { Id } from "../../../new_fields/FieldSymbols";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -81,28 +82,15 @@ 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.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.dropAction || de.data.userDropAction) {
-                    added = de.data.droppedDocuments.reduce((added: boolean, d) => {
-                        let moved = this.props.addDocument(d);
-                        return moved || added;
-                    }, false);
+                    added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 } else if (de.data.moveDocument) {
-                    const move = de.data.moveDocument;
-                    added = de.data.droppedDocuments.reduce((added: boolean, d) => {
-                        let moved = move(d, this.props.Document, this.props.addDocument);
-                        return moved || added;
-                    }, false);
+                    let movedDocs = de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments;
+                    added = movedDocs.reduce((added: boolean, d) =>
+                        de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false);
                 } else {
-                    added = de.data.droppedDocuments.reduce((added: boolean, d) => {
-                        let moved = this.props.addDocument(d);
-                        return moved || added;
-                    }, false);
+                    added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 }
                 e.stopPropagation();
                 return added;
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 458030b28..2dc4b2e80 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -19,10 +19,6 @@
         padding-left: 20px;
     }
 
-    li {
-        margin: 5px 0;
-    }
-
 
     .no-indent {
         padding-left: 0;
@@ -34,30 +30,9 @@
         width: 15px;
         display: block;
         color: $intermediate-color;
-        margin-top: 3px;
+        margin-top: 8px;
         transform: scale(1.3, 1.3);
     }
-
-    .docContainer {
-        margin-left: 10px;
-        display: block;
-        // width:100%;//width: max-content;
-    }
-
-    .docContainer:hover {
-        .treeViewItem-openRight {
-            display: inline-block;
-            height:13px;
-            // display: inline;
-            svg {
-                display:block;
-                padding:0px;
-                margin: 0px;
-            }
-        }
-    }
-
-
     .editableView-container {
         font-weight: bold;
     }
@@ -70,10 +45,6 @@
         display: inline;
     }
 
-    .treeViewItem-openRight {
-        margin-left: 5px;
-        display: none;
-    }
 
     .docContainer:hover {
         .delete-button {
@@ -88,13 +59,51 @@
         font-size: 24px;
     }
 
-    .collection-child {
-        margin-top: 10px;
-        margin-bottom: 10px;
-    }
-
     .collectionTreeView-keyHeader {
         font-style: italic;
         font-size: 8pt;
     }
+}
+
+.docContainer {
+    margin-left: 10px;
+    display: block;
+    // width:100%;//width: max-content;
+
+    .treeViewItem-openRight {
+        margin-left: 5px;
+        display: none;
+    }
+}
+#docContainer-data {
+    margin-top: 5px;
+}
+
+.docContainer:hover {
+    .treeViewItem-openRight {
+        display: inline-block;
+        height:13px;
+        // display: inline;
+        svg {
+            display:block;
+            padding:0px;
+            margin: 0px;
+        }
+    }
+}
+
+.treeViewItem-header {
+    border: transparent 1px solid;
+}
+.treeViewItem-header-above {
+    border: transparent 1px solid;
+    border-top: black 1px solid;
+}
+.treeViewItem-header-below {
+    border: transparent 1px solid;
+    border-bottom: black 1px solid;
+}
+.treeViewItem-header-inside {
+    border: transparent 1px solid;
+    border: black 1px solid;
 }
\ No newline at end of file
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 7f7263cf1..7e02a5bc5 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -211,13 +211,11 @@ export namespace Doc {
         return Array.from(results);
     }
 
-
     export function MakeAlias(doc: Doc) {
-        const alias = new Doc;
         if (!GetT(doc, "isPrototype", "boolean", true)) {
-            alias.proto = doc.proto;
+            return Doc.MakeCopy(doc);
         }
-        return alias;
+        return new Doc;
     }
 
     export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc {
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index e5b7a025b..816c5f269 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -29,6 +29,7 @@ export class CurrentUserUtils {
     private static createUserDocument(id: string): Doc {
         let doc = new Doc(id, true);
         doc.viewType = CollectionViewType.Tree;
+        doc.dropAction = "alias";
         doc.layout = CollectionView.LayoutString();
         doc.title = this.email;
         doc.data = new List<Doc>();
-- 
cgit v1.2.3-70-g09d2


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

---
 src/client/documents/Documents.ts                  |  18 +++
 src/client/util/DocumentManager.ts                 |   4 +-
 src/client/util/DragManager.ts                     |   8 +-
 src/client/util/TooltipTextMenu.tsx                |   2 +-
 src/client/views/MainOverlayTextBox.tsx            |   4 +-
 src/client/views/MainView.tsx                      |  20 ++-
 .../views/collections/CollectionBaseView.tsx       |   2 +-
 .../views/collections/CollectionDockingView.tsx    | 162 ++++++++++++++-------
 .../views/collections/CollectionTreeView.tsx       |   2 +-
 .../views/collections/ParentDocumentSelector.tsx   |   4 +-
 src/client/views/nodes/DocumentView.tsx            |  12 +-
 11 files changed, 158 insertions(+), 80 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ab61b915c..b346e1570 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -35,6 +35,7 @@ import { dropActionType } from "../util/DragManager";
 import { DateField } from "../../new_fields/DateField";
 import { UndoManager } from "../util/UndoManager";
 import { RouteStore } from "../../server/RouteStore";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 var requestImageSize = require('request-image-size');
 var path = require('path');
 
@@ -315,6 +316,23 @@ export namespace Docs {
     export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
         return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
     }
+    export type DocConfig = {
+        doc: Doc,
+        initialWidth?: number
+    }
+    export function StandardCollectionDockingDocument(configs: Array<DocConfig>, options: DocumentOptions, id?: string, type: string = "row") {
+        let layoutConfig = {
+            content: [
+                {
+                    type: type,
+                    content: [
+                        ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth))
+                    ]
+                }
+            ]
+        };
+        return DockDocument(configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id);
+    }
 
     export function CaptionDocument(doc: Doc) {
         const captionDoc = Doc.MakeAlias(doc);
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 65c4b9e4b..72d9f7e76 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -134,7 +134,7 @@ export class DocumentManager {
                 const actualDoc = Doc.MakeAlias(docDelegate);
                 actualDoc.libraryBrush = true;
                 if (linkPage !== undefined) actualDoc.curPage = linkPage;
-                (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc);
+                (dockFunc || CollectionDockingView.AddRightSplit)(actualDoc);
             } else {
                 let contextView: DocumentView | null;
                 docDelegate.libraryBrush = true;
@@ -142,7 +142,7 @@ export class DocumentManager {
                     contextDoc.panTransformType = "Ease";
                     contextView.props.focus(contextDoc);
                 } else {
-                    (dockFunc || CollectionDockingView.Instance.AddRightSplit)(contextDoc);
+                    (dockFunc || CollectionDockingView.AddRightSplit)(contextDoc);
                 }
             }
         }
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 1e84a0db0..7625b0463 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -26,8 +26,8 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
         // if (this.props.isSelected() || this.props.isTopMost) {
         if (e.button === 0) {
             e.stopPropagation();
-            if (e.shiftKey && CollectionDockingView.Instance) {
-                CollectionDockingView.Instance.StartOtherDrag([await docFunc()], e);
+            if (e.shiftKey && CollectionDockingView.TopLevel) {
+                CollectionDockingView.TopLevel.StartOtherDrag([await docFunc()], e);
             } else {
                 document.addEventListener("pointermove", onRowMove);
                 document.addEventListener("pointerup", onRowUp);
@@ -264,9 +264,9 @@ export namespace DragManager {
             if (dragData instanceof DocumentDragData) {
                 dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
             }
-            if (e.shiftKey && CollectionDockingView.Instance) {
+            if (e.shiftKey && CollectionDockingView.TopLevel) {
                 AbortDrag();
-                CollectionDockingView.Instance.StartOtherDrag(docs, {
+                CollectionDockingView.TopLevel.StartOtherDrag(docs, {
                     pageX: e.pageX,
                     pageY: e.pageY,
                     preventDefault: emptyFunction,
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index f517f757a..fa2483db5 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -194,7 +194,7 @@ export class TooltipTextMenu {
                                 if (DocumentManager.Instance.getDocumentView(f)) {
                                     DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
                                 }
-                                else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f);
+                                else if (CollectionDockingView.TopLevel) CollectionDockingView.AddRightSplit(f);
                             }
                         }));
                     }
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 24327b995..718979123 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -86,7 +86,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
 
     addDocTab = (doc: Doc, location: string) => {
         if (true) { // location === "onRight") { need to figure out stack to add "inTab"
-            CollectionDockingView.Instance.AddRightSplit(doc);
+            CollectionDockingView.AddRightSplit(doc);
         }
     }
     render() {
@@ -102,6 +102,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                 </div>
             </ div>;
         }
-        else return (null); Z
+        else return (null);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 67a026897..426e2440a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
 import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt, faBell } from '@fortawesome/free-solid-svg-icons';
+import { faClone, faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt, faBell } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
@@ -106,6 +106,7 @@ export class MainView extends React.Component {
         library.add(faFilm);
         library.add(faMusic);
         library.add(faTree);
+        library.add(faClone)
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
@@ -151,8 +152,11 @@ export class MainView extends React.Component {
         const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
         if (list) {
             let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` });
-            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}` }, id);
+            let configs = [
+                { doc: CurrentUserUtils.UserDocument, initialWidth: 150 },
+                { doc: freeformDoc, initialWidth: 600 }
+            ]
+            let mainDoc = Docs.StandardCollectionDockingDocument(configs, { title: `Workspace ${list.length + 1}` }, id);
             list.push(mainDoc);
             // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
             setTimeout(() => {
@@ -183,8 +187,8 @@ export class MainView extends React.Component {
     }
 
     openNotifsCol = () => {
-        if (this._notifsCol && CollectionDockingView.Instance) {
-            CollectionDockingView.Instance.AddRightSplit(this._notifsCol);
+        if (this._notifsCol && CollectionDockingView.TopLevel) {
+            CollectionDockingView.AddRightSplit(this._notifsCol);
         }
     }
 
@@ -240,6 +244,7 @@ export class MainView extends React.Component {
 
         let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
         let addColNode = action(() => Docs.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
+        let addDockingNode = action(() => Docs.StandardCollectionDockingDocument([{ doc: addColNode(), initialWidth: 200 }], { width: 200, height: 200, title: "a nested docking freeform collection" }));
         let addSchemaNode = action(() => Docs.SchemaDocument(["title"], [], { width: 200, height: 200, title: "a schema collection" }));
         let addTreeNode = action(() => CurrentUserUtils.UserDocument);
         //let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" }));
@@ -260,6 +265,7 @@ export class MainView 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],
+            [React.createRef<HTMLDivElement>(), "clone", "Add Docking Frame", addDockingNode]
         ];
 
         return < div id="add-nodes-menu" >
@@ -327,12 +333,12 @@ export class MainView extends React.Component {
         switch (e.key) {
             case "ArrowRight":
                 if (this.mainFreeform) {
-                    CollectionDockingView.Instance.AddRightSplit(this.mainFreeform!);
+                    CollectionDockingView.AddRightSplit(this.mainFreeform!);
                 }
                 break;
             case "ArrowLeft":
                 if (this.mainFreeform) {
-                    CollectionDockingView.Instance.CloseRightSplit(this.mainFreeform!);
+                    CollectionDockingView.CloseRightSplit(this.mainFreeform!);
                 }
                 break;
             case "o":
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 4d6721dc1..096c65092 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -16,7 +16,7 @@ export enum CollectionViewType {
     Schema,
     Docking,
     Tree,
-    Stacking
+    Stacking,
 }
 
 export interface CollectionRenderProps {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1adb73bcf..dfb8fac35 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,9 +1,9 @@
 import 'golden-layout/src/css/goldenlayout-base.css';
 import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, observable, reaction, Lambda } from "mobx";
+import { action, observable, reaction, Lambda, IReactionDisposer } from "mobx";
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
-import Measure from "react-measure";
+import Measure, { ContentRect } from "react-measure";
 import * as GoldenLayout from "../../../client/goldenLayout";
 import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc";
 import { FieldId } from "../../../new_fields/RefField";
@@ -26,7 +26,7 @@ import { CurrentUserUtils } from '../../../server/authentication/models/current_
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
-    public static Instance: CollectionDockingView;
+    public static TopLevel: CollectionDockingView;
     public static makeDocumentConfig(document: Doc, width?: number) {
         return {
             type: 'react-component',
@@ -35,11 +35,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             width: width,
             props: {
                 documentId: document[Id],
-                //collectionDockingView: CollectionDockingView.Instance
             }
         };
     }
 
+    private makeDocConfig = (document: Doc, width?: number) => {
+        const config = CollectionDockingView.makeDocumentConfig(document, width);
+        (config.props as any).parent = this;
+        return config;
+    }
+
     private _goldenLayout: any = null;
     private _containerRef = React.createRef<HTMLDivElement>();
     private _flush: boolean = false;
@@ -48,7 +53,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     constructor(props: SubCollectionViewProps) {
         super(props);
-        CollectionDockingView.Instance = this;
+        CollectionDockingView.TopLevel = this;
         (window as any).React = React;
         (window as any).ReactDOM = ReactDOM;
     }
@@ -58,41 +63,69 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         this.hack = true;
         this.undohack = UndoManager.StartBatch("goldenDrag");
         dragDocs.map(dragDoc =>
-            this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.
+            CollectionDockingView.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.
                 onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
     }
 
-    @action
-    public OpenFullScreen(document: Doc) {
+    private openFullScreen = (document: Doc) => {
         let newItemStackConfig = {
             type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(document)]
+            content: [this.makeDocConfig(document)]
         };
         var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
         this._goldenLayout.root.contentItems[0].addChild(docconfig);
         docconfig.callDownwards('_$init');
         this._goldenLayout._$maximiseItem(docconfig);
-        this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
+        this._ignoreStateChange = this.retrieveConfiguration();
         this.stateChanged();
     }
 
+    @action
+    public static OpenFullScreen(document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) {
+        dockingView.openFullScreen(document);
+    }
+
+    initializeConfiguration = (configText: string) => {
+        let configuration: any = JSON.parse(configText);
+        this.injectParentProp(configuration.content);
+        this._goldenLayout = new GoldenLayout(configuration);
+    }
+
+    retrieveConfiguration = () => {
+        let configuration: any = this._goldenLayout.toConfig();
+        this.injectParentProp(configuration.content, true);
+        return JSON.stringify(configuration);
+    }
+
+    injectParentProp = (contentArray: any[], reverse: boolean = false) => {
+        if (!contentArray || contentArray.length == 0) return;
+        contentArray.forEach(member => {
+            let baseCase = Object.keys(member).includes("props");
+            if (!baseCase) {
+                this.injectParentProp(member.content, reverse)
+            } else {
+                reverse ? delete member.props.parent : member.props.parent = this;
+            }
+        });
+    }
+
     @undoBatch
     @action
-    public CloseRightSplit = (document: Doc): boolean => {
+    public static CloseRightSplit = (document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel): boolean => {
         let retVal = false;
-        if (this._goldenLayout.root.contentItems[0].isRow) {
-            retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
+        if (dockingView._goldenLayout.root.contentItems[0].isRow) {
+            retVal = Array.from(dockingView._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
                 if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
                     Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) {
                     child.contentItems[0].remove();
-                    this.layoutChanged(document);
+                    dockingView.layoutChanged(document);
                     return true;
                 } else {
                     Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
                         if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {
                             child.contentItems[j].remove();
                             child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
-                            let docs = Cast(this.props.Document.data, listSpec(Doc));
+                            let docs = Cast(dockingView.props.Document.data, listSpec(Doc));
                             docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1);
                             return true;
                         }
@@ -103,7 +136,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             });
         }
         if (retVal) {
-            this.stateChanged();
+            dockingView.stateChanged();
         }
         return retVal;
     }
@@ -112,23 +145,26 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     layoutChanged(removed?: Doc) {
         this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
         this._goldenLayout.emit('stateChanged');
-        this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
-        if (removed) CollectionDockingView.Instance._removedDocs.push(removed);
+        this._ignoreStateChange = this.retrieveConfiguration();
+        if (removed) CollectionDockingView.TopLevel._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
     //
-    @action
-    public AddRightSplit = (document: Doc, minimize: boolean = false) => {
+    public static AddRightSplit = (document: Doc, minimize: boolean = false, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) => {
+        return dockingView.addRightSplit(document, minimize);
+    }
+
+    private addRightSplit(document: Doc, minimize = false) {
         let docs = Cast(this.props.Document.data, listSpec(Doc));
         if (docs) {
             docs.push(document);
         }
         let newItemStackConfig = {
             type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(document)]
+            content: [this.makeDocConfig(document)]
         };
 
         var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
@@ -157,13 +193,17 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
         return newContentItem;
     }
-    @action
-    public AddTab = (stack: any, document: Doc) => {
+
+    public static AddTab = (stack: any, document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) => {
+        dockingView.addTab(stack, document);
+    }
+
+    private addTab = (stack: any, document: Doc) => {
         let docs = Cast(this.props.Document.data, listSpec(Doc));
         if (docs) {
             docs.push(document);
         }
-        let docContentConfig = CollectionDockingView.makeDocumentConfig(document);
+        let docContentConfig = this.makeDocConfig(document);
         var newContentItem = stack.layoutManager.createContentItem(docContentConfig, this._goldenLayout);
         stack.addChild(newContentItem.contentItems[0], undefined);
         this.layoutChanged();
@@ -173,10 +213,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         var config = StrCast(this.props.Document.dockingConfig);
         if (config) {
             if (!this._goldenLayout) {
-                this._goldenLayout = new GoldenLayout(JSON.parse(config));
+                this.initializeConfiguration(config);
             }
             else {
-                if (config === JSON.stringify(this._goldenLayout.toConfig())) {
+                if (config === this.retrieveConfiguration()) {
                     return;
                 }
                 try {
@@ -186,7 +226,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     this._goldenLayout.unbind('stackCreated', this.stackCreated);
                 } catch (e) { }
                 this._goldenLayout.destroy();
-                this._goldenLayout = new GoldenLayout(JSON.parse(config));
+                this.initializeConfiguration(config);
             }
             this._goldenLayout.on('itemDropped', this.itemDropped);
             this._goldenLayout.on('tabCreated', this.tabCreated);
@@ -204,13 +244,13 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             this._goldenLayout.init();
         }
     }
-    reactionDisposer?: Lambda;
+    reactionDisposer?: IReactionDisposer;
     componentDidMount: () => void = () => {
         if (this._containerRef.current) {
             this.reactionDisposer = reaction(
                 () => StrCast(this.props.Document.dockingConfig),
                 () => {
-                    if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
+                    if (!this._goldenLayout || this._ignoreStateChange !== this.retrieveConfiguration()) {
                         // Because this is in a set timeout, if this component unmounts right after mounting,
                         // we will leak a GoldenLayout, because we try to destroy it before we ever create it
                         setTimeout(() => this.setupGoldenLayout(), 1);
@@ -218,7 +258,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     this._ignoreStateChange = "";
                 }, { fireImmediately: true });
 
-            window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
+            // window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
         }
     }
     componentWillUnmount: () => void = () => {
@@ -232,18 +272,19 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         }
         if (this._goldenLayout) this._goldenLayout.destroy();
         this._goldenLayout = null;
-        window.removeEventListener('resize', this.onResize);
+        // window.removeEventListener('resize', this.onResize);
 
         if (this.reactionDisposer) {
             this.reactionDisposer();
         }
     }
     @action
-    onResize = (event: any) => {
-        var cur = this._containerRef.current;
-
+    onResize = (size: ContentRect) => {
         // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
-        this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
+        // this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
+        if (this._goldenLayout) {
+            this._goldenLayout.updateSize(size.offset!.width, size.offset!.height);
+        }
     }
 
     @action
@@ -301,12 +342,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     @undoBatch
     stateChanged = () => {
-        let docs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc));
-        CollectionDockingView.Instance._removedDocs.map(theDoc =>
+        let docs = Cast(CollectionDockingView.TopLevel.props.Document.data, listSpec(Doc));
+        CollectionDockingView.TopLevel._removedDocs.map(theDoc =>
             docs && docs.indexOf(theDoc) !== -1 &&
             docs.splice(docs.indexOf(theDoc), 1));
-        CollectionDockingView.Instance._removedDocs.length = 0;
-        var json = JSON.stringify(this._goldenLayout.toConfig());
+        CollectionDockingView.TopLevel._removedDocs.length = 0;
+        var json = this.retrieveConfiguration();
         this.props.Document.dockingConfig = json;
         if (this.undohack && !this.hack) {
             this.undohack.end();
@@ -315,18 +356,18 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         this.hack = false;
     }
 
-    itemDropped = () => {
+    private itemDropped = () => {
         this.stateChanged();
     }
 
-    htmlToElement(html: string) {
+    private htmlToElement(html: string) {
         var template = document.createElement('template');
         html = html.trim(); // Never return a text node of whitespace as the result
         template.innerHTML = html;
         return template.content.firstChild;
     }
 
-    tabCreated = async (tab: any) => {
+    private tabCreated = async (tab: any) => {
         if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
             if (tab.contentItem.config.fixed) {
                 tab.contentItem.parent.config.fixed = true;
@@ -346,7 +387,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         }
                         tab.setActive(true);
                     }
-                    ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv);
+                    ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.AddTab(stack, doc)} />, upDiv);
                     tab.reactComponents = [upDiv];
                     tab.element.append(upDiv);
                     counter.DashDocId = tab.contentItem.config.props.documentId;
@@ -368,13 +409,13 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId);
                 if (doc instanceof Doc) {
                     let theDoc = doc;
-                    CollectionDockingView.Instance._removedDocs.push(theDoc);
+                    CollectionDockingView.TopLevel._removedDocs.push(theDoc);
                 }
                 tab.contentItem.remove();
             });
     }
 
-    tabDestroyed = (tab: any) => {
+    private tabDestroyed = (tab: any) => {
         if (tab.reactComponents) {
             for (const ele of tab.reactComponents) {
                 ReactDOM.unmountComponentAtNode(ele);
@@ -383,7 +424,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
     _removedDocs: Doc[] = [];
 
-    stackCreated = (stack: any) => {
+    private stackCreated = (stack: any) => {
         //stack.header.controlsContainer.find('.lm_popout').hide();
         stack.header.controlsContainer.find('.lm_close') //get the close icon
             .off('click') //unbind the current click handler
@@ -394,7 +435,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     let doc = await DocServer.GetRefField(contentItem.config.props.documentId);
                     if (doc instanceof Doc) {
                         let theDoc = doc;
-                        CollectionDockingView.Instance._removedDocs.push(theDoc);
+                        CollectionDockingView.TopLevel._removedDocs.push(theDoc);
                     }
                 });
                 //}
@@ -410,8 +451,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     render() {
         return (
-            <div className="collectiondockingview-container" id="menuContainer"
-                onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
+            <Measure onResize={this.onResize} offset>
+                {({ measureRef }) => (
+                    <div ref={measureRef} style={{ width: "100%", height: "100%" }}>
+                        <div className="collectiondockingview-container" id="menuContainer"
+                            onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
+                    </div>
+                )}
+            </Measure>
         );
     }
 
@@ -419,16 +466,23 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
 interface DockedFrameProps {
     documentId: FieldId;
-    //collectionDockingView: CollectionDockingView
+    glContainer: any;
+    glEventHub: any;
+    parent: CollectionDockingView;
 }
+
 @observer
 export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     _mainCont = React.createRef<HTMLDivElement>();
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
+    private get parentProps(): SubCollectionViewProps {
+        return this.props.parent.props;
+    }
+
     get _stack(): any {
-        let parent = (this.props as any).glContainer.parent.parent;
+        let parent = this.props.glContainer.parent.parent;
         if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1)
             return parent.parent.contentItems[1];
         return parent;
@@ -451,7 +505,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         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(1 / this.contentScaling() / scale);
+            return this.parentProps.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
         }
         return Transform.Identity();
     }
@@ -469,9 +523,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
 
     addDocTab = (doc: Doc, location: string) => {
         if (location === "onRight") {
-            CollectionDockingView.Instance.AddRightSplit(doc);
+            CollectionDockingView.AddRightSplit(doc);
         } else {
-            CollectionDockingView.Instance.AddTab(this._stack, doc);
+            CollectionDockingView.AddTab(this._stack, doc);
         }
     }
     get content() {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 48da52ffa..b0a310ec1 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -123,7 +123,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     return true;
                 }}
             />);
-        let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []) : [];
+        let dataDocs = CollectionDockingView.TopLevel ? Cast(CollectionDockingView.TopLevel.props.Document.data, listSpec(Doc), []) : [];
         let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : (
             <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
                 <FontAwesomeIcon icon="angle-right" size="lg" />
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index f11af04a3..95183bd2c 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -29,8 +29,8 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
         allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
         docs.forEach(doc => map.delete(doc));
         runInAction(() => {
-            this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.Document }));
-            this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
+            this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.TopLevel.props.Document)).map(doc => ({ col: doc, target: this.props.Document }));
+            this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.TopLevel.props.Document)).map(([col, target]) => ({ col, target }));
         });
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7750b9173..efba26c2c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -238,11 +238,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                             expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized);
                         }
                     }
-                    if (maxLocation && maxLocation !== "inPlace" && CollectionDockingView.Instance) {
-                        let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
+                    if (maxLocation && maxLocation !== "inPlace" && CollectionDockingView.TopLevel) {
+                        let dataDocs = DocListCast(CollectionDockingView.TopLevel.props.Document.data);
                         if (dataDocs) {
                             expandedDocs.forEach(maxDoc =>
-                                (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) &&
+                                (!CollectionDockingView.CloseRightSplit(Doc.GetProto(maxDoc)) &&
                                     this.props.addDocTab(getDispDoc(maxDoc), maxLocation)));
                         }
                     } else {
@@ -270,8 +270,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
-        if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) {
-            CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
+        if (e.shiftKey && e.buttons === 1 && CollectionDockingView.TopLevel) {
+            CollectionDockingView.TopLevel.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
             e.stopPropagation();
         } else {
             if (this.active) e.stopPropagation(); // events stop at the lowest document that is active.  
@@ -316,7 +316,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
     fullScreenClicked = (): void => {
-        CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false));
+        CollectionDockingView.TopLevel && CollectionDockingView.OpenFullScreen(Doc.MakeCopy(this.props.Document, false));
         SelectionManager.DeselectAll();
     }
 
-- 
cgit v1.2.3-70-g09d2


From 15e8341334419142f4a54db23cc643f18ba0e0f1 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 14 Jun 2019 15:53:13 -0400
Subject: a few tweaks to fix unfreezing documents to give them a margin

---
 src/client/views/collections/CollectionDockingView.tsx | 8 ++++++--
 src/client/views/nodes/DocumentView.tsx                | 3 ++-
 src/client/views/nodes/PDFBox.tsx                      | 5 ++++-
 3 files changed, 12 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 51e29cb54..235bf5ae4 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -9,7 +9,7 @@ import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";
 import { Id } from '../../../new_fields/FieldSymbols';
 import { FieldId } from "../../../new_fields/RefField";
 import { listSpec } from "../../../new_fields/Schema";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
 import { emptyFunction, returnTrue, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { DocumentManager } from '../../util/DocumentManager';
@@ -429,7 +429,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
 
     nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
-    nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight);
+    nativeHeight = () => {
+        let nh = NumCast(this._document!.nativeHeight, this._panelHeight);
+        let res = BoolCast(this._document!.ignoreAspect) ? this._panelHeight : nh;
+        return res;
+    }
     contentScaling = () => {
         const nativeH = this.nativeHeight();
         const nativeW = this.nativeWidth();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6fe01963a..583fa3e1a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -452,8 +452,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     render() {
         var scaling = this.props.ContentScaling();
-        var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
         var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
+        let ph = this.props.PanelHeight();
+        var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
         return (
             <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index cce7b2631..899caa68a 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -33,9 +33,12 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
 
     loaded = (nw: number, nh: number) => {
         if (this.props.Document) {
+            if (this.props.Document.nativeWidth && this.props.Document.nativeHeight) return;
             let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+            let oldaspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1);
             doc.nativeWidth = nw;
-            doc.nativeHeight = nh;
+            if (doc.nativeHeight) doc.nativeHeight = nw * oldaspect;
+            else doc.nativeHeight = nh;
             let ccv = this.props.ContainingCollectionView;
             if (ccv) {
                 ccv.props.Document.pdfHeight = nh;
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From b4bd43f6e79c9dec30842262f270ca6122f1184a Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Sat, 15 Jun 2019 17:17:45 -0400
Subject: cleaned up some leftover linkedfromdocs/linkedtodocs

---
 src/client/documents/Documents.ts                  | 31 ++---------------
 src/client/util/LinkManager.ts                     |  3 +-
 src/client/views/DocumentDecorations.tsx           |  3 --
 .../views/collections/CollectionDockingView.tsx    |  5 +--
 src/client/views/nodes/DocumentView.tsx            | 40 +++++++++-------------
 5 files changed, 24 insertions(+), 58 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9cb41aecc..268dbc927 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -87,21 +87,11 @@ const delegateKeys = ["x", "y", "width", "height", "panX", "panY"];
 export namespace DocUtils {
     export function MakeLink(source: Doc, target: Doc) {
         if (LinkManager.Instance.doesLinkExist(source, target)) return;
-        // let protoSrc = source.proto ? source.proto : source;
-        // let protoTarg = target.proto ? target.proto : target;
-        UndoManager.RunInBatch(() => {
-            // let groupDoc1 = Docs.TextDocument();
-            // groupDoc1.proto!.type = "*";
-            // groupDoc1.proto!.metadata = Docs.TextDocument();
 
-            // let groupDoc2 = Docs.TextDocument();
-            // groupDoc2.proto!.type = "*";
-            // groupDoc2.proto!.metadata = Docs.TextDocument();
+        UndoManager.RunInBatch(() => {
 
             let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 });
-            //let linkDoc = new Doc;
-            // linkDoc.proto!.title = source.proto!.title + " and " + target.proto!.title;
-            // linkDoc.proto!.linkDescription = "";
+
             linkDoc.proto!.anchor1 = source;
             linkDoc.proto!.anchor1Page = source.curPage;
             linkDoc.proto!.anchor1Groups = new List<Doc>([]);
@@ -110,23 +100,6 @@ export namespace DocUtils {
             linkDoc.proto!.anchor2Page = target.curPage;
             linkDoc.proto!.anchor2Groups = new List<Doc>([]);
 
-            // linkDoc.proto!.linkedTo = target;
-            // linkDoc.proto!.linkedToPage = target.curPage;
-            // linkDoc.proto!.linkedFrom = source;
-            // linkDoc.proto!.linkedFromPage = source.curPage;
-
-            // let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc));
-            // if (!linkedFrom) {
-            //     protoTarg.linkedFromDocs = linkedFrom = new List<Doc>();
-            // }
-            // linkedFrom.push(linkDoc);
-
-            // let linkedTo = Cast(protoSrc.linkedToDocs, listSpec(Doc));
-            // if (!linkedTo) {
-            //     protoSrc.linkedToDocs = linkedTo = new List<Doc>();
-            // }
-            // linkedTo.push(linkDoc);
-
             LinkManager.Instance.allLinks.push(linkDoc);
 
             return linkDoc;
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index aaed7388d..23ba9d2e4 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -30,7 +30,8 @@ export class LinkManager {
     }
 
     @observable public allLinks: Array<Doc> = []; // list of link docs
-    @observable public groupMetadataKeys: Map<string, Array<string>> = new Map(); // map of group type to list of its metadata keys
+    @observable public groupMetadataKeys: Map<string, Array<string>> = new Map();
+    // map of group type to list of its metadata keys; serves as a dictionary of groups to what kind of metadata it hodls
 
     // finds all links that contain the given anchor
     public findAllRelatedLinks(anchor: Doc): Array<Doc> {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index eb45c770e..6ed263ac8 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -511,9 +511,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let linkButton = null;
         if (SelectionManager.SelectedDocuments().length > 0) {
             let selFirst = SelectionManager.SelectedDocuments()[0];
-            // let linkToSize = Cast(selFirst.props.Document.linkedToDocs, listSpec(Doc), []).length;
-            // let linkFromSize = Cast(selFirst.props.Document.linkedFromDocs, listSpec(Doc), []).length;
-            // let linkCount = linkToSize + linkFromSize;
             let linkCount = LinkManager.Instance.findAllRelatedLinks(selFirst.props.Document).length;
             linkButton = (<Flyout
                 anchorPoint={anchorPoints.RIGHT_TOP}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index dcc1bd95d..9861b2c20 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -23,6 +23,7 @@ import { DocumentManager } from '../../util/DocumentManager';
 import { CollectionViewType } from './CollectionBaseView';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { LinkManager } from '../../util/LinkManager';
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -338,9 +339,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     tab.reactComponents = [upDiv];
                     tab.element.append(upDiv);
                     counter.DashDocId = tab.contentItem.config.props.documentId;
-                    tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title],
+                    tab.reactionDisposer = reaction(() => [LinkManager.Instance.findAllRelatedLinks(doc), doc.title],
                         () => {
-                            counter.innerHTML = DocListCast(doc.linkedFromDocs).length + DocListCast(doc.linkedToDocs).length;
+                            counter.innerHTML = LinkManager.Instance.findAllRelatedLinks(doc).length;
                             tab.titleElement[0].textContent = doc.title;
                         }, { fireImmediately: true });
                     tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fc6974e6c..c1a32ad82 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -31,6 +31,7 @@ import React = require("react");
 import { Id, Copy } from '../../../new_fields/FieldSymbols';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { list, object, createSimpleSchema } from 'serializr';
+import { LinkManager } from '../../util/LinkManager';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(faTrash);
@@ -49,16 +50,16 @@ library.add(faCrosshairs);
 library.add(faDesktop);
 
 
-const linkSchema = createSchema({
-    title: "string",
-    linkDescription: "string",
-    linkTags: "string",
-    linkedTo: Doc,
-    linkedFrom: Doc
-});
+// const linkSchema = createSchema({
+//     title: "string",
+//     linkDescription: "string",
+//     linkTags: "string",
+//     linkedTo: Doc,
+//     linkedFrom: Doc
+// });
 
-type LinkDoc = makeInterface<[typeof linkSchema]>;
-const LinkDoc = makeInterface(linkSchema);
+// type LinkDoc = makeInterface<[typeof linkSchema]>;
+// const LinkDoc = makeInterface(linkSchema);
 
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
@@ -216,8 +217,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 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 linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs, []);
-                let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs, []);
+                let linkedDocs = LinkManager.Instance.findAllRelatedLinks(this.props.Document);
                 let expandedDocs: Doc[] = [];
                 expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
                 expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
@@ -252,18 +252,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         this.props.collapseToPoint && this.props.collapseToPoint(scrpt, expandedProtoDocs);
                     }
                 }
-                else if (linkedToDocs.length || linkedFromDocs.length) {
-                    let linkedFwdDocs = [
-                        linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
-                        linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
-
-                    let linkedFwdPage = [
-                        linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : undefined,
-                        linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : undefined];
-                    if (!linkedFwdDocs.some(l => l instanceof Promise)) {
-                        let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab");
-                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0]);
-                    }
+                else if (linkedDocs.length) {
+                    let linkedDoc = linkedDocs.length ? linkedDocs[0] : expandedDocs[0];
+                    let linkedPages = [linkedDocs.length ? NumCast(linkedDocs[0].anchor1Page, undefined) : NumCast(linkedDocs[0].anchor2Page, undefined),
+                    linkedDocs.length ? NumCast(linkedDocs[0].anchor2Page, undefined) : NumCast(linkedDocs[0].anchor1Page, undefined)];
+                    let maxLocation = StrCast(linkedDoc.maximizeLocation, "inTab");
+                    DocumentManager.Instance.jumpToDocument(linkedDoc, ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedPages[altKey ? 1 : 0]);
                 }
             }
         }
-- 
cgit v1.2.3-70-g09d2


From 4d3d9b4c33be1df26144aea16dde060b9263924e Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sun, 16 Jun 2019 17:20:25 -0400
Subject: Added check for Docking Instance initializtion

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

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 51e29cb54..0ad82f345 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -48,7 +48,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     constructor(props: SubCollectionViewProps) {
         super(props);
-        CollectionDockingView.Instance = this;
+        if (!CollectionDockingView.Instance) {
+            CollectionDockingView.Instance = this;
+        }
+        //Why is this here?
         (window as any).React = React;
         (window as any).ReactDOM = ReactDOM;
     }
-- 
cgit v1.2.3-70-g09d2


From de0304b2966ebdede9d9db8c510e19020046115c Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samuel_wilkins@brown.edu>
Date: Mon, 17 Jun 2019 13:38:15 -0400
Subject: peripheral renaming fixes

---
 src/client/documents/Documents.ts                              |  4 ++--
 src/client/util/History.ts                                     |  6 +++---
 src/client/util/SearchUtil.ts                                  |  4 ++--
 src/client/util/TooltipTextMenu.tsx                            |  8 ++++----
 src/client/views/MainView.tsx                                  | 10 +++++-----
 src/client/views/SearchBox.tsx                                 |  6 +++---
 src/client/views/collections/CollectionDockingView.tsx         | 10 +++++-----
 src/client/views/collections/CollectionSubView.tsx             |  8 ++++----
 src/client/views/collections/CollectionVideoView.tsx           |  2 +-
 src/client/views/collections/DockedFrameRenderer.tsx           |  2 +-
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx  |  4 ++--
 .../views/collections/collectionFreeForm/MarqueeView.tsx       |  2 +-
 src/client/views/nodes/DocumentView.tsx                        |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx                    |  6 +++---
 src/client/views/nodes/PDFBox.tsx                              |  2 +-
 src/client/views/nodes/VideoBox.tsx                            |  2 +-
 src/debug/Viewer.tsx                                           |  2 +-
 src/mobile/ImageUpload.tsx                                     |  4 ++--
 src/new_fields/Proxy.ts                                        |  2 +-
 src/server/authentication/models/current_user_utils.ts         |  6 +++---
 20 files changed, 46 insertions(+), 46 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b10954636..758291b9b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -98,7 +98,7 @@ export namespace Docs {
             // non-guid string ids for each document prototype
             let protoIds = [textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId]
             // fetch the actual prototype documents from the server
-            let actualProtos = await DocServer.GetRefFields(protoIds);
+            let actualProtos = await DocServer.getRefFields(protoIds);
 
             // initialize prototype documents
             textProto = actualProtos[textProtoId] as Doc || CreateTextProto();
@@ -363,7 +363,7 @@ export namespace Docs {
                 CurrentUserUtils.AddNorthstarSchema(schema, schemaDoc);
                 const docs = schemaDocuments;
                 CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
-                    DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
+                    DocServer.getRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
                         if (field instanceof Doc) {
                             docs.push(field);
                         } else {
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 545ea8629..94bfcbe09 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -88,7 +88,7 @@ export namespace HistoryUtil {
     }
 
     export function createUrl(params: ParsedUrl): string {
-        let baseUrl = DocServer.prepend(`/${params.type}`);
+        let baseUrl = DocServer.Util.prepend(`/${params.type}`);
         switch (params.type) {
             case "doc":
                 const initializers = encodeURIComponent(JSON.stringify(params.initializers));
@@ -103,7 +103,7 @@ export namespace HistoryUtil {
     }
 
     export async function initDoc(id: string, initializer: DocInitializerList) {
-        const doc = await DocServer.GetRefField(id);
+        const doc = await DocServer.getRefField(id);
         if (!(doc instanceof Doc)) {
             return;
         }
@@ -111,7 +111,7 @@ export namespace HistoryUtil {
     }
 
     async function onDocUrl(url: DocUrl) {
-        const field = await DocServer.GetRefField(url.docId);
+        const field = await DocServer.getRefField(url.docId);
         await Promise.all(Object.keys(url.initializers).map(id => initDoc(id, url.initializers[id])));
         if (field instanceof Doc) {
             MainView.Instance.openWorkspace(field, true);
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 28ec8ca14..9dd9acbb7 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -7,13 +7,13 @@ export namespace SearchUtil {
     export function Search(query: string, returnDocs: true): Promise<Doc[]>;
     export function Search(query: string, returnDocs: false): Promise<string[]>;
     export async function Search(query: string, returnDocs: boolean) {
-        const ids = JSON.parse(await rp.get(DocServer.prepend("/search"), {
+        const ids = JSON.parse(await rp.get(DocServer.Util.prepend("/search"), {
             qs: { query }
         }));
         if (!returnDocs) {
             return ids;
         }
-        const docMap = await DocServer.GetRefFields(ids);
+        const docMap = await DocServer.getRefFields(ids);
         return ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc);
     }
 
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index fa2483db5..36219a99e 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -187,9 +187,9 @@ export class TooltipTextMenu {
                 let link = node && node.marks.find(m => m.type.name === "link");
                 if (link) {
                     let href: string = link.attrs.href;
-                    if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
-                        let docid = href.replace(DocServer.prepend("/doc/"), "");
-                        DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
+                    if (href.indexOf(DocServer.Util.prepend("/doc/")) === 0) {
+                        let docid = href.replace(DocServer.Util.prepend("/doc/"), "");
+                        DocServer.getRefField(docid).then(action((f: Opt<Field>) => {
                             if (f instanceof Doc) {
                                 if (DocumentManager.Instance.getDocumentView(f)) {
                                     DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
@@ -218,7 +218,7 @@ export class TooltipTextMenu {
                         handlers: {
                             dragComplete: action(() => {
                                 let m = dragData.droppedDocuments;
-                                this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
+                                this.makeLink(DocServer.Util.prepend("/doc/" + m[0][Id]));
                             }),
                         },
                         hideSource: false
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 984db0426..734961b56 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -76,11 +76,11 @@ export class MainView extends React.Component {
         // causes errors to be generated when modifying an observable outside of an action
         configure({ enforceActions: "observed" });
         if (window.location.search.includes("readonly")) {
-            DocServer.makeReadOnly();
+            DocServer.Util.makeReadOnly();
         }
         if (window.location.search.includes("safe")) {
             if (!window.location.search.includes("nro")) {
-                DocServer.makeReadOnly();
+                DocServer.Util.makeReadOnly();
             }
             CollectionBaseView.SetSafeMode(true);
         }
@@ -141,7 +141,7 @@ export class MainView extends React.Component {
                 this.createNewWorkspace();
             }
         } else {
-            DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field =>
+            DocServer.getRefField(CurrentUserUtils.MainDocId).then(field =>
                 field instanceof Doc ? this.openWorkspace(field) :
                     this.createNewWorkspace(CurrentUserUtils.MainDocId));
         }
@@ -294,7 +294,7 @@ export class MainView extends React.Component {
         let logoutRef = React.createRef<HTMLDivElement>();
 
         return [
-            <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.altKey && DocServer.DeleteDatabase()}>Clear Database</button>,
+            <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.altKey && DocServer.Util.deleteDatabase()}>Clear Database</button>,
             <div id="toolbar" key="toolbar">
                 <div ref={notifsRef}>
                     <button className="toolbar-button round-button" title="Notifs"
@@ -312,7 +312,7 @@ export class MainView extends React.Component {
             </div >,
             this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <SearchBox /> </div> : null,
             <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
-                <button onClick={() => request.get(DocServer.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
+                <button onClick={() => request.get(DocServer.Util.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
         ];
 
     }
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 7164d98a4..973715876 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -56,13 +56,13 @@ export class SearchBox extends React.Component {
 
     @action
     getResults = async (query: string) => {
-        let response = await rp.get(DocServer.prepend('/search'), {
+        let response = await rp.get(DocServer.Util.prepend('/search'), {
             qs: {
                 query
             }
         });
         let res: string[] = JSON.parse(response);
-        const fields = await DocServer.GetRefFields(res);
+        const fields = await DocServer.getRefFields(res);
         const docs: Doc[] = [];
         for (const id of res) {
             const field = fields[id];
@@ -74,7 +74,7 @@ export class SearchBox extends React.Component {
     }
     public static async convertDataUri(imageUri: string, returnedFilename: string) {
         try {
-            let posting = DocServer.prepend(RouteStore.dataUriToImage);
+            let posting = DocServer.Util.prepend(RouteStore.dataUriToImage);
             const returnedUri = await rp.post(posting, {
                 body: {
                     uri: imageUri,
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index e2bcb10ec..4f5837590 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -306,7 +306,7 @@ 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;
-            DocServer.GetRefField(docid).then(action(async (sourceDoc: Opt<Field>) =>
+            DocServer.getRefField(docid).then(action(async (sourceDoc: Opt<Field>) =>
                 (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
         } else
             if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) {
@@ -320,7 +320,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 if (glTab && glTab.contentItem && glTab.contentItem.parent) {
                     glTab.contentItem.parent.setActiveContentItem(glTab.contentItem);
                 }
-                DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
+                DocServer.getRefField(docid).then(action((f: Opt<Field>) => {
                     if (f instanceof Doc) {
                         DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y,
                             {
@@ -372,7 +372,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             if (tab.contentItem.config.fixed) {
                 tab.contentItem.parent.config.fixed = true;
             }
-            DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async doc => {
+            DocServer.getRefField(tab.contentItem.config.props.documentId).then(async doc => {
                 if (doc instanceof Doc) {
                     let counter: any = this.htmlToElement(`<span class="messageCounter">0</div>`);
                     tab.element.append(counter);
@@ -409,7 +409,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 if (tab.reactionDisposer) {
                     tab.reactionDisposer();
                 }
-                let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId);
+                let doc = await DocServer.getRefField(tab.contentItem.config.props.documentId);
                 if (doc instanceof Doc) {
                     let theDoc = doc;
                     CollectionDockingView.TopLevel._removedDocs.push(theDoc);
@@ -434,7 +434,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 //if (confirm('really close this?')) {
                 stack.remove();
                 stack.contentItems.map(async (contentItem: any) => {
-                    let doc = await DocServer.GetRefField(contentItem.config.props.documentId);
+                    let doc = await DocServer.getRefField(contentItem.config.props.documentId);
                     if (doc instanceof Doc) {
                         let theDoc = doc;
                         CollectionDockingView.TopLevel._removedDocs.push(theDoc);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 440a2410b..36e276d13 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -133,7 +133,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 if (path.includes(window.location.hostname)) {
                     let s = path.split('/');
                     let id = s[s.length - 1];
-                    DocServer.GetRefField(id).then(field => {
+                    DocServer.getRefField(id).then(field => {
                         if (field instanceof Doc) {
                             let alias = Doc.MakeAlias(field);
                             alias.x = options.x || 0;
@@ -170,8 +170,8 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             if (html && html.indexOf(document.location.origin)) { // prosemirror text containing link to dash document
                 let start = html.indexOf(window.location.origin);
                 let path = html.substr(start, html.length - start);
-                let docid = path.substr(0, path.indexOf("\">")).replace(DocServer.prepend("/doc/"), "").split("?")[0];
-                DocServer.GetRefField(docid).then(f => (f instanceof Doc) && this.props.addDocument(f, false));
+                let docid = path.substr(0, path.indexOf("\">")).replace(DocServer.Util.prepend("/doc/"), "").split("?")[0];
+                DocServer.getRefField(docid).then(f => (f instanceof Doc) && this.props.addDocument(f, false));
                 return;
             }
             if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
@@ -194,7 +194,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
                     let str: string;
                     let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve))
-                        .then(action((s: string) => rp.head(DocServer.prepend(RouteStore.corsProxy + "/" + (str = s)))))
+                        .then(action((s: string) => rp.head(DocServer.Util.prepend(RouteStore.corsProxy + "/" + (str = s)))))
                         .then(result => {
                             let type = result["content-type"];
                             if (type) {
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index bd5cd5450..ccbac9915 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -97,7 +97,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
                 let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
                 SearchBox.convertDataUri(dataUrl, filename).then((returnedFilename) => {
                     if (returnedFilename) {
-                        let url = DocServer.prepend(returnedFilename);
+                        let url = DocServer.Util.prepend(returnedFilename);
                         let imageSummary = Docs.Create.ImageDocument(url, {
                             x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
                             width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
diff --git a/src/client/views/collections/DockedFrameRenderer.tsx b/src/client/views/collections/DockedFrameRenderer.tsx
index 25d4b2a49..1e7c5661b 100644
--- a/src/client/views/collections/DockedFrameRenderer.tsx
+++ b/src/client/views/collections/DockedFrameRenderer.tsx
@@ -42,7 +42,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
     constructor(props: any) {
         super(props);
-        DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
+        DocServer.getRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
     }
 
     nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9d19df540..cd613e6ab 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -235,8 +235,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             e.preventDefault();
             let start = html.indexOf(window.location.origin);
             let path = html.substr(start, html.length - start);
-            let docid = path.substr(0, path.indexOf("\">")).replace(DocServer.prepend("/doc/"), "").split("?")[0];
-            DocServer.GetRefField(docid).then(f => {
+            let docid = path.substr(0, path.indexOf("\">")).replace(DocServer.Util.prepend("/doc/"), "").split("?")[0];
+            DocServer.getRefField(docid).then(f => {
                 if (f instanceof Doc) {
                     f.x = pt[0];
                     f.y = pt[1];
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index cd386abfa..07a58ed64 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -306,7 +306,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
                     SearchBox.convertDataUri(dataUrl, "icon" + summary[Id] + "_image").then((returnedFilename) => {
                         if (returnedFilename) {
-                            let url = DocServer.prepend(returnedFilename);
+                            let url = DocServer.Util.prepend(returnedFilename);
                             let imageSummary = Docs.Create.ImageDocument(url, {
                                 x: bounds.left, y: bounds.top + 100 / zoomBasis,
                                 width: 150, height: bounds.height / bounds.width * 150, title: "-summary image-"
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 16e40000d..fdcb20e9a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -422,7 +422,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }, icon: "search"
         });
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document), icon: "crosshairs" });
-        cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
+        cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.Util.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
         cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
         cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         if (!this.topMost) {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index d00a4b928..6a14a04f7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -237,9 +237,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 href = parent.childNodes[0].href;
             }
             if (href) {
-                if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
-                    let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
-                    DocServer.GetRefField(docid).then(f => {
+                if (href.indexOf(DocServer.Util.prepend("/doc/")) === 0) {
+                    let docid = href.replace(DocServer.Util.prepend("/doc/"), "").split("?")[0];
+                    DocServer.getRefField(docid).then(f => {
                         (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab"))
                     });
                 }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index aa29a7170..df9e49b64 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -257,7 +257,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                 .then(action((dataUrl: string) => {
                     SearchBox.convertDataUri(dataUrl, "icon" + this.Document[Id] + "_" + this.curPage).then((returnedFilename) => {
                         if (returnedFilename) {
-                            let url = DocServer.prepend(returnedFilename);
+                            let url = DocServer.Util.prepend(returnedFilename);
                             this.props.Document.thumbnail = new ImageField(new URL(url));
                         }
                         runInAction(() => this._renderAsSvg = true);
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 35ecf12f6..9ab607e91 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -97,7 +97,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
             };
             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.Util.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');
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index b22300d0b..753149756 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -146,7 +146,7 @@ class Viewer extends React.Component {
     @action
     onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
         if (e.key === "Enter") {
-            DocServer.GetRefField(this.idToAdd).then(action((field: any) => {
+            DocServer.getRefField(this.idToAdd).then(action((field: any) => {
                 if (field !== undefined) {
                     this.fields.push(field);
                 }
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index a8f94b746..df597e0a9 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -57,11 +57,11 @@ class Uploader extends React.Component {
 
                         this.status = "getting user document";
 
-                        const res = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId));
+                        const res = await rp.get(DocServer.Util.prepend(RouteStore.getUserDocumentId));
                         if (!res) {
                             throw new Error("No user id returned");
                         }
-                        const field = await DocServer.GetRefField(res);
+                        const field = await DocServer.getRefField(res);
                         let pending: Opt<Doc>;
                         if (field instanceof Doc) {
                             pending = await Cast(field.optionalRightCollection, Doc);
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
index 130ec066e..230e4ab8b 100644
--- a/src/new_fields/Proxy.ts
+++ b/src/new_fields/Proxy.ts
@@ -57,7 +57,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
             return undefined;
         }
         if (!this.promise) {
-            this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => {
+            this.promise = DocServer.getRefField(this.fieldId).then(action((field: any) => {
                 this.promise = undefined;
                 this.cache = field;
                 if (field === undefined) this.failed = true;
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 169be3b99..95c20d2db 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -40,7 +40,7 @@ export class CurrentUserUtils {
     }
 
     public static async loadCurrentUser(): Promise<any> {
-        let userPromise = rp.get(DocServer.prepend(RouteStore.getCurrUser)).then(response => {
+        let userPromise = rp.get(DocServer.Util.prepend(RouteStore.getCurrUser)).then(response => {
             if (response) {
                 let obj = JSON.parse(response);
                 CurrentUserUtils.curr_id = obj.id as string;
@@ -49,9 +49,9 @@ export class CurrentUserUtils {
                 throw new Error("There should be a user! Why does Dash think there isn't one?");
             }
         });
-        let userDocPromise = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId)).then(id => {
+        let userDocPromise = await rp.get(DocServer.Util.prepend(RouteStore.getUserDocumentId)).then(id => {
             if (id) {
-                return DocServer.GetRefField(id).then(field =>
+                return DocServer.getRefField(id).then(field =>
                     runInAction(() => this.user_document = field instanceof Doc ? field : this.createUserDocument(id)));
             } else {
                 throw new Error("There should be a user id! Why does Dash think there isn't one?");
-- 
cgit v1.2.3-70-g09d2


From f4fcf306e2579b7479610899a01c06fb157d47de Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 18 Jun 2019 12:03:14 -0400
Subject: fixed goldenlayout nesting

---
 .../views/collections/CollectionDockingView.tsx    |  4 +-
 src/client/views/nodes/FieldView.tsx               | 43 +++++++++++-----------
 src/client/views/nodes/KeyValuePair.tsx            |  2 +-
 3 files changed, 24 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index b5d57a015..4d48cedd8 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -48,9 +48,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     constructor(props: SubCollectionViewProps) {
         super(props);
-        if (!CollectionDockingView.Instance) {
-            CollectionDockingView.Instance = this;
-        }
+        if (props.addDocTab === emptyFunction) CollectionDockingView.Instance = this;
         //Why is this here?
         (window as any).React = React;
         (window as any).ReactDOM = ReactDOM;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 4738e90d7..1f1582f22 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -85,27 +85,28 @@ export class FieldView extends React.Component<FieldViewProps> {
             return <p>{field.date.toLocaleString()}</p>;
         }
         else if (field instanceof Doc) {
-            let returnHundred = () => 100;
-            return (
-                <DocumentContentsView Document={field}
-                    addDocument={undefined}
-                    addDocTab={this.props.addDocTab}
-                    removeDocument={undefined}
-                    ScreenToLocalTransform={Transform.Identity}
-                    ContentScaling={returnOne}
-                    PanelWidth={returnHundred}
-                    PanelHeight={returnHundred}
-                    isTopMost={true} //TODO Why is this top most?
-                    selectOnLoad={false}
-                    focus={emptyFunction}
-                    isSelected={this.props.isSelected}
-                    select={returnFalse}
-                    layoutKey={"layout"}
-                    ContainingCollectionView={this.props.ContainingCollectionView}
-                    parentActive={this.props.active}
-                    whenActiveChanged={this.props.whenActiveChanged}
-                    bringToFront={emptyFunction} />
-            );
+            return <p><b>{field.title}</b></p>;
+            // let returnHundred = () => 100;
+            // return (
+            //     <DocumentContentsView Document={field}
+            //         addDocument={undefined}
+            //         addDocTab={this.props.addDocTab}
+            //         removeDocument={undefined}
+            //         ScreenToLocalTransform={Transform.Identity}
+            //         ContentScaling={returnOne}
+            //         PanelWidth={returnHundred}
+            //         PanelHeight={returnHundred}
+            //         isTopMost={true} //TODO Why is this top most?
+            //         selectOnLoad={false}
+            //         focus={emptyFunction}
+            //         isSelected={this.props.isSelected}
+            //         select={returnFalse}
+            //         layoutKey={"layout"}
+            //         ContainingCollectionView={this.props.ContainingCollectionView}
+            //         parentActive={this.props.active}
+            //         whenActiveChanged={this.props.whenActiveChanged}
+            //         bringToFront={emptyFunction} />
+            // );
         }
         else if (field instanceof List) {
             return (<div>
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index e8bc17532..dd1bca7f6 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -40,7 +40,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             focus: emptyFunction,
             PanelWidth: returnZero,
             PanelHeight: returnZero,
-            addDocTab: emptyFunction
+            addDocTab: returnZero,
         };
         let contents = <FieldView {...props} />;
         let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
-- 
cgit v1.2.3-70-g09d2


From 9544576ec0167d64f564ae4c87d392eba07ff467 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <35748010+samwilkins333@users.noreply.github.com>
Date: Tue, 18 Jun 2019 13:18:34 -0400
Subject: Added tab focusing on hover

---
 src/client/views/collections/CollectionDockingView.tsx | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4d48cedd8..9aa4ab45a 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -336,6 +336,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     tab.element.append(counter);
                     let upDiv = document.createElement("span");
                     const stack = tab.contentItem.parent;
+                    // shifts the focus to this tab when another tab is dragged over it
+                    tab.element[0].onmouseenter = (e: any) => {
+                        if (!this._isPointerDown) return;
+                        var activeContentItem = tab.header.parent.getActiveContentItem();
+                        if (tab.contentItem !== activeContentItem) {
+                            tab.header.parent.setActiveContentItem(tab.contentItem);
+                        }
+                        tab.setActive(true);
+                    }
                     ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv);
                     tab.reactComponents = [upDiv];
                     tab.element.append(upDiv);
@@ -503,4 +512,4 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 {({ measureRef }) => <div ref={measureRef}>  {theContent} </div>}
             </Measure>;
     }
-}
\ No newline at end of file
+}
-- 
cgit v1.2.3-70-g09d2


From b1af251b058743798aa3fa3895d22327c8560dfc Mon Sep 17 00:00:00 2001
From: Sam Wilkins <35748010+samwilkins333@users.noreply.github.com>
Date: Tue, 18 Jun 2019 13:19:50 -0400
Subject: Added pointer down flag for tab focus

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

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 9aa4ab45a..6b0e6deba 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -45,6 +45,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     private _containerRef = React.createRef<HTMLDivElement>();
     private _flush: boolean = false;
     private _ignoreStateChange = "";
+    private _isPointerDown = false;
 
     constructor(props: SubCollectionViewProps) {
         super(props);
-- 
cgit v1.2.3-70-g09d2


From ca126adda9e4def83fb5c2e07e382917ca0b4ee0 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 18 Jun 2019 17:24:59 -0400
Subject: Fixed docking view?

---
 src/client/views/collections/CollectionDockingView.tsx | 2 ++
 1 file changed, 2 insertions(+)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6b0e6deba..f2b3528b8 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -250,6 +250,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     @action
     onPointerUp = (e: React.PointerEvent): void => {
+        this._isPointerDown = false;
         if (this._flush) {
             this._flush = false;
             setTimeout(() => this.stateChanged(), 10);
@@ -257,6 +258,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
     @action
     onPointerDown = (e: React.PointerEvent): void => {
+        this._isPointerDown = true;
         var className = (e.target as any).className;
         if (className === "messageCounter") {
             e.stopPropagation();
-- 
cgit v1.2.3-70-g09d2


From 13e301dea2f537b67b338cc6a98d3f3b5a8e1f36 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 18 Jun 2019 20:58:32 -0400
Subject: Fixed linter errors

---
 src/client/northstar/dash-nodes/HistogramBox.tsx             |  3 +--
 src/client/util/DocumentManager.ts                           |  6 +++---
 src/client/util/DragManager.ts                               |  4 +++-
 src/client/util/RichTextSchema.tsx                           | 10 +++++-----
 src/client/util/TooltipTextMenu.tsx                          |  2 +-
 src/client/views/ContextMenu.tsx                             |  4 ++--
 src/client/views/DocumentDecorations.tsx                     |  2 +-
 src/client/views/MainOverlayTextBox.tsx                      |  2 +-
 src/client/views/MainView.tsx                                |  7 ++++---
 src/client/views/PresentationView.tsx                        |  4 ++--
 src/client/views/collections/CollectionBaseView.tsx          |  2 +-
 src/client/views/collections/CollectionDockingView.tsx       |  5 +++--
 src/client/views/collections/CollectionPDFView.tsx           |  2 +-
 src/client/views/collections/CollectionSchemaView.tsx        | 11 ++++++-----
 src/client/views/collections/CollectionStackingView.tsx      |  4 ++--
 src/client/views/collections/CollectionTreeView.tsx          | 10 +++++-----
 .../collectionFreeForm/CollectionFreeFormLinksView.tsx       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx            |  2 +-
 .../views/collections/collectionFreeForm/MarqueeView.tsx     |  2 +-
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx    |  2 +-
 src/client/views/nodes/DocumentView.tsx                      | 12 ++++++------
 src/client/views/nodes/FormattedTextBox.tsx                  |  2 +-
 src/client/views/nodes/ImageBox.tsx                          |  3 ++-
 src/client/views/pdf/PDFAnnotationLayer.tsx                  |  2 +-
 src/client/views/pdf/PDFMenu.tsx                             |  2 +-
 src/client/views/pdf/PDFViewer.tsx                           |  4 ++--
 src/server/index.ts                                          |  2 +-
 27 files changed, 59 insertions(+), 54 deletions(-)

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

diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index d7732ee86..a60eaea85 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -125,8 +125,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                                 let mapped = brushingDocs.map((brush, i) => {
                                     brush.backgroundColor = StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length];
                                     let brushed = DocListCast(brush.brushingDocs);
-                                    if (!brushed.length)
-                                        return null;
+                                    if (!brushed.length) return null;
                                     return { l: brush, b: brushed[0][Id] === proto[Id] ? brushed[1] : brushed[0] };
                                 });
                                 runInAction(() => this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...mapped.filter(m => m) as { l: Doc, b: Doc }[]));
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ff0c1560b..862395d74 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -35,9 +35,9 @@ export class DocumentManager {
         let toReturn: DocumentView | null = null;
         let passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
 
-        for (let i = 0; i < passes.length; i++) {
+        for (let pass of passes) {
             DocumentManager.Instance.DocumentViews.map(view => {
-                if (view.props.Document[Id] === id && (!passes[i] || view.props.ContainingCollectionView === preferredCollection)) {
+                if (view.props.Document[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
                     toReturn = view;
                     return;
                 }
@@ -45,7 +45,7 @@ export class DocumentManager {
             if (!toReturn) {
                 DocumentManager.Instance.DocumentViews.map(view => {
                     let doc = view.props.Document.proto;
-                    if (doc && doc[Id] === id && (!passes[i] || view.props.ContainingCollectionView === preferredCollection)) {
+                    if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
                         toReturn = view;
                     }
                 });
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 89566e777..c3c92daa5 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -282,11 +282,13 @@ export namespace DragManager {
             // }
             let set = dragElement.getElementsByTagName('*');
             if (dragElement.hasAttribute("style")) (dragElement as any).style.pointerEvents = "none";
-            for (let i = 0; i < set.length; i++)
+            // tslint:disable-next-line: prefer-for-of
+            for (let i = 0; i < set.length; i++) {
                 if (set[i].hasAttribute("style")) {
                     let s = set[i];
                     (s as any).style.pointerEvents = "none";
                 }
+            }
 
 
             dragDiv.appendChild(dragElement);
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index e1e595925..943cdb4d1 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -156,12 +156,12 @@ export const nodes: { [index: string]: NodeSpec } = {
                     title: dom.getAttribute("title"),
                     alt: dom.getAttribute("alt"),
                     width: Math.min(100, Number(dom.getAttribute("width"))),
-                }
+                };
             }
         }],
         toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}` }
-            return ["video", { ...node.attrs, ...attrs }]
+            const attrs = { style: `width: ${node.attrs.width}` };
+            return ["video", { ...node.attrs, ...attrs }];
         }
     },
 
@@ -285,7 +285,7 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM() {
             return ['span', {
                 style: 'color: blue'
-            }]
+            }];
         }
     },
 
@@ -536,4 +536,4 @@ schema.nodeFromJSON = (json: any) => {
         node.attrs.oldtext = Slice.fromJSON(schema, node.attrs.oldtextslice);
     }
     return node;
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 0a61b7e7d..f2559db74 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -256,7 +256,7 @@ export class TooltipTextMenu {
             starButton.onclick = () => {
                 let state = this.view.state;
                 this.insertStar(state, this.view.dispatch);
-            }
+            };
 
             this.tooltip.appendChild(starButton);
         }
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index da374455e..eb1937683 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,8 +1,8 @@
 import React = require("react");
 import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
 import { observable, action } from "mobx";
-import { observer } from "mobx-react"
-import "./ContextMenu.scss"
+import { observer } from "mobx-react";
+import "./ContextMenu.scss";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faSearch, faCircle } from '@fortawesome/free-solid-svg-icons';
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index ceca940b6..e60f8c86c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -466,7 +466,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         SelectionManager.SelectedDocuments().forEach(element => {
             const rect = element.ContentDiv ? element.ContentDiv.getBoundingClientRect() : new DOMRect();
 
-            if (rect.width !== 0 && (dX != 0 || dY != 0 || dW != 0 || dH != 0)) {
+            if (rect.width !== 0 && (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0)) {
                 let doc = PositionDocument(element.props.Document);
                 let nwidth = doc.nativeWidth || 0;
                 let nheight = doc.nativeHeight || 0;
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 0de880175..b4ad5f4d7 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -40,7 +40,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                     this.TextDoc = box.props.Document;
                     let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined);
                     let xf = () => { box.props.ScreenToLocalTransform(); return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale); };
-                    this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content")
+                    this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content");
                 }
                 else {
                     this.TextDoc = undefined;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index fd76cbbd3..7f979cd3b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -237,10 +237,11 @@ export class MainView extends React.Component {
     }
 
     onColorClick = (e: React.MouseEvent) => {
-        let target = (e.nativeEvent! as any).path[0];
-        let parent = (e.nativeEvent! as any).path[1];
-        if (target.localName === "input" || parent.localName === "span")
+        let target = (e.nativeEvent as any).path[0];
+        let parent = (e.nativeEvent as any).path[1];
+        if (target.localName === "input" || parent.localName === "span") {
             e.stopPropagation();
+        }
     }
 
 
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index d2d41a4ba..1dacbb663 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -40,8 +40,8 @@ class PresentationViewList extends React.Component<PresListProps> {
             //this doc is selected
             className += " presentationView-selected";
         }
-        let onEnter = (e: React.PointerEvent) => { document.libraryBrush = true; }
-        let onLeave = (e: React.PointerEvent) => { document.libraryBrush = false; }
+        let onEnter = (e: React.PointerEvent) => { document.libraryBrush = true; };
+        let onLeave = (e: React.PointerEvent) => { document.libraryBrush = false; };
         return (
             <div className={className} key={document[Id] + index}
                 onPointerEnter={onEnter} onPointerLeave={onLeave}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 5238ad114..1e42593d1 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -130,7 +130,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     @action.bound
     removeDocument(doc: Doc): boolean {
-        let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView)
+        let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
         docView && SelectionManager.DeselectDoc(docView);
         const props = this.props;
         //TODO This won't create the field if it doesn't already exist
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f2b3528b8..5beb89315 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -347,7 +347,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                             tab.header.parent.setActiveContentItem(tab.contentItem);
                         }
                         tab.setActive(true);
-                    }
+                    };
                     ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv);
                     tab.reactComponents = [upDiv];
                     tab.element.append(upDiv);
@@ -432,8 +432,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _document: Opt<Doc>;
     get _stack(): any {
         let parent = (this.props as any).glContainer.parent.parent;
-        if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1)
+        if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) {
             return parent.parent.contentItems[1];
+        }
         return parent;
     }
     constructor(props: any) {
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index b62d3f7bb..b2d016934 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -40,7 +40,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
                 // console.log(this.props.Document[HeightSym]());
             },
             { fireImmediately: true }
-        )
+        );
     }
 
     public static LayoutString(fieldKey: string = "data") {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 14a7d19d0..faea8d44d 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -87,8 +87,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             let columnDocs = DocListCast(schemaDoc.data);
             if (columnDocs) {
                 let ddoc = columnDocs.find(doc => doc.title === columnName);
-                if (ddoc)
+                if (ddoc) {
                     return ddoc;
+                }
             }
         }
         return this.props.Document;
@@ -285,7 +286,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     }
 
     getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(
-        - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth, - this.borderWidth);
+        - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth, - this.borderWidth)
 
 
     get documentKeysCheckList() {
@@ -334,7 +335,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             columns={this.tableColumns}
             column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }}
             getTrProps={this.getTrProps}
-        />
+        />;
     }
 
     @computed
@@ -360,7 +361,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             addDocTab={this.props.addDocTab}
             setPreviewScript={this.setPreviewScript}
             previewScript={this.previewScript}
-        />
+        />;
     }
     @action
     setPreviewScript = (script: string) => {
@@ -409,7 +410,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
     }
     private PanelWidth = () => this.nativeWidth * this.contentScaling();
     private PanelHeight = () => this.nativeHeight * this.contentScaling();
-    private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, 0).scale(1 / this.contentScaling())
+    private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, 0).scale(1 / this.contentScaling());
     get centeringOffset() { return (this.props.width() - this.nativeWidth * this.contentScaling()) / 2; }
     @action
     onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e1ac3505b..f5ad4ee95 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -101,7 +101,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             let childFocus = (doc: Doc) => {
                 doc.libraryBrush = true;
                 this.props.focus(this.props.Document); // just focus on this collection, not the underlying document because the API doesn't support adding an offset to focus on and we can't pan zoom our contents to be centered.
-            }
+            };
             return (<div className="collectionStackingView-masonryDoc"
                 key={d[Id]}
                 ref={dref}
@@ -131,7 +131,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     whenActiveChanged={this.props.whenActiveChanged}
                 />
             </div>);
-        })
+        });
     }
     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
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b13694e9d..3e495b734 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -67,8 +67,8 @@ class TreeView extends React.Component<TreeViewProps> {
     @undoBatch delete = () => this.props.deleteDoc(this.props.document);
     @undoBatch openRight = async () => this.props.addDocTab(this.props.document, "openRight");
 
-    @action onMouseEnter = () => { this._isOver = true; }
-    @action onMouseLeave = () => { this._isOver = false; }
+    @action onMouseEnter = () => { this._isOver = true; };
+    @action onMouseLeave = () => { this._isOver = false; };
 
     onPointerEnter = (e: React.PointerEvent): void => {
         this.props.active() && (this.props.document.libraryBrush = true);
@@ -89,8 +89,8 @@ class TreeView extends React.Component<TreeViewProps> {
         let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
         let before = x[1] < bounds[1];
         let inside = x[0] > bounds[0] + 75 || (!before && this._bulletType === BulletType.Collapsible);
-        this._header!.current!.className = "treeViewItem-header"
-        if (inside && this._bulletType != BulletType.List) this._header!.current!.className += " treeViewItem-header-inside";
+        this._header!.current!.className = "treeViewItem-header";
+        if (inside && this._bulletType !== BulletType.List) this._header!.current!.className += " treeViewItem-header-inside";
         else if (before) this._header!.current!.className += " treeViewItem-header-above";
         else if (!before) this._header!.current!.className += " treeViewItem-header-below";
         e.stopPropagation();
@@ -192,7 +192,7 @@ class TreeView extends React.Component<TreeViewProps> {
             if (inside) {
                 let docList = Cast(this.props.document.data, listSpec(Doc));
                 if (docList !== undefined) {
-                    addDoc = (doc: Doc) => { docList && docList.push(doc); return true; }
+                    addDoc = (doc: Doc) => { docList && docList.push(doc); return true; };
                 }
             }
             let added = false;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index c4dd534ed..be75c6c5c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -109,7 +109,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
                     }
                     return match || found;
                 }, false)) {
-                    drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })
+                    drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] });
                 }
             });
             return drawnPairs;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 250b6bf5d..84841e469 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -342,7 +342,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private childViews = () => [
         <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
         ...this.views
-    ];
+    ]
     render() {
         const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
         const easing = () => this.props.Document.panTransformType === "Ease";
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index dedcc3172..05dc6f534 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -321,7 +321,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                             summary.imageSummary = imageSummary;
                             this.props.addDocument(imageSummary, false);
                         }
-                    })
+                    });
                     newCollection.proto!.summaryDoc = summary;
                     selected = [newCollection];
                     newCollection.x = bounds.left + bounds.width;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index f6b1c62ee..858959d81 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -48,7 +48,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     panelHeight = () => this.props.PanelHeight();
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
-        .scale(1 / this.contentScaling()).scale(1 / this.zoom);
+        .scale(1 / this.contentScaling()).scale(1 / this.zoom)
 
     animateBetweenIcon = (icon: number[], stime: number, maximizing: boolean) => {
         this.props.bringToFront(this.props.Document);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7c058d91c..4992669df 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -168,8 +168,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     public static animateBetweenIconFunc = (doc: Doc, width: number, height: number, stime: number, maximizing: boolean, cb?: (progress: number) => void) => {
         setTimeout(() => {
             let now = Date.now();
-            let progress = now < stime + 200 ? Math.min(1, (now - stime) / 200) : 1
-            doc.width = progress === 1 ? width : maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress
+            let progress = now < stime + 200 ? Math.min(1, (now - stime) / 200) : 1;
+            doc.width = progress === 1 ? width : maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress;
             doc.height = progress === 1 ? height : maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress;
             cb && cb(progress);
             if (now < stime + 200) {
@@ -229,7 +229,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (minimizedDoc) {
             let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(
                 NumCast(minimizedDoc.x) - NumCast(this.Document.x), NumCast(minimizedDoc.y) - NumCast(this.Document.y));
-            this.collapseTargetsToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs))
+            this.collapseTargetsToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs));
         }
     }
 
@@ -372,8 +372,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._lastTap = Date.now();
     }
 
-    deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); }
-    fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight") };
+    deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); };
+    fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight"); };
     makeBtnClicked = (): void => {
         let doc = Doc.GetProto(this.props.Document);
         doc.isButton = !BoolCast(doc.isButton, false);
@@ -527,7 +527,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
 
     isSelected = () => SelectionManager.IsSelected(this);
-    @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }
+    @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
 
     @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
     @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 36d902c4f..df12f261b 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -161,7 +161,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     de.data.moveDocument(de.data.draggedDocuments[0], stackDoc, (doc) => {
                         Cast(stackDoc.data, listSpec(Doc))!.push(doc);
                         return true;
-                    })
+                    });
                 }
             }
         }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 6b8b64c5f..f208ce2ce 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -188,8 +188,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     }
     @action onError = () => {
         let timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount;
-        if (timeout < 10)
+        if (timeout < 10) {
             setTimeout(this.retryPath, Math.min(10000, timeout * 5));
+        }
     }
     _curSuffix = "_m";
     render() {
diff --git a/src/client/views/pdf/PDFAnnotationLayer.tsx b/src/client/views/pdf/PDFAnnotationLayer.tsx
index e92dcacbf..1f49e0d2f 100644
--- a/src/client/views/pdf/PDFAnnotationLayer.tsx
+++ b/src/client/views/pdf/PDFAnnotationLayer.tsx
@@ -19,6 +19,6 @@ export class PDFAnnotationLayer extends React.Component {
             <div className="pdfAnnotationLayer-cont" style={{ width: "100%", height: "100%", position: "relative", top: "-200%" }} onPointerDown={this.onPointerDown}>
 
             </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 7817e8c26..2ed891131 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -162,6 +162,6 @@ export default class PDFMenu extends React.Component {
                 </button>
                 <div className="pdfMenu-dragger" onPointerDown={this.dragStart} style={{ width: this._pinned ? "20px" : "0px" }} />
             </div >
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 69372f43b..8c0aaea00 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -198,7 +198,7 @@ class Viewer extends React.Component<IViewerProps> {
                 <Page
                     pdf={this.props.pdf}
                     page={page}
-                    numPages={this.props.pdf!.numPages}
+                    numPages={this.props.pdf.numPages}
                     key={`rendered-${page + 1}`}
                     name={`${this.props.pdf.fingerprint + `-page${page + 1}`}`}
                     pageLoaded={this.pageLoaded}
@@ -219,7 +219,7 @@ class Viewer extends React.Component<IViewerProps> {
     @action
     getPageImage = async (page: number) => {
         let handleError = () => this.getRenderedPage(page);
-        if (this._isPage[page] != "image") {
+        if (this._isPage[page] !== "image") {
             this._isPage[page] = "image";
             const address = this.props.url;
             let res = JSON.parse(await rp.get(DocServer.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`)));
diff --git a/src/server/index.ts b/src/server/index.ts
index 7ef542b01..2901f61ed 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -172,7 +172,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) {
                     canvasContext: canvasAndContext.context,
                     viewport: viewport,
                     canvasFactory: factory
-                }
+                };
                 console.log("read " + pageNumber);
 
                 page.render(renderContext).promise
-- 
cgit v1.2.3-70-g09d2


From 3b880d7b15b7107049ae27601b9f759b17f7fde9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 18 Jun 2019 22:51:46 -0400
Subject: added initial keyboard shortcuts for adding and moving docs in
 TreeView.  fixed image drag bug.

---
 src/client/documents/Documents.ts                  |  3 +-
 src/client/views/EditableView.tsx                  | 34 +++++---
 .../views/collections/CollectionDockingView.tsx    |  1 +
 .../views/collections/CollectionTreeView.tsx       | 96 +++++++++++++++++-----
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 5 files changed, 103 insertions(+), 33 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 91d3707f6..fcd1010c6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -51,7 +51,6 @@ export interface DocumentOptions {
     panY?: number;
     page?: number;
     scale?: number;
-    baseLayout?: string;
     layout?: string;
     templates?: List<string>;
     viewType?: number;
@@ -136,7 +135,7 @@ export namespace Docs {
     }
 
     function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Doc {
-        return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout, baseLayout: layout });
+        return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout });
     }
     function SetInstanceOptions<U extends Field>(doc: Doc, options: DocumentOptions, value: U) {
         const deleg = Doc.MakeDelegate(doc);
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index c946d68e1..a96fca464 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -18,13 +18,17 @@ export interface EditableProps {
 
     OnFillDown?(value: string): void;
 
+    OnTab?(): void;
+
     /**
      * The contents to render when not editing
      */
     contents: any;
+    fontStyle?: string;
     height?: number;
     display?: string;
     oneLine?: boolean;
+    editing?: boolean
 }
 
 /**
@@ -34,40 +38,48 @@ export interface EditableProps {
  */
 @observer
 export class EditableView extends React.Component<EditableProps> {
-    @observable
-    editing: boolean = false;
+    @observable _editing: boolean = false;
+
+    constructor(props: EditableProps) {
+        super(props);
+        this._editing = this.props.editing ? true : false;
+    }
 
     @action
     onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
-        if (e.key === "Enter") {
+        if (e.key === "Tab") {
+            this.props.OnTab && this.props.OnTab();
+        } else if (e.key === "Enter") {
             if (!e.ctrlKey) {
                 if (this.props.SetValue(e.currentTarget.value)) {
-                    this.editing = false;
+                    this._editing = false;
                 }
             } else if (this.props.OnFillDown) {
                 this.props.OnFillDown(e.currentTarget.value);
-                this.editing = false;
+                this._editing = false;
             }
         } else if (e.key === "Escape") {
-            this.editing = false;
+            this._editing = false;
         }
     }
 
     @action
     onClick = (e: React.MouseEvent) => {
-        this.editing = true;
+        this._editing = true;
         e.stopPropagation();
     }
 
     render() {
-        if (this.editing) {
-            return <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
+        if (this._editing) {
+            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${this.props.oneLine ? "-oneLine" : ""}`} 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={this.onClick} >
-                    <span>{this.props.contents}</span>
+                    <span style={{ fontStyle: this.props.fontStyle }}>{this.props.contents}</span>
                 </div>
             );
         }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f2b3528b8..bd3020a78 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -467,6 +467,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         let docHeight = NumCast(this._document!.height);
         if (NumCast(this._document!.nativeWidth) || !docWidth || !this._panelWidth || !this._panelHeight) return 1;
         if (StrCast(this._document!.layout).indexOf("Collection") === -1 ||
+            !BoolCast(this._document!.fitToContents, false) ||
             NumCast(this._document!.viewType) !== CollectionViewType.Freeform) return 1;
         let scaling = Math.max(1, this._panelWidth / docWidth * docHeight > this._panelHeight ?
             this._panelHeight / docHeight : this._panelWidth / docWidth);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b13694e9d..23efe9f79 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -21,6 +21,7 @@ import "./CollectionTreeView.scss";
 import React = require("react");
 import { Transform } from '../../util/Transform';
 import { SelectionManager } from '../../util/SelectionManager';
+import { emptyFunction } from '../../../Utils';
 
 
 export interface TreeViewProps {
@@ -30,6 +31,7 @@ export interface TreeViewProps {
     dropAction: "alias" | "copy" | undefined;
     addDocTab: (doc: Doc, where: string) => void;
     addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
+    indentDocument?: () => void;
     ScreenToLocalTransform: () => Transform;
     treeViewId: string;
     parentKey: string;
@@ -114,6 +116,12 @@ class TreeView extends React.Component<TreeViewProps> {
         }
         return true;
     }
+    @action
+    indent = () => {
+        this.props.addDocument(this.props.document);
+        this.delete();
+    }
+
 
     renderBullet(type: BulletType) {
         let onClicked = action(() => this._collapsed = !this._collapsed);
@@ -124,25 +132,37 @@ class TreeView extends React.Component<TreeViewProps> {
         }
         return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>;
     }
+    static loadId = "";
+    editableView = (key: string, style?: string) =>
+        (<EditableView
+            oneLine={true}
+            display={"inline"}
+            editing={this.props.document[Id] === TreeView.loadId}
+            contents={StrCast(this.props.document[key])}
+            height={36}
+            fontStyle={style}
+            GetValue={() => StrCast(this.props.document[key])}
+            OnFillDown={(value: string) => {
+                Doc.GetProto(this.props.document)[key] = value;
+                let doc = Docs.FreeformDocument([], { title: "untitled" });
+                TreeView.loadId = doc[Id];
+                this.props.addDocument(doc);
+                return true;
+            }}
+            OnTab={() => this.props.indentDocument && this.props.indentDocument()}
+            SetValue={(value: string) => {
+                Doc.GetProto(this.props.document)[key] = value;
+                return true;
+            }}
+        />);
+
     /**
      * Renders the EditableView title element for placement into the tree.
      */
     renderTitle() {
         let reference = React.createRef<HTMLDivElement>();
         let onItemDown = SetupDrag(reference, () => this.props.document, this.move, this.props.dropAction, this.props.treeViewId, true);
-        let editableView = (titleString: string) =>
-            (<EditableView
-                oneLine={!this._isOver ? true : false}
-                display={"inline"}
-                contents={titleString}
-                height={36}
-                GetValue={() => StrCast(this.props.document.title)}
-                SetValue={(value: string) => {
-                    let target = this.props.document.proto ? this.props.document.proto : this.props.document;
-                    target.title = value;
-                    return true;
-                }}
-            />);
+
         let dataDocs = CollectionDockingView.Instance ? 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}>
@@ -156,7 +176,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
                 }}
             >
-                {editableView(StrCast(this.props.document.title))}
+                {this.editableView("title")}
                 {/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */}
             </div >
             {openRight}
@@ -221,6 +241,7 @@ class TreeView extends React.Component<TreeViewProps> {
         return true;
     }
 
+    @observable _chosenKey: string = "data"
     _bulletType: BulletType = BulletType.List;
     render() {
         let bulletType = BulletType.List;
@@ -234,7 +255,21 @@ class TreeView extends React.Component<TreeViewProps> {
             keys.splice(keys.indexOf("data"), 1);
             keys.splice(0, 0, "data");
         }
+        let keyList: string[] = [];
         keys.map(key => {
+            let docList = Cast(this.props.document[key], listSpec(Doc));
+            let doc = Cast(this.props.document[key], Doc);
+            if (doc instanceof Doc || (docList && (DocListCast(docList).length > 0 || key === "data"))) {
+                keyList.push(key);
+            }
+        });
+        let headerElements = <div style={{ display: "block", marginTop: "7px" }} key={this._chosenKey}>{keyList.map(key =>
+            <span className="collectionTreeView-keyHeader" key={key} onPointerDown={action(() => this._chosenKey = key)}
+                style={{ display: "inline", marginRight: "3px", marginTop: "7px", background: key === this._chosenKey ? "lightgray" : undefined }}>
+                {key}
+            </span>)}
+        </div>
+        [this._chosenKey].map(key => {
             let docList = Cast(this.props.document[key], listSpec(Doc));
             let remDoc = (doc: Doc) => this.remove(doc, key);
             let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.document, key, doc, addBefore, before);
@@ -243,9 +278,10 @@ class TreeView extends React.Component<TreeViewProps> {
                 if (!this._collapsed) {
                     bulletType = BulletType.Collapsible;
                     contentElement.push(<ul key={key + "more"}>
-                        <span className="collectionTreeView-keyHeader" style={{ display: "block", marginTop: "7px" }} key={key}>{key}</span>
+                        {headerElements}
                         <div style={{ display: "block" }}>
                             {TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, key, addDoc, remDoc, this.move,
+                                this.indent,
                                 this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active)}
                         </div>
                     </ul >);
@@ -274,14 +310,36 @@ class TreeView extends React.Component<TreeViewProps> {
         add: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean,
         remove: ((doc: Doc) => void),
         move: DragManager.MoveFunction,
+        indent: () => void,
         dropAction: dropActionType,
         addDocTab: (doc: Doc, where: string) => void,
         screenToLocalXf: () => Transform,
         active: () => boolean
     ) {
-        return docs.filter(child => !child.excludeFromLibrary && (key !== "data" || !child.isMinimized)).map(child =>
-            <TreeView document={child} treeViewId={treeViewId} key={child[Id]} deleteDoc={remove} addDocument={add} moveDocument={move}
-                dropAction={dropAction} addDocTab={addDocTab} ScreenToLocalTransform={screenToLocalXf} parentKey={key} active={active} />);
+        let docList = docs.filter(child => !child.excludeFromLibrary && (key !== "data" || !child.isMinimized));
+        return docList.map((child, i) => {
+            let indent = i == 0 ? undefined : () => {
+                if (StrCast(docList[i - 1].layout).indexOf("CollectionView") !== -1) {
+                    let fieldKeysub = StrCast(docList[i - 1].layout).split("fieldKey")[1];
+                    let fieldKey = fieldKeysub.split("\"")[1];
+                    TreeView.AddDocToList(docList[i - 1], fieldKey, child);
+                    remove(child);
+                }
+            }
+            return <TreeView
+                document={child}
+                treeViewId={treeViewId}
+                key={child[Id]}
+                indentDocument={indent}
+                deleteDoc={remove}
+                addDocument={add}
+                moveDocument={move}
+                dropAction={dropAction}
+                addDocTab={addDocTab}
+                ScreenToLocalTransform={screenToLocalXf}
+                parentKey={key}
+                active={active} />
+        });
     }
 }
 
@@ -321,7 +379,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
         let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
         let childElements = TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove,
-            moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active);
+            moveDoc, emptyFunction, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active);
 
         return (
             <div id="body" className="collectionTreeView-dropTarget"
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 6b8b64c5f..345fa5cc7 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -217,7 +217,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         let aspect = (rotation % 180) ? this.props.Document[HeightSym]() / this.props.Document[WidthSym]() : 1;
         let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
         return (
-            <div id={id} className={`imageBox-cont${interactive}`}
+            <div id={id} className={`imageBox-cont${interactive}`} style={{ background: "transparent" }}
                 onPointerDown={this.onPointerDown}
                 onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
                 <img id={id}
-- 
cgit v1.2.3-70-g09d2


From e495d4e32402384dbf7bb72945e83c912fce892b Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 20 Jun 2019 17:46:56 -0400
Subject: first shot at templates...

---
 src/client/views/MainOverlayTextBox.tsx            |  6 ++--
 src/client/views/MainView.tsx                      |  1 +
 .../views/collections/CollectionDockingView.tsx    |  4 ++-
 .../views/collections/CollectionSchemaView.tsx     |  3 +-
 .../views/collections/CollectionStackingView.tsx   |  4 ++-
 src/client/views/collections/CollectionView.tsx    | 37 ++++++++++++++++++++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 src/client/views/nodes/DocumentContentsView.tsx    | 12 ++++---
 src/client/views/nodes/DocumentView.tsx            |  1 +
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/FormattedTextBox.tsx        | 32 ++++++++++---------
 11 files changed, 77 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index b4ad5f4d7..64271650b 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -37,7 +37,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
             (box?: FormattedTextBox) => {
                 this._textBox = box;
                 if (box) {
-                    this.TextDoc = box.props.Document;
+                    this.TextDoc = box.props.DataDoc;
                     let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined);
                     let xf = () => { box.props.ScreenToLocalTransform(); return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale); };
                     this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content");
@@ -118,7 +118,9 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                 <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
                     style={{ width: `${textRect.width * s}px`, height: "0px" }}>
                     <div style={{ height: hgt, width: "100%", position: "absolute", bottom: this._textBottom ? "0px" : undefined }}>
-                        <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document}
+                        <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true}
+                            Document={FormattedTextBox.InputBoxOverlay.props.Document}
+                            DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
                             isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true}
                             ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
                             ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} />
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 51630c29b..8198b88d2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -203,6 +203,7 @@ export class MainView extends React.Component {
         let mainCont = this.mainContainer;
         let content = !mainCont ? (null) :
             <DocumentView Document={mainCont}
+                DataDoc={mainCont}
                 addDocument={undefined}
                 addDocTab={emptyFunction}
                 removeDocument={undefined}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 69b9e77eb..9a25e090c 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -492,7 +492,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         return (
             <div className="collectionDockingView-content" ref={this._mainCont}
                 style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier}, ${this.scaleToFitMultiplier})` }}>
-                <DocumentView key={this._document[Id]} Document={this._document}
+                <DocumentView key={this._document[Id]}
+                    Document={this._document}
+                    DataDoc={this._document}
                     bringToFront={emptyFunction}
                     addDocument={undefined}
                     removeDocument={undefined}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 9cc8961e3..c5b0527cc 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -98,6 +98,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     renderCell = (rowProps: CellInfo) => {
         let props: FieldViewProps = {
             Document: rowProps.original,
+            DataDoc: rowProps.original,
             fieldKey: rowProps.column.id as string,
             ContainingCollectionView: this.props.CollectionView,
             isSelected: returnFalse,
@@ -423,7 +424,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
         return (<div className="collectionSchemaView-previewRegion" style={{ width: this.props.width(), height: "100%" }}>
             {!this.props.Document || !this.props.width ? (null) : (
                 <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.centeringOffset}px, 0px)`, height: "100%" }}>
-                    <DocumentView Document={this.props.Document} isTopMost={false} selectOnLoad={false}
+                    <DocumentView DataDoc={this.props.Document} Document={this.props.Document} isTopMost={false} selectOnLoad={false}
                         addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} moveDocument={this.props.moveDocument}
                         ScreenToLocalTransform={this.getTransform}
                         ContentScaling={this.contentScaling}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 368e94a8c..8e61181f4 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -119,7 +119,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     gridColumnEnd: `span 1`,
                     transform: `scale(${renderScale})`
                 }} >
-                <DocumentView key={d[Id]} Document={d}
+                <DocumentView key={d[Id]}
+                    Document={d}
+                    DataDoc={d}
                     addDocument={this.props.addDocument}
                     removeDocument={this.props.removeDocument}
                     moveDocument={this.moveDocument}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 68eefab4c..6d3301ce4 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -14,6 +14,10 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
 import { CollectionTreeView } from "./CollectionTreeView";
+import { Doc } from '../../../new_fields/Doc';
+import { FormattedTextBox } from '../nodes/FormattedTextBox';
+import { Docs } from '../../documents/Documents';
+import { List } from '../../../new_fields/List';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
@@ -54,6 +58,39 @@ export class CollectionView extends React.Component<FieldViewProps> {
             subItems.push({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
             subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
             ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems });
+            ContextMenu.Instance.addItem({
+                description: "Add Description Template", event: undoBatch(() => {
+                    Doc.GetProto(this.props.Document).description = "my first templated box";
+                    let template = Doc.MakeAlias(this.props.Document);
+                    template.layout = FormattedTextBox.LayoutString("description");
+                    template.x = 0;
+                    template.y = 0;
+                    template.width = 100;
+                    template.height = 25;
+                    Doc.AddDocToList(this.props.Document, "data", template);
+                }), icon: "project-diagram"
+            });
+            ContextMenu.Instance.addItem({
+                description: "Add Summary Template", event: undoBatch(() => {
+                    Doc.GetProto(this.props.Document).summary = "my first templated box";
+                    let template = Doc.MakeAlias(this.props.Document);
+                    template.layout = FormattedTextBox.LayoutString("summary");
+                    template.x = 0;
+                    template.y = 0;
+                    template.width = 100;
+                    template.height = 25;
+                    Doc.AddDocToList(this.props.Document, "data", template);
+                }), icon: "project-diagram"
+            });
+            ContextMenu.Instance.addItem({
+                description: "Apply Template", event: undoBatch(() => {
+                    let otherdoc = Docs.TextDocument({ width: 100, height: 50, title: "applied template" });
+                    Doc.GetProto(otherdoc).description = "THIS DESCRIPTION IS REALLY IMPORTANT!";
+                    Doc.GetProto(otherdoc).summary = "THIS SUMMARY IS MEANINGFUL!";
+                    Doc.GetProto(otherdoc).layout = this.props.Document;
+                    this.props.addDocTab && this.props.addDocTab(otherdoc, "onRight");
+                }), icon: "project-diagram"
+            });
         }
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 84841e469..013c11837 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -296,6 +296,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     getDocumentViewProps(document: Doc): DocumentViewProps {
         return {
             Document: document,
+            DataDoc: this.props.DataDoc,
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 02396c3af..a4316c143 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -23,6 +23,7 @@ import { FieldViewProps } from "./FieldView";
 import { Without, OmitKeys } from "../../../Utils";
 import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
+import { Doc } from "../../../new_fields/Doc";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -47,11 +48,12 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     hideOnLeave?: boolean
 }> {
     @computed get layout(): string {
-        const layout = Cast(this.props.Document[this.props.layoutKey], "string");
+        let layoutDoc = this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
+        const layout = Cast(layoutDoc[this.props.layoutKey], "string");
         if (layout === undefined) {
             return this.props.Document.data ?
                 "<FieldView {...props} fieldKey='data' />" :
-                KeyValueBox.LayoutString(this.props.Document.proto ? "proto" : "");
+                KeyValueBox.LayoutString(layoutDoc.proto ? "proto" : "");
         } else if (typeof layout === "string") {
             return layout;
         } else {
@@ -59,8 +61,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         }
     }
 
-    CreateBindings(): JsxBindings {
-        return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
+    CreateBindings(layoutDoc?: Doc): JsxBindings {
+        return { props: { ...OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit, Document: layoutDoc } };
     }
 
     @computed get templates(): List<string> {
@@ -104,7 +106,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
             components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
-            bindings={this.CreateBindings()}
+            bindings={this.CreateBindings(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document)}
             jsx={this.finalLayout}
             showWarnings={true}
             onError={(test: any) => { console.log(test); }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 280804630..93b883533 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -67,6 +67,7 @@ const LinkDoc = makeInterface(linkSchema);
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     Document: Doc;
+    DataDoc: Doc;
     addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
     removeDocument?: (doc: Doc) => boolean;
     moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 1f1582f22..35608af86 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -30,6 +30,7 @@ export interface FieldViewProps {
     fieldKey: string;
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     Document: Doc;
+    DataDoc: Doc;
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
     isTopMost: boolean;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 376b5a574..8231cc089 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEdit, faSmile } from '@fortawesome/free-solid-svg-icons';
-import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction, computed } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
@@ -104,19 +104,21 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    @computed get dataDoc() { return this.props.DataDoc; }
+
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView) {
             const state = this._editorView.state.apply(tx);
             this._editorView.updateState(state);
             this._applyingChange = true;
-            Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON())));
-            Doc.SetOnPrototype(this.props.Document, "documentText", state.doc.textBetween(0, state.doc.content.size, "\n\n"));
+            Doc.SetOnPrototype(this.dataDoc, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON())));
+            Doc.SetOnPrototype(this.dataDoc, "documentText", state.doc.textBetween(0, state.doc.content.size, "\n\n"));
             this._applyingChange = false;
-            let title = StrCast(this.props.Document.title);
+            let title = StrCast(this.dataDoc.title);
             if (title && title.startsWith("-") && this._editorView) {
                 let str = this._editorView.state.doc.textContent;
                 let titlestr = str.substr(0, Math.min(40, str.length));
-                let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+                let target = this.dataDoc.proto ? this.dataDoc.proto : this.dataDoc;
                 target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
             }
         }
@@ -144,14 +146,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             e.stopPropagation();
         } else {
             if (de.data instanceof DragManager.DocumentDragData) {
-                let ldocs = Cast(this.props.Document.subBulletDocs, listSpec(Doc));
+                let ldocs = Cast(this.dataDoc.subBulletDocs, listSpec(Doc));
                 if (!ldocs) {
-                    this.props.Document.subBulletDocs = new List<Doc>([]);
+                    this.dataDoc.subBulletDocs = new List<Doc>([]);
                 }
-                ldocs = Cast(this.props.Document.subBulletDocs, listSpec(Doc));
+                ldocs = Cast(this.dataDoc.subBulletDocs, listSpec(Doc));
                 if (!ldocs) return;
                 if (!ldocs || !ldocs[0] || ldocs[0] instanceof Promise || StrCast((ldocs[0] as Doc).layout).indexOf("CollectionView") === -1) {
-                    ldocs.splice(0, 0, Docs.StackingDocument([], { title: StrCast(this.props.Document.title) + "-subBullets", x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document.height), width: 300, height: 300 }));
+                    ldocs.splice(0, 0, Docs.StackingDocument([], { title: StrCast(this.dataDoc.title) + "-subBullets", x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document.height), width: 300, height: 300 }));
                     this.props.addDocument && this.props.addDocument(ldocs[0] as Doc);
                     this.props.Document.templates = new List<string>([Templates.Bullet.Layout]);
                     this.props.Document.isBullet = true;
@@ -201,13 +203,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
         this._reactionDisposer = reaction(
             () => {
-                const field = this.props.Document ? Cast(this.props.Document[this.props.fieldKey], RichTextField) : undefined;
+                const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined;
                 return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
             },
             field => this._editorView && !this._applyingChange &&
                 this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)))
         );
-        this.setupEditor(config, this.props.Document, this.props.fieldKey);
+        this.setupEditor(config, this.dataDoc, this.props.fieldKey);
     }
 
     private setupEditor(config: any, doc: Doc, fieldKey: string) {
@@ -360,10 +362,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         // stop propagation doesn't seem to stop propagation of native keyboard events.
         // so we set a flag on the native event that marks that the event's been handled.
         (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
-        if (StrCast(this.props.Document.title).startsWith("-") && this._editorView) {
+        if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView) {
             let str = this._editorView.state.doc.textContent;
             let titlestr = str.substr(0, Math.min(40, str.length));
-            let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+            let target = this.dataDoc.proto ? this.dataDoc.proto : this.dataDoc;
             target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
         }
         if (!this._undoTyping) {
@@ -372,11 +374,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (this.props.isOverlay && this.props.Document.autoHeight) {
             let xf = this._ref.current!.getBoundingClientRect();
             let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height);
-            let nh = NumCast(this.props.Document.nativeHeight, 0);
+            let nh = NumCast(this.dataDoc.nativeHeight, 0);
             let dh = NumCast(this.props.Document.height, 0);
             let sh = scrBounds.height;
             this.props.Document.height = nh ? dh / nh * sh : sh;
-            this.props.Document.proto!.nativeHeight = nh ? sh : undefined;
+            this.dataDoc.proto!.nativeHeight = nh ? sh : undefined;
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 9ea5aa5a5da3f098afbc92343c63bc21afaf4b1f Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samuel_wilkins@brown.edu>
Date: Thu, 20 Jun 2019 19:39:18 -0400
Subject: fixed missed pointer up event bug

---
 src/client/views/collections/CollectionDockingView.tsx | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 69b9e77eb..3113ada73 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -250,7 +250,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     @action
     onPointerUp = (e: React.PointerEvent): void => {
-        this._isPointerDown = false;
         if (this._flush) {
             this._flush = false;
             setTimeout(() => this.stateChanged(), 10);
@@ -259,6 +258,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         this._isPointerDown = true;
+        console.log("DOWN")
+
+        let onPointerUp = action(() => {
+            window.removeEventListener("pointerup", onPointerUp)
+            this._isPointerDown = false
+            console.log("UP")
+        })
+        window.addEventListener("pointerup", onPointerUp);
         var className = (e.target as any).className;
         if (className === "messageCounter") {
             e.stopPropagation();
-- 
cgit v1.2.3-70-g09d2


From a40e7bb5e9d1256002083d7e3f3c4db60cd8e9df Mon Sep 17 00:00:00 2001
From: Sam Wilkins <35748010+samwilkins333@users.noreply.github.com>
Date: Thu, 20 Jun 2019 19:41:39 -0400
Subject: Fixed missed pointer up event

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

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 69b9e77eb..de5c66c0b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -259,6 +259,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         this._isPointerDown = true;
+        let onPointerUp = action(() => {
+            window.removeEventListener("pointerup", onPointerUp)
+            this._isPointerDown = false
+        })
+        window.addEventListener("pointerup", onPointerUp);
         var className = (e.target as any).className;
         if (className === "messageCounter") {
             e.stopPropagation();
-- 
cgit v1.2.3-70-g09d2


From 5483c3353b99e7993f358e3d212b1717bf21fec6 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 21 Jun 2019 10:44:15 -0400
Subject: more template improvements for treeview

---
 src/client/views/DocumentDecorations.tsx           | 14 ++++----
 src/client/views/Templates.tsx                     |  4 +--
 .../views/collections/CollectionDockingView.tsx    |  4 +--
 .../views/collections/CollectionStackingView.tsx   |  2 +-
 .../views/collections/CollectionTreeView.tsx       | 37 +++++++++++++++++++---
 src/client/views/collections/CollectionView.tsx    | 27 ----------------
 src/client/views/nodes/KeyValuePair.tsx            |  1 +
 7 files changed, 45 insertions(+), 44 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b3f5340d9..3a366b4d2 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -74,24 +74,22 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (text[0] === '#') {
                 this._fieldKey = text.slice(1, text.length);
                 this._title = this.selectionTitle;
+            } else if (text[0] === ">") {
+                let metaKey = text.slice(1, text.length);
                 let first = SelectionManager.SelectedDocuments()[0].props.Document!;
                 let collection = SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView!.props.Document;
-                Doc.GetProto(collection)[this._fieldKey] = "Testing";
+                Doc.GetProto(collection)[metaKey] = "-empty field-";
                 let template = Doc.MakeAlias(collection);
-                template.title = "FIELD-" + this._fieldKey;
-                template.layout = FormattedTextBox.LayoutString(this._fieldKey);
+                template.title = metaKey;
+                template.layout = FormattedTextBox.LayoutString(metaKey);
                 template.isTemplate = true;
                 template.x = NumCast(first.x);
                 template.y = NumCast(first.y);
                 template.width = first[WidthSym]();
                 template.height = first[HeightSym]();
-                //{props.DataDoc.${fieldKey}}
-                template.templates = new List<string>([Templates.TitleBar(this._fieldKey)]);
+                template.templates = new List<string>([Templates.TitleBar(metaKey)]);//`{props.DataDoc.${metaKey}_text}`)]);
                 Doc.AddDocToList(collection, "data", template);
                 SelectionManager.SelectedDocuments().map(dv => dv.props.removeDocument && dv.props.removeDocument(dv.props.Document));
-
-                // let template = SelectionManager.SelectedDocuments()[0].props.Document; template.proto = Doc.GetProto(collection)
-                // template.layout = FormattedTextBox.LayoutString(this._fieldKey);
             }
             else {
                 if (SelectionManager.SelectedDocuments().length > 0) {
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 0904b03b0..5bb8d454a 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -84,10 +84,10 @@ export namespace Templates {
             </div > `);
     }
 
-    export function TitleBar(titlestr: string) {
+    export function TitleBar(datastring: string) {
         return (`<div>
             <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; z-index: 100">
-                <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">${titlestr}</span>
+                <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">${datastring}</span>
             </div>
             <div style="height:calc(100% - 25px);">
                 <div style="width:100%;overflow:auto">{layout}</div>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 05c3cdac6..79a0d18df 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -261,8 +261,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         this._isPointerDown = true;
         let onPointerUp = action(() => {
             window.removeEventListener("pointerup", onPointerUp)
-            this._isPointerDown = false
-        })
+            this._isPointerDown = false;
+        });
         window.addEventListener("pointerup", onPointerUp);
         var className = (e.target as any).className;
         if (className === "messageCounter") {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index a1e84302b..19a9a1208 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -100,7 +100,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             let dref = React.createRef<HTMLDivElement>();
             let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]());
             let width = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth;
-            let height = () => aspect ? width() / aspect : d[HeightSym]()
+            let height = () => aspect ? width() / aspect : d[HeightSym]();
             let rowSpan = Math.ceil((height() + this.gridGap) / (this._gridSize + this.gridGap));
             return (<div className="collectionStackingView-masonryDoc"
                 key={d[Id]}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 7528bc29d..18d6751c6 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -25,10 +25,13 @@ import { CollectionSchemaPreview } from './CollectionSchemaView';
 import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import React = require("react");
+import { FormattedTextBox } from '../nodes/FormattedTextBox';
 
 
 export interface TreeViewProps {
     document: Doc;
+    dataDoc: Doc;
+    containingCollection: Doc;
     deleteDoc: (doc: Doc) => boolean;
     moveDocument: DragManager.MoveFunction;
     dropAction: "alias" | "copy" | undefined;
@@ -142,7 +145,29 @@ class TreeView extends React.Component<TreeViewProps> {
         height={36}
         fontStyle={style}
         GetValue={() => StrCast(this.props.document[key])}
-        SetValue={(value: string) => (Doc.GetProto(this.props.document)[key] = value) ? true : true}
+        SetValue={(value: string) => {
+            let res = (Doc.GetProto(this.props.document)[key] = value) ? true : true;
+
+            if (value.startsWith(">")) {
+                let metaKey = value.slice(1, value.length);
+                let collection = this.props.containingCollection;
+                Doc.GetProto(collection)[metaKey] = "-empty field-";
+                let template = Doc.MakeAlias(collection);
+                template.title = metaKey;
+                template.embed = true;
+                template.layout = FormattedTextBox.LayoutString(metaKey);
+                template.x = 0;
+                template.y = 0;
+                template.width = 100;
+                template.height = 50;
+                template.isTemplate = true;
+                template.templates = new List<string>([Templates.TitleBar(metaKey)]);//`{props.DataDoc.${metaKey}_text}`)]);
+                Doc.AddDocToList(collection, "data", template);
+                this.delete();
+            }
+
+            return res;
+        }}
         OnFillDown={(value: string) => {
             Doc.GetProto(this.props.document)[key] = value;
             let doc = Docs.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
@@ -269,14 +294,14 @@ class TreeView extends React.Component<TreeViewProps> {
         if (!this._collapsed) {
             if (!this.props.document.embed) {
                 contentElement = <ul key={this._chosenKey + "more"}>
-                    {TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this._chosenKey, addDoc, remDoc, this.move,
+                    {TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.props.dataDoc, this._chosenKey, addDoc, remDoc, this.move,
                         this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth)}
                 </ul >;
             } else {
                 contentElement = <div ref={this._dref} style={{ display: "inline-block", height: this.props.panelHeight() }} key={this.props.document[Id]}>
                     <CollectionSchemaPreview
                         Document={this.props.document}
-                        DataDocument={this.props.document}
+                        DataDocument={this.props.dataDoc}
                         width={docWidth}
                         height={this.props.panelHeight}
                         getTransform={this.docTransform}
@@ -307,6 +332,8 @@ class TreeView extends React.Component<TreeViewProps> {
     public static GetChildElements(
         docs: Doc[],
         treeViewId: string,
+        containingCollection: Doc,
+        dataDoc: Doc,
         key: string,
         add: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean,
         remove: ((doc: Doc) => boolean),
@@ -338,6 +365,8 @@ class TreeView extends React.Component<TreeViewProps> {
             };
             return <TreeView
                 document={child}
+                dataDoc={dataDoc}
+                containingCollection={containingCollection}
                 treeViewId={treeViewId}
                 key={child[Id]}
                 indentDocument={indent}
@@ -419,7 +448,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                 </div>
                 <ul className="no-indent">
                     {
-                        TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove,
+                        TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove,
                             moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth)
                     }
                 </ul>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index f254e6005..89e5ad74c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -58,33 +58,6 @@ export class CollectionView extends React.Component<FieldViewProps> {
             subItems.push({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
             subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
             ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems });
-            ContextMenu.Instance.addItem({
-                description: "Add Description Template", event: undoBatch(() => {
-                    let collection = this.props.Document;
-                    Doc.GetProto(collection).description = "my first templated box";
-                    let template = Doc.MakeAlias(collection);
-                    template.layout = FormattedTextBox.LayoutString("description");
-                    template.isTemplate = true;
-                    template.x = 0;
-                    template.y = 0;
-                    template.width = 100;
-                    template.height = 25;
-                    Doc.AddDocToList(this.props.Document, "data", template);
-                }), icon: "project-diagram"
-            });
-            ContextMenu.Instance.addItem({
-                description: "Add Summary Template", event: undoBatch(() => {
-                    Doc.GetProto(this.props.Document).summary = "my first templated box";
-                    let template = Doc.MakeAlias(this.props.Document);
-                    template.layout = FormattedTextBox.LayoutString("summary");
-                    template.isTemplate = true;
-                    template.x = 0;
-                    template.y = 0;
-                    template.width = 100;
-                    template.height = 25;
-                    Doc.AddDocToList(this.props.Document, "data", template);
-                }), icon: "project-diagram"
-            });
             ContextMenu.Instance.addItem({
                 description: "Apply Template", event: undoBatch(() => {
                     let otherdoc = Docs.TextDocument({ width: 100, height: 50, title: "applied template" });
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 420a1ad94..d3bd9c875 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -28,6 +28,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
     render() {
         let props: FieldViewProps = {
             Document: this.props.doc,
+            DataDoc: this.props.doc,
             ContainingCollectionView: undefined,
             fieldKey: this.props.keyName,
             isSelected: returnFalse,
-- 
cgit v1.2.3-70-g09d2


From 542501fc3247dafa5879482f62a28147ec4b3793 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 21 Jun 2019 13:13:18 -0400
Subject: lots more template stuff.

---
 src/client/util/DocumentManager.ts                 |   6 +-
 src/client/util/DragManager.ts                     |  18 ++--
 src/client/views/DocumentDecorations.tsx           |  35 ++++----
 src/client/views/MainOverlayTextBox.tsx            |   8 +-
 .../views/collections/CollectionDockingView.tsx    | 100 +++++++++++----------
 .../views/collections/CollectionSchemaView.tsx     |   2 +-
 .../views/collections/CollectionTreeView.tsx       |  33 +++++--
 src/client/views/collections/CollectionView.tsx    |   4 +-
 .../views/collections/ParentDocumentSelector.tsx   |   4 +-
 src/client/views/nodes/DocumentView.tsx            |  27 +++---
 src/client/views/nodes/FieldView.tsx               |   2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |   2 +-
 src/client/views/nodes/ImageBox.tsx                |  43 +++++----
 src/client/views/pdf/PDFViewer.tsx                 |   1 -
 src/new_fields/Doc.ts                              |   8 ++
 15 files changed, 174 insertions(+), 119 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 862395d74..8421c902e 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -138,7 +138,7 @@ export class DocumentManager {
                         docContext.panTransformType = "Ease";
                         targetContextView.props.focus(docDelegate);
                     } else {
-                        (dockFunc || CollectionDockingView.Instance.AddRightSplit)(docContext);
+                        (dockFunc || CollectionDockingView.Instance.AddRightSplit)(docContext, docContext);
                         setTimeout(() => {
                             this.jumpToDocument(docDelegate, forceDockFunc, dockFunc, linkPage);
                         }, 10);
@@ -147,7 +147,7 @@ export class DocumentManager {
                     const actualDoc = Doc.MakeAlias(docDelegate);
                     actualDoc.libraryBrush = true;
                     if (linkPage !== undefined) actualDoc.curPage = linkPage;
-                    (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc);
+                    (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc, actualDoc);
                 }
             } else {
                 let contextView: DocumentView | null;
@@ -156,7 +156,7 @@ export class DocumentManager {
                     contextDoc.panTransformType = "Ease";
                     contextView.props.focus(docDelegate);
                 } else {
-                    (dockFunc || CollectionDockingView.Instance.AddRightSplit)(contextDoc);
+                    (dockFunc || CollectionDockingView.Instance.AddRightSplit)(contextDoc, contextDoc);
                     setTimeout(() => {
                         this.jumpToDocument(docDelegate, forceDockFunc, dockFunc, linkPage);
                     }, 10);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index c3c92daa5..cc83b7346 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -15,7 +15,8 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
 
         document.removeEventListener("pointermove", onRowMove);
         document.removeEventListener('pointerup', onRowUp);
-        var dragData = new DragManager.DocumentDragData([await docFunc()]);
+        let doc = await docFunc();
+        var dragData = new DragManager.DocumentDragData([doc], [doc]);
         dragData.dropAction = dropAction;
         dragData.moveDocument = moveFunc;
         dragData.options = options;
@@ -31,7 +32,7 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
         if (e.button === 0) {
             e.stopPropagation();
             if (e.shiftKey && CollectionDockingView.Instance) {
-                CollectionDockingView.Instance.StartOtherDrag([await docFunc()], e);
+                CollectionDockingView.Instance.StartOtherDrag(e, [await docFunc()]);
             } else {
                 document.addEventListener("pointermove", onRowMove);
                 document.addEventListener("pointerup", onRowUp);
@@ -59,7 +60,8 @@ export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: n
             let doc = await Cast(draggedDoc.annotationOn, Doc);
             if (doc) moddrag.push(doc);
         }
-        let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
+        let linkDocs = moddrag.length ? moddrag : draggedDocs;
+        let dragData = new DragManager.DocumentDragData(linkDocs, linkDocs); // bcz: dataDocs?
         DragManager.StartDocumentDrag([dragEle], dragData, x, y, {
             handlers: {
                 dragComplete: action(emptyFunction),
@@ -141,13 +143,15 @@ export namespace DragManager {
 
     export type MoveFunction = (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     export class DocumentDragData {
-        constructor(dragDoc: Doc[]) {
+        constructor(dragDoc: Doc[], dragDataDocs: Doc[]) {
             this.draggedDocuments = dragDoc;
+            this.draggedDataDocs = dragDataDocs;
             this.droppedDocuments = dragDoc;
             this.xOffset = 0;
             this.yOffset = 0;
         }
         draggedDocuments: Doc[];
+        draggedDataDocs: Doc[];
         droppedDocuments: Doc[];
         xOffset: number;
         yOffset: number;
@@ -238,6 +242,8 @@ export namespace DragManager {
 
         const docs: Doc[] =
             dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnnotationDragData ? [dragData.dragDocument] : [];
+        const datadocs: Doc[] =
+            dragData instanceof DocumentDragData ? dragData.draggedDataDocs : dragData instanceof AnnotationDragData ? [dragData.dragDocument] : [];
         let dragElements = eles.map(ele => {
             const w = ele.offsetWidth,
                 h = ele.offsetHeight;
@@ -314,12 +320,12 @@ export namespace DragManager {
             }
             if (e.shiftKey && CollectionDockingView.Instance) {
                 AbortDrag();
-                CollectionDockingView.Instance.StartOtherDrag(docs, {
+                CollectionDockingView.Instance.StartOtherDrag({
                     pageX: e.pageX,
                     pageY: e.pageY,
                     preventDefault: emptyFunction,
                     button: 0
-                });
+                }, docs, datadocs);
             }
             //TODO: Why can't we use e.movementX and e.movementY?
             let moveX = e.pageX - lastX;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3a366b4d2..8f02f04d7 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -166,7 +166,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let dragDocView = SelectionManager.SelectedDocuments()[0];
         const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
         const [xoff, yoff] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top);
-        let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
+        let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document), SelectionManager.SelectedDocuments().map(dv => dv.props.DataDoc));
         dragData.xOffset = xoff;
         dragData.yOffset = yoff;
         dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument;
@@ -485,9 +485,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 let doc = PositionDocument(element.props.Document);
                 let nwidth = doc.nativeWidth || 0;
                 let nheight = doc.nativeHeight || 0;
-                let zoomBasis = NumCast(doc.zoomBasis, 1);
-                let width = (doc.width || 0) / zoomBasis;
-                let height = (doc.height || (nheight / nwidth * width)) / zoomBasis;
+                let width = (doc.width || 0);
+                let height = (doc.height || (nheight / nwidth * width));
                 let scale = element.props.ScreenToLocalTransform().Scale;
                 let actualdW = Math.max(width + (dW * scale), 20);
                 let actualdH = Math.max(height + (dH * scale), 20);
@@ -502,25 +501,27 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 }
                 if (nwidth > 0 && nheight > 0) {
                     if (Math.abs(dW) > Math.abs(dH)) {
-                        if (!fixedAspect) proto.nativeWidth = zoomBasis * actualdW / (doc.width || 1) * NumCast(proto.nativeWidth);
-                        doc.width = zoomBasis * actualdW;
-                        // doc.zoomBasis = zoomBasis * width / actualdW;
+                        if (!fixedAspect) {
+                            Doc.SetInPlace(element.props.Document, "nativeWidth", actualdW / (doc.width || 1) * (doc.nativeWidth || 0), true);
+                        }
+                        doc.width = actualdW;
                         if (fixedAspect) doc.height = nheight / nwidth * doc.width;
-                        else doc.height = zoomBasis * actualdH;
-                        proto.nativeHeight = (doc.height || 0) / doc.width * NumCast(proto.nativeWidth);
+                        else doc.height = actualdH;
+                        Doc.SetInPlace(element.props.Document, "nativeHeight", (doc.height || 0) / doc.width * (doc.nativeWidth || 0), true);
                     }
                     else {
-                        if (!fixedAspect) proto.nativeHeight = zoomBasis * actualdH / (doc.height || 1) * NumCast(proto.nativeHeight);
-                        doc.height = zoomBasis * actualdH;
-                        //doc.zoomBasis = zoomBasis * height / actualdH;
+                        if (!fixedAspect) {
+                            Doc.SetInPlace(element.props.Document, "nativeHeight", actualdH / (doc.height || 1) * (doc.nativeHeight || 0), true);
+                        }
+                        doc.height = actualdH;
                         if (fixedAspect) doc.width = nwidth / nheight * doc.height;
-                        else doc.width = zoomBasis * actualdW;
-                        proto.nativeWidth = (doc.width || 0) / doc.height * NumCast(proto.nativeHeight);
+                        else doc.width = actualdW;
+                        Doc.SetInPlace(element.props.Document, "nativeWidth", (doc.width || 0) / doc.height * (doc.nativeHeight || 0), true);
                     }
                 } else {
-                    dW && (doc.width = zoomBasis * actualdW);
-                    dH && (doc.height = zoomBasis * actualdH);
-                    proto.autoHeight = undefined;
+                    dW && (doc.width = actualdW);
+                    dH && (doc.height = actualdH);
+                    Doc.SetInPlace(element.props.Document, "autoHeight", undefined, true);
                 }
             }
         });
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 64271650b..73d59ce31 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -82,10 +82,10 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     }
     @action
     textBoxMove = (e: PointerEvent) => {
-        if (e.movementX > 1 || e.movementY > 1) {
+        if ((e.movementX > 1 || e.movementY > 1) && FormattedTextBox.InputBoxOverlay) {
             document.removeEventListener("pointermove", this.textBoxMove);
             document.removeEventListener('pointerup', this.textBoxUp);
-            let dragData = new DragManager.DocumentDragData(FormattedTextBox.InputBoxOverlay ? [FormattedTextBox.InputBoxOverlay.props.Document] : []);
+            let dragData = new DragManager.DocumentDragData([FormattedTextBox.InputBoxOverlay.props.Document], [FormattedTextBox.InputBoxOverlay.props.DataDoc]);
             const [left, top] = this._textXf().inverse().transformPoint(0, 0);
             dragData.xOffset = e.clientX - left;
             dragData.yOffset = e.clientY - top;
@@ -102,9 +102,9 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         document.removeEventListener('pointerup', this.textBoxUp);
     }
 
-    addDocTab = (doc: Doc, location: string) => {
+    addDocTab = (doc: Doc, dataDoc: Doc, location: string) => {
         if (true) { // location === "onRight") { need to figure out stack to add "inTab"
-            CollectionDockingView.Instance.AddRightSplit(doc);
+            CollectionDockingView.Instance.AddRightSplit(doc, dataDoc);
         }
     }
     render() {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 79a0d18df..89fadab57 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -28,7 +28,7 @@ import { MainView } from '../MainView';
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
     public static Instance: CollectionDockingView;
-    public static makeDocumentConfig(document: Doc, width?: number) {
+    public static makeDocumentConfig(document: Doc, dataDoc: Doc, width?: number) {
         return {
             type: 'react-component',
             component: 'DocumentFrameRenderer',
@@ -36,6 +36,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             width: width,
             props: {
                 documentId: document[Id],
+                dataDocumentId: dataDoc[Id]
                 //collectionDockingView: CollectionDockingView.Instance
             }
         };
@@ -56,19 +57,19 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
     hack: boolean = false;
     undohack: any = null;
-    public StartOtherDrag(dragDocs: Doc[], e: any) {
+    public StartOtherDrag(e: any, dragDocs: Doc[], dragDataDocs?: Doc[]) {
         this.hack = true;
         this.undohack = UndoManager.StartBatch("goldenDrag");
-        dragDocs.map(dragDoc =>
-            this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.
+        dragDocs.map((dragDoc, i) =>
+            this.AddRightSplit(dragDoc, dragDataDocs ? dragDataDocs[i] : dragDoc, true).contentItems[0].tab._dragListener.
                 onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
     }
 
     @action
-    public OpenFullScreen(document: Doc) {
+    public OpenFullScreen(document: Doc, dataDoc: Doc) {
         let newItemStackConfig = {
             type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(document)]
+            content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)]
         };
         var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
         this._goldenLayout.root.contentItems[0].addChild(docconfig);
@@ -123,14 +124,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
     //
     @action
-    public AddRightSplit = (document: Doc, minimize: boolean = false) => {
+    public AddRightSplit = (document: Doc, dataDoc: 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)]
+            content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)]
         };
 
         var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
@@ -160,12 +161,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         return newContentItem;
     }
     @action
-    public AddTab = (stack: any, document: Doc) => {
+    public AddTab = (stack: any, document: Doc, dataDocument: Doc) => {
         let docs = Cast(this.props.Document.data, listSpec(Doc));
         if (docs) {
             docs.push(document);
         }
-        let docContentConfig = CollectionDockingView.makeDocumentConfig(document);
+        let docContentConfig = CollectionDockingView.makeDocumentConfig(document, dataDocument);
         var newContentItem = stack.layoutManager.createContentItem(docContentConfig, this._goldenLayout);
         stack.addChild(newContentItem.contentItems[0], undefined);
         this.layoutChanged();
@@ -281,14 +282,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 let x = e.clientX;
                 let y = e.clientY;
                 let docid = (e.target as any).DashDocId;
+                let datadocid = (e.target as any).DashDataDocId;
                 let tab = (e.target as any).parentElement as HTMLElement;
                 let glTab = (e.target as any).Tab;
                 if (glTab && glTab.contentItem && glTab.contentItem.parent) {
                     glTab.contentItem.parent.setActiveContentItem(glTab.contentItem);
                 }
-                DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
+                DocServer.GetRefField(docid).then(action(async (f: Opt<Field>) => {
                     if (f instanceof Doc) {
-                        DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y,
+                        let dataDoc = (datadocid !== docid) ? await DocServer.GetRefField(datadocid) : f;
+                        DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f], [dataDoc instanceof Doc ? dataDoc : f]), x, y,
                             {
                                 handlers: {
                                     dragComplete: emptyFunction,
@@ -338,33 +341,34 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             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(`<span class="messageCounter">0</div>`);
-                    tab.element.append(counter);
-                    let upDiv = document.createElement("span");
-                    const stack = tab.contentItem.parent;
-                    // shifts the focus to this tab when another tab is dragged over it
-                    tab.element[0].onmouseenter = (e: any) => {
-                        if (!this._isPointerDown) return;
-                        var activeContentItem = tab.header.parent.getActiveContentItem();
-                        if (tab.contentItem !== activeContentItem) {
-                            tab.header.parent.setActiveContentItem(tab.contentItem);
-                        }
-                        tab.setActive(true);
-                    };
-                    ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv);
-                    tab.reactComponents = [upDiv];
-                    tab.element.append(upDiv);
-                    counter.DashDocId = tab.contentItem.config.props.documentId;
-                    tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title],
-                        () => {
-                            counter.innerHTML = DocListCast(doc.linkedFromDocs).length + DocListCast(doc.linkedToDocs).length;
-                            tab.titleElement[0].textContent = doc.title;
-                        }, { fireImmediately: true });
-                    tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
-                }
-            });
+            let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId) as Doc;
+            let dataDoc = await DocServer.GetRefField(tab.contentItem.config.props.dataDocumentId) as Doc;
+            if (doc instanceof Doc && dataDoc instanceof Doc) {
+                let counter: any = this.htmlToElement(`<span class="messageCounter">0</div>`);
+                tab.element.append(counter);
+                let upDiv = document.createElement("span");
+                const stack = tab.contentItem.parent;
+                // shifts the focus to this tab when another tab is dragged over it
+                tab.element[0].onmouseenter = (e: any) => {
+                    if (!this._isPointerDown) return;
+                    var activeContentItem = tab.header.parent.getActiveContentItem();
+                    if (tab.contentItem !== activeContentItem) {
+                        tab.header.parent.setActiveContentItem(tab.contentItem);
+                    }
+                    tab.setActive(true);
+                };
+                ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, dataDoc, location) => CollectionDockingView.Instance.AddTab(stack, doc, dataDoc)} />, upDiv);
+                tab.reactComponents = [upDiv];
+                tab.element.append(upDiv);
+                counter.DashDocId = tab.contentItem.config.props.documentId;
+                counter.DashDataDocId = tab.contentItem.config.props.dataDocumentId;
+                tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title],
+                    () => {
+                        counter.innerHTML = DocListCast(doc.linkedFromDocs).length + DocListCast(doc.linkedToDocs).length;
+                        tab.titleElement[0].textContent = doc.title;
+                    }, { fireImmediately: true });
+                tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
+            }
         }
         tab.titleElement[0].Tab = tab;
         tab.closeElement.off('click') //unbind the current click handler
@@ -427,6 +431,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
 interface DockedFrameProps {
     documentId: FieldId;
+    dataDocumentId: FieldId;
     //collectionDockingView: CollectionDockingView
 }
 @observer
@@ -435,6 +440,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
+    @observable private _dataDoc: Opt<Doc>;
     get _stack(): any {
         let parent = (this.props as any).glContainer.parent.parent;
         if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) {
@@ -444,7 +450,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
     constructor(props: any) {
         super(props);
-        DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
+        DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => {
+            this._document = f as Doc;
+            if (!this.props.dataDocumentId || this.props.documentId === this.props.dataDocumentId) this._dataDoc = this._document;
+        }));
+        if (this.props.dataDocumentId && this.props.documentId !== this.props.dataDocumentId) DocServer.GetRefField(this.props.dataDocumentId).then(action((f: Opt<Field>) => this._dataDoc = f as Doc));
     }
 
     nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
@@ -481,17 +491,17 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
     get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
 
-    addDocTab = (doc: Doc, location: string) => {
+    addDocTab = (doc: Doc, dataDoc: Doc, location: string) => {
         if (doc.dockingConfig) {
             MainView.Instance.openWorkspace(doc);
         } else if (location === "onRight") {
-            CollectionDockingView.Instance.AddRightSplit(doc);
+            CollectionDockingView.Instance.AddRightSplit(doc, dataDoc);
         } else {
-            CollectionDockingView.Instance.AddTab(this._stack, doc);
+            CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc);
         }
     }
     get content() {
-        if (!this._document) {
+        if (!this._document || !this._dataDoc) {
             return (null);
         }
         return (
@@ -499,7 +509,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier}, ${this.scaleToFitMultiplier})` }}>
                 <DocumentView key={this._document[Id]}
                     Document={this._document}
-                    DataDoc={this._document}
+                    DataDoc={this._dataDoc}
                     bringToFront={emptyFunction}
                     addDocument={undefined}
                     removeDocument={undefined}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index d539835b9..9f5cef3cd 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -394,7 +394,7 @@ interface CollectionSchemaPreviewProps {
     removeDocument: (document: Doc) => boolean;
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
-    addDocTab: (document: Doc, where: string) => void;
+    addDocTab: (document: Doc, dataDoc: Doc, where: string) => void;
     setPreviewScript: (script: string) => void;
     previewScript?: string;
 }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 18d6751c6..83a7c9e3a 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -26,6 +26,8 @@ import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import React = require("react");
 import { FormattedTextBox } from '../nodes/FormattedTextBox';
+import { ImageField } from '../../../new_fields/URLField';
+import { ImageBox } from '../nodes/ImageBox';
 
 
 export interface TreeViewProps {
@@ -35,7 +37,7 @@ export interface TreeViewProps {
     deleteDoc: (doc: Doc) => boolean;
     moveDocument: DragManager.MoveFunction;
     dropAction: "alias" | "copy" | undefined;
-    addDocTab: (doc: Doc, where: string) => void;
+    addDocTab: (doc: Doc, dataDoc: Doc, where: string) => void;
     panelWidth: () => number;
     panelHeight: () => number;
     addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
@@ -73,7 +75,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     @undoBatch delete = () => this.props.deleteDoc(this.props.document);
-    @undoBatch openRight = async () => this.props.addDocTab(this.props.document, "onRight");
+    @undoBatch openRight = async () => this.props.addDocTab(this.props.document, this.props.dataDoc, "onRight");
 
     onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
     onPointerEnter = (e: React.PointerEvent): void => {
@@ -148,6 +150,25 @@ class TreeView extends React.Component<TreeViewProps> {
         SetValue={(value: string) => {
             let res = (Doc.GetProto(this.props.document)[key] = value) ? true : true;
 
+            if (value.startsWith(">>")) {
+                let metaKey = value.slice(2, value.length);
+                let collection = this.props.containingCollection;
+                Doc.GetProto(collection)[metaKey] = new ImageField("http://www.cs.brown.edu/~bcz/face.gif");
+                let template = Doc.MakeAlias(collection);
+                template.title = metaKey;
+                template.embed = true;
+                template.layout = ImageBox.LayoutString(metaKey);
+                template.x = 0;
+                template.y = 0;
+                template.nativeWidth = 300;
+                template.nativeHeight = 300;
+                template.width = 300;
+                template.height = 300;
+                template.isTemplate = true;
+                template.templates = new List<string>([Templates.TitleBar(metaKey)]);//`{props.DataDoc.${metaKey}_text}`)]);
+                Doc.AddDocToList(collection, "data", template);
+                this.delete();
+            }
             if (value.startsWith(">")) {
                 let metaKey = value.slice(1, value.length);
                 let collection = this.props.containingCollection;
@@ -236,10 +257,10 @@ class TreeView extends React.Component<TreeViewProps> {
     onWorkspaceContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.props.document)) });
-            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" });
+            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, kvp, "onRight"); }, icon: "layer-group" });
             if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
-                ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, "inTab"), icon: "folder" });
-                ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight"), icon: "caret-square-right" });
+                ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.props.dataDoc, "inTab"), icon: "folder" });
+                ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.props.dataDoc, "onRight"), icon: "caret-square-right" });
                 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)) });
                 }
@@ -339,7 +360,7 @@ class TreeView extends React.Component<TreeViewProps> {
         remove: ((doc: Doc) => boolean),
         move: DragManager.MoveFunction,
         dropAction: dropActionType,
-        addDocTab: (doc: Doc, where: string) => void,
+        addDocTab: (doc: Doc, dataDoc: Doc, where: string) => void,
         screenToLocalXf: () => Transform,
         outerXf: () => { translateX: number, translateY: number },
         active: () => boolean,
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 89e5ad74c..10e6fb885 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -18,6 +18,7 @@ import { Doc } from '../../../new_fields/Doc';
 import { FormattedTextBox } from '../nodes/FormattedTextBox';
 import { Docs } from '../../documents/Documents';
 import { List } from '../../../new_fields/List';
+import { ImageField } from '../../../new_fields/URLField';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
@@ -63,8 +64,9 @@ export class CollectionView extends React.Component<FieldViewProps> {
                     let otherdoc = Docs.TextDocument({ width: 100, height: 50, title: "applied template" });
                     Doc.GetProto(otherdoc).description = "THIS DESCRIPTION IS REALLY IMPORTANT!";
                     Doc.GetProto(otherdoc).summary = "THIS SUMMARY IS MEANINGFUL!";
+                    Doc.GetProto(otherdoc).photo = new ImageField("http://www.cs.brown.edu/~bcz/snowbeast.JPG");
                     Doc.GetProto(otherdoc).layout = Doc.MakeDelegate(this.props.Document);
-                    this.props.addDocTab && this.props.addDocTab(otherdoc, "onRight");
+                    this.props.addDocTab && this.props.addDocTab(otherdoc, otherdoc, "onRight");
                 }), icon: "project-diagram"
             });
         }
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index f11af04a3..fa7058d5a 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -9,7 +9,7 @@ import { CollectionDockingView } from "./CollectionDockingView";
 import { NumCast } from "../../../new_fields/Types";
 import { CollectionViewType } from "./CollectionBaseView";
 
-type SelectorProps = { Document: Doc, addDocTab(doc: Doc, location: string): void };
+type SelectorProps = { Document: Doc, addDocTab(doc: Doc, dataDoc: Doc, location: string): void };
 @observer
 export class SelectorContextMenu extends React.Component<SelectorProps> {
     @observable private _docs: { col: Doc, target: Doc }[] = [];
@@ -43,7 +43,7 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
                 col.panX = newPanX;
                 col.panY = newPanY;
             }
-            this.props.addDocTab(col, "inTab");
+            this.props.addDocTab(col, col, "inTab"); // bcz: dataDoc?
         };
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index da50d0b53..08f58dcd6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -81,7 +81,7 @@ export interface DocumentViewProps {
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     bringToFront: (doc: Doc) => void;
-    addDocTab: (doc: Doc, where: string) => void;
+    addDocTab: (doc: Doc, dataDoc: Doc, where: string) => void;
     animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void;
 }
 
@@ -212,8 +212,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) {
         if (this._mainCont.current) {
             let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
+            let alldataConnected = [this.props.DataDoc, ...(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);
+            let dragData = new DragManager.DocumentDragData(allConnected, alldataConnected);
             const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.xOffset = xoff;
@@ -268,7 +269,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let altKey = e.altKey;
         let ctrlKey = e.ctrlKey;
         if (this._doubleTap && !this.props.isTopMost) {
-            this.props.addDocTab(this.props.Document, "inTab");
+            this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab");
             SelectionManager.DeselectAll();
             this.props.Document.libraryBrush = false;
         }
@@ -311,7 +312,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         if (dataDocs) {
                             expandedDocs.forEach(maxDoc =>
                                 (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) &&
-                                    this.props.addDocTab(getDispDoc(maxDoc), maxLocation)));
+                                    this.props.addDocTab(getDispDoc(maxDoc), getDispDoc(maxDoc), maxLocation)));
                         }
                     } else {
                         let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2);
@@ -334,7 +335,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     if (!linkedFwdDocs.some(l => l instanceof Promise)) {
                         let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab");
                         let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
-                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0], targetContext);
+                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, document, maxLocation), linkedFwdPage[altKey ? 1 : 0], targetContext);
                     }
                 }
             }
@@ -345,7 +346,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._downY = e.clientY;
         this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
         if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) {
-            CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
+            CollectionDockingView.Instance.StartOtherDrag(e, [Doc.MakeAlias(this.props.Document)], [this.props.DataDoc]);
             e.stopPropagation();
         } else {
             if (this.active) e.stopPropagation(); // events stop at the lowest document that is active.  
@@ -376,7 +377,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); };
-    fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight"); };
+    fieldsClicked = (): void => { let kvp = Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }); this.props.addDocTab(kvp, kvp, "onRight"); };
     makeBtnClicked = (): void => {
         let doc = Doc.GetProto(this.props.Document);
         doc.isButton = !BoolCast(doc.isButton, false);
@@ -390,7 +391,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
     fullScreenClicked = (): void => {
-        CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false));
+        CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(Doc.MakeAlias(this.props.Document), this.props.DataDoc);
         SelectionManager.DeselectAll();
     }
 
@@ -473,10 +474,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const cm = ContextMenu.Instance;
         let subitems: ContextMenuProps[] = [];
         subitems.push({ description: "Open Full Screen", event: this.fullScreenClicked, icon: "desktop" });
-        subitems.push({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" });
-        subitems.push({ description: "Open Tab Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), "inTab"), icon: "folder" });
-        subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "onRight"), icon: "caret-square-right" });
-        subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "caret-square-right" });
+        subitems.push({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab"), icon: "folder" });
+        subitems.push({ description: "Open Tab Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "inTab"), icon: "folder" });
+        subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight"), icon: "caret-square-right" });
+        subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "onRight"), icon: "caret-square-right" });
         subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
         cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
         cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "edit" });
@@ -486,7 +487,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({
             description: "Find aliases", event: async () => {
                 const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
-                this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), "onRight");
+                this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), Docs.SchemaDocument(["title"], aliases, {}), "onRight"); // bcz: dataDoc?
             }, icon: "search"
         });
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document), icon: "crosshairs" });
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 35608af86..8879da55f 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -36,7 +36,7 @@ export interface FieldViewProps {
     isTopMost: boolean;
     selectOnLoad: boolean;
     addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
-    addDocTab: (document: Doc, where: string) => void;
+    addDocTab: (document: Doc, dataDoc: Doc, where: string) => void;
     removeDocument?: (document: Doc) => boolean;
     moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f59af226d..39444fd6a 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -270,7 +270,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
                     if (this._linkClicked) {
                         DocServer.GetRefField(this._linkClicked).then(f => {
-                            (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab"));
+                            (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, document, "inTab"));
                         });
                         e.stopPropagation();
                         e.preventDefault();
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f56a2d926..241a593b0 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,13 +1,13 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faImage } from '@fortawesome/free-solid-svg-icons';
-import { action, observable } from 'mobx';
+import { action, observable, computed } from 'mobx';
 import { observer } from "mobx-react";
 import Lightbox from 'react-image-lightbox';
 import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
 import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc';
 import { List } from '../../../new_fields/List';
 import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
-import { Cast, FieldValue, NumCast, StrCast } from '../../../new_fields/Types';
+import { Cast, FieldValue, NumCast, StrCast, BoolCast } from '../../../new_fields/Types';
 import { ImageField } from '../../../new_fields/URLField';
 import { Utils } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
@@ -36,7 +36,7 @@ const ImageDocument = makeInterface(pageSchema, positionSchema);
 @observer
 export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) {
 
-    public static LayoutString() { return FieldView.LayoutString(ImageBox); }
+    public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
     private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
     private _downX: number = 0;
     private _downY: number = 0;
@@ -46,6 +46,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     private dropDisposer?: DragManager.DragDropDisposer;
 
 
+    @computed get dataDoc() { return this.props.DataDoc && BoolCast(this.props.Document.isTemplate) ? this.props.DataDoc : this.props.Document; }
+
 
     protected createDropTarget = (ele: HTMLDivElement) => {
         if (this.dropDisposer) {
@@ -66,19 +68,24 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.DocumentDragData) {
             de.data.droppedDocuments.forEach(action((drop: Doc) => {
-                let layout = StrCast(drop.backgroundLayout);
-                if (layout.indexOf(ImageBox.name) !== -1) {
-                    let imgData = this.props.Document[this.props.fieldKey];
-                    if (imgData instanceof ImageField) {
-                        Doc.SetOnPrototype(this.props.Document, "data", new List([imgData]));
-                    }
-                    let imgList = Cast(this.props.Document[this.props.fieldKey], listSpec(ImageField), [] as any[]);
-                    if (imgList) {
-                        let field = drop.data;
-                        if (field instanceof ImageField) imgList.push(field);
-                        else if (field instanceof List) imgList.concat(field);
-                    }
+                if (this.dataDoc !== this.props.Document && drop.data instanceof ImageField) {
+                    this.dataDoc[this.props.fieldKey] = new ImageField(drop.data.url);
                     e.stopPropagation();
+                } else {
+                    let layout = StrCast(drop.backgroundLayout);
+                    if (layout.indexOf(ImageBox.name) !== -1) {
+                        let imgData = this.dataDoc[this.props.fieldKey];
+                        if (imgData instanceof ImageField) {
+                            Doc.SetOnPrototype(this.dataDoc, this.props.fieldKey, new List([imgData]));
+                        }
+                        let imgList = Cast(this.dataDoc[this.props.fieldKey], listSpec(ImageField), [] as any[]);
+                        if (imgList) {
+                            let field = drop.data;
+                            if (field instanceof ImageField) imgList.push(field);
+                            else if (field instanceof List) imgList.concat(field);
+                        }
+                        e.stopPropagation();
+                    }
                 }
             }));
             // de.data.removeDocument()  bcz: need to implement
@@ -206,7 +213,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         let paths: string[] = ["http://www.cs.brown.edu/~bcz/noImage.png"];
         // this._curSuffix = "";
         // if (w > 20) {
-        let field = this.Document[this.props.fieldKey];
+        let field = this.dataDoc[this.props.fieldKey];
         // if (w < 100 && this._smallRetryCount < 10) this._curSuffix = "_s";
         // else if (w < 600 && this._mediumRetryCount < 10) this._curSuffix = "_m";
         // else if (this._largeRetryCount < 10) this._curSuffix = "_l";
@@ -214,8 +221,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => this.choosePath((p as ImageField).url));
         // }
         let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
-        let rotation = NumCast(this.props.Document.rotation, 0);
-        let aspect = (rotation % 180) ? this.props.Document[HeightSym]() / this.props.Document[WidthSym]() : 1;
+        let rotation = NumCast(this.dataDoc.rotation, 0);
+        let aspect = (rotation % 180) ? this.dataDoc[HeightSym]() / this.dataDoc[WidthSym]() : 1;
         let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
         return (
             <div id={id} className={`imageBox-cont${interactive}`} style={{ background: "transparent" }}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 6adead626..7000352e7 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -203,7 +203,6 @@ class Viewer extends React.Component<IViewerProps> {
             this._isPage[page] = "page";
             this._visibleElements[page] = (
                 <Page
-                    size={this._pageSizes[page]}
                     pdf={this.props.pdf}
                     page={page}
                     numPages={this.props.pdf.numPages}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 9bacf49ba..27b3473f8 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -166,6 +166,14 @@ export namespace Doc {
     export function IsPrototype(doc: Doc) {
         return GetT(doc, "isPrototype", "boolean", true);
     }
+    export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
+        let hasProto = doc.proto instanceof Doc;
+        let onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
+        let onProto = Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1;
+        if (onDeleg || !hasProto || (!onProto && !defaultProto))
+            doc[key] = value;
+        else doc.proto![key] = value;
+    }
     export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
         const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc;
 
-- 
cgit v1.2.3-70-g09d2


From fe48c1ecea514b583c7cfa3567b0d674e0ab156e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 22 Jun 2019 16:25:25 -0400
Subject: fixed template issues w/ tree view opening in tabs

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

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 89fadab57..b403000ad 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -451,10 +451,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     constructor(props: any) {
         super(props);
         DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => {
-            this._document = f as Doc;
-            if (!this.props.dataDocumentId || this.props.documentId === this.props.dataDocumentId) this._dataDoc = this._document;
+            this._dataDoc = this._document = f as Doc;
+            if (this.props.dataDocumentId && this.props.documentId !== this.props.dataDocumentId) {
+                DocServer.GetRefField(this.props.dataDocumentId).then(action((f: Opt<Field>) => this._dataDoc = f as Doc));
+            }
         }));
-        if (this.props.dataDocumentId && this.props.documentId !== this.props.dataDocumentId) DocServer.GetRefField(this.props.dataDocumentId).then(action((f: Opt<Field>) => this._dataDoc = f as Doc));
     }
 
     nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
@@ -506,7 +507,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
         return (
             <div className="collectionDockingView-content" ref={this._mainCont}
-                style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier}, ${this.scaleToFitMultiplier})` }}>
+                style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier})` }}>
                 <DocumentView key={this._document[Id]}
                     Document={this._document}
                     DataDoc={this._dataDoc}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 4c877b3fa..8d7ebb97d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -88,7 +88,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     @undoBatch delete = () => this.props.deleteDoc(this.dataDoc);
-    @undoBatch openRight = async () => this.props.addDocTab(this.props.document, this.props.dataDoc, "onRight");
+    @undoBatch openRight = async () => this.props.addDocTab(this.props.document, this.props.document, "onRight");
 
     onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
     onPointerEnter = (e: React.PointerEvent): void => {
@@ -293,8 +293,8 @@ class TreeView extends React.Component<TreeViewProps> {
             ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.dataDoc)) });
             ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.KVPDocument(this.dataDoc, { width: 300, height: 300 }); this.props.addDocTab(kvp, kvp, "onRight"); }, icon: "layer-group" });
             if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
-                ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.props.dataDoc, "inTab"), icon: "folder" });
-                ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.props.dataDoc, "onRight"), icon: "caret-square-right" });
+                ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.props.document, "inTab"), icon: "folder" });
+                ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.props.document, "onRight"), icon: "caret-square-right" });
                 if (DocumentManager.Instance.getDocumentViews(this.dataDoc).length) {
                     ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.dataDoc).map(view => view.props.focus(this.props.document)) });
                 }
-- 
cgit v1.2.3-70-g09d2


From 6e5cd0e991e2e6d7ae8de1d73ff273ba0737355c Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tschicke@gmail.com>
Date: Sun, 23 Jun 2019 17:23:33 -0400
Subject: Fixed shift dragging with no open panes

---
 src/client/views/collections/CollectionDockingView.tsx | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index de5c66c0b..5f8862c43 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -135,10 +135,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
         var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
 
-        if (this._goldenLayout.root.contentItems[0].isRow) {
+        if (this._goldenLayout.root.contentItems.length === 0) {
+            this._goldenLayout.root.addChild(newContentItem);
+        } else if (this._goldenLayout.root.contentItems[0].isRow) {
             this._goldenLayout.root.contentItems[0].addChild(newContentItem);
-        }
-        else {
+        } else {
             var collayout = this._goldenLayout.root.contentItems[0];
             var newRow = collayout.layoutManager.createContentItem({ type: "row" }, this._goldenLayout);
             collayout.parent.replaceChild(collayout, newRow);
-- 
cgit v1.2.3-70-g09d2


From 219cabb3fe42ab199550efc3423b7aaed4e1ee93 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 24 Jun 2019 22:45:19 -0400
Subject: Switched shift drag of tabs to normal drag and added drag target for
 document drag

---
 src/client/util/DragManager.ts                     |  4 ++-
 .../views/collections/CollectionDockingView.tsx    | 41 +++++++++++++++-------
 2 files changed, 32 insertions(+), 13 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index c3c92daa5..b707dbe57 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -90,6 +90,8 @@ export namespace DragManager {
         handlers: DragHandlers;
 
         hideSource: boolean | (() => boolean);
+
+        withoutShiftDrag?: boolean;
     }
 
     export interface DragDropDisposer {
@@ -312,7 +314,7 @@ export namespace DragManager {
             if (dragData instanceof DocumentDragData) {
                 dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
             }
-            if (e.shiftKey && CollectionDockingView.Instance) {
+            if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) {
                 AbortDrag();
                 CollectionDockingView.Instance.StartOtherDrag(docs, {
                     pageX: e.pageX,
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 5f8862c43..f1473139c 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -24,6 +24,10 @@ import { SubCollectionViewProps } from "./CollectionSubView";
 import { ParentDocSelector } from './ParentDocumentSelector';
 import React = require("react");
 import { MainView } from '../MainView';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faFile } from '@fortawesome/free-solid-svg-icons';
+library.add(faFile);
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -261,9 +265,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     onPointerDown = (e: React.PointerEvent): void => {
         this._isPointerDown = true;
         let onPointerUp = action(() => {
-            window.removeEventListener("pointerup", onPointerUp)
-            this._isPointerDown = false
-        })
+            window.removeEventListener("pointerup", onPointerUp);
+            this._isPointerDown = false;
+        });
         window.addEventListener("pointerup", onPointerUp);
         var className = (e.target as any).className;
         if (className === "messageCounter") {
@@ -276,7 +280,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             DocServer.GetRefField(docid).then(action(async (sourceDoc: Opt<Field>) =>
                 (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
         } else
-            if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) {
+            if ((className === "lm_title" || className === "lm_tab lm_active") && e.shiftKey) {
                 e.stopPropagation();
                 e.preventDefault();
                 let x = e.clientX;
@@ -294,7 +298,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                                 handlers: {
                                     dragComplete: emptyFunction,
                                 },
-                                hideSource: false
+                                hideSource: false,
+                                withoutShiftDrag: true
                             });
                     }
                 }));
@@ -341,8 +346,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             }
             DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async doc => {
                 if (doc instanceof Doc) {
-                    let counter: any = this.htmlToElement(`<span class="messageCounter">0</div>`);
-                    tab.element.append(counter);
+                    let dragSpan = document.createElement("span");
+                    dragSpan.style.position = "relative";
+                    dragSpan.style.bottom = "6px";
+                    dragSpan.style.paddingLeft = "4px";
+                    dragSpan.style.paddingRight = "2px";
                     let upDiv = document.createElement("span");
                     const stack = tab.contentItem.parent;
                     // shifts the focus to this tab when another tab is dragged over it
@@ -354,15 +362,24 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         }
                         tab.setActive(true);
                     };
-                    ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv);
-                    tab.reactComponents = [upDiv];
+                    ReactDOM.render(<span onPointerDown={
+                        e => {
+                            e.preventDefault();
+                            e.stopPropagation();
+                            DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
+                                handlers: { dragComplete: emptyFunction },
+                                hideSource: false
+                            });
+                        }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
+                    ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={doc => CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv);
+                    tab.reactComponents = [dragSpan, upDiv];
+                    tab.element.append(dragSpan);
                     tab.element.append(upDiv);
-                    counter.DashDocId = tab.contentItem.config.props.documentId;
-                    tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title],
+                    tab.reactionDisposer = reaction(() => [doc.title],
                         () => {
-                            counter.innerHTML = DocListCast(doc.linkedFromDocs).length + DocListCast(doc.linkedToDocs).length;
                             tab.titleElement[0].textContent = doc.title;
                         }, { fireImmediately: true });
+                    //TODO why can't this just be doc instead of the id?
                     tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
                 }
             });
-- 
cgit v1.2.3-70-g09d2


From 45ded4da2592bc2a8201e1260dfb6a80485684c7 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 25 Jun 2019 09:35:09 -0400
Subject: converted isTopMost into renderDepth and capped renderDepth at 7. 
 removed previous cycle detection.

---
 src/client/documents/Documents.ts                  |  6 +--
 src/client/util/DragManager.ts                     |  2 -
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/MainOverlayTextBox.tsx            |  2 +-
 src/client/views/MainView.tsx                      |  2 +-
 .../views/collections/CollectionBaseView.tsx       | 48 ++++------------------
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 .../views/collections/CollectionSchemaView.tsx     |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  8 ++--
 src/client/views/nodes/DocumentContentsView.tsx    |  1 +
 src/client/views/nodes/DocumentView.tsx            | 11 ++---
 src/client/views/nodes/FieldView.tsx               |  4 +-
 src/client/views/nodes/KeyValuePair.tsx            |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 14 files changed, 31 insertions(+), 65 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b04fc401a..ac8a892b8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -344,7 +344,7 @@ export namespace Docs {
         {layout}
     </div>
     <div style="height:(100% + 25px); width:100%; position:absolute">
-        <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+        <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} renderDepth={renderDepth}/>
     </div>
 </div>       
         `);
@@ -356,7 +356,7 @@ export namespace Docs {
             {layout}
         </div>
         <div style="height:25px; width:100%; position:absolute">
-            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} renderDepth={renderDepth}/>
         </div>
     </div>       
             `);
@@ -379,7 +379,7 @@ export namespace Docs {
             {layout}
         </div>
         <div style="height:15%; width:100%; position:absolute">
-            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} renderDepth={renderDepth}/>
         </div>
     </div>       
             `);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 3143924fc..442187912 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -28,7 +28,6 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
         document.removeEventListener('pointerup', onRowUp);
     };
     let onItemDown = async (e: React.PointerEvent) => {
-        // if (this.props.isSelected() || this.props.isTopMost) {
         if (e.button === 0) {
             e.stopPropagation();
             if (e.shiftKey && CollectionDockingView.Instance) {
@@ -38,7 +37,6 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
                 document.addEventListener("pointerup", onRowUp);
             }
         }
-        //}
     };
     return onItemDown;
 }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9d5844426..042306997 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -153,7 +153,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @computed
     get Bounds(): { x: number, y: number, b: number, r: number } {
         return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
-            if (documentView.props.isTopMost) {
+            if (documentView.props.renderDepth === 0) {
                 return bounds;
             }
             let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index b2cfc82c4..7363b08ef 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -123,7 +123,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                         <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true}
                             Document={FormattedTextBox.InputBoxOverlay.props.Document}
                             DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
-                            isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true}
+                            isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true}
                             ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
                             ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={(dominus: HTMLElement) => this._dominus = dominus} />
                     </div>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a72f25b99..d26e24748 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -211,7 +211,7 @@ export class MainView extends React.Component {
                 ContentScaling={returnOne}
                 PanelWidth={this.getPWidth}
                 PanelHeight={this.getPHeight}
-                isTopMost={true}
+                renderDepth={0}
                 selectOnLoad={false}
                 focus={emptyFunction}
                 parentActive={returnTrue}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 79a9f3be0..50d1a5071 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -64,8 +64,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     active = (): boolean => {
         var isSelected = this.props.isSelected();
-        var topMost = this.props.isTopMost;
-        return isSelected || this._isChildActive || topMost;
+        return isSelected || this._isChildActive || this.props.renderDepth === 0;
     }
 
     //TODO should this be observable?
@@ -75,32 +74,6 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         this.props.whenActiveChanged(isActive);
     }
 
-    createsCycle(documentToAdd: Doc, containerDocument: Doc): boolean {
-        if (!(documentToAdd instanceof Doc)) {
-            return false;
-        }
-        if (StrCast(documentToAdd.layout).indexOf("CollectionView") !== -1) {
-            let data = DocListCast(documentToAdd.data);
-            for (const doc of data) {
-                if (this.createsCycle(doc, containerDocument)) {
-                    return true;
-                }
-            }
-        }
-        let annots = DocListCast(documentToAdd.annotations);
-        for (const annot of annots) {
-            if (this.createsCycle(annot, containerDocument)) {
-                return true;
-            }
-        }
-        for (let containerProto: Opt<Doc> = containerDocument; containerProto !== undefined; containerProto = FieldValue(containerProto.proto)) {
-            if (containerProto[Id] === documentToAdd[Id]) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     @action.bound
     addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
         var curPage = NumCast(this.props.Document.curPage, -1);
@@ -109,19 +82,16 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             Doc.GetProto(doc).annotationOn = this.props.Document;
         }
         allowDuplicates = true;
-        if (!this.createsCycle(doc, this.dataDoc)) {
-            //TODO This won't create the field if it doesn't already exist
-            const value = Cast(this.dataDoc[this.props.fieldKey], listSpec(Doc));
-            if (value !== undefined) {
-                if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) {
-                    value.push(doc);
-                }
-            } else {
-                Doc.SetOnPrototype(this.dataDoc, this.props.fieldKey, new List([doc]));
+        //TODO This won't create the field if it doesn't already exist
+        const value = Cast(this.dataDoc[this.props.fieldKey], listSpec(Doc));
+        if (value !== undefined) {
+            if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) {
+                value.push(doc);
             }
-            return true;
+        } else {
+            Doc.SetOnPrototype(this.dataDoc, this.props.fieldKey, new List([doc]));
         }
-        return false;
+        return true;
     }
 
     @action.bound
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 82cb5dbbb..6d2f61173 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -535,7 +535,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                     PanelWidth={this.nativeWidth}
                     PanelHeight={this.nativeHeight}
                     ScreenToLocalTransform={this.ScreenToLocalTransform}
-                    isTopMost={true}
+                    renderDepth={0}
                     selectOnLoad={false}
                     parentActive={returnTrue}
                     whenActiveChanged={emptyFunction}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b4158a5b1..f03afe7e0 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -104,7 +104,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             ContainingCollectionView: this.props.CollectionView,
             isSelected: returnFalse,
             select: emptyFunction,
-            isTopMost: false,
+            renderDepth: this.props.renderDepth + 1,
             selectOnLoad: false,
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyFunction,
@@ -454,7 +454,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                 <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.centeringOffset}px, 0px)`, height: "100%" }}>
                     <DocumentView
                         DataDoc={this.props.DataDocument}
-                        Document={this.props.Document} isTopMost={false} selectOnLoad={false}
+                        Document={this.props.Document} renderDepth={0} selectOnLoad={false}
                         addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} moveDocument={this.props.moveDocument}
                         ScreenToLocalTransform={this.getTransform}
                         ContentScaling={this.contentScaling}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c6f003a81..05909c9f7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -199,7 +199,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             var dv = DocumentManager.Instance.getDocumentView(doc);
             return dv && SelectionManager.IsSelected(dv) ? true : false;
         });
-        if (!this.props.isSelected() && !childSelected && !this.props.isTopMost) {
+        if (!this.props.isSelected() && !childSelected && this.props.renderDepth > 0) {
             return;
         }
         e.stopPropagation();
@@ -303,7 +303,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
             ScreenToLocalTransform: this.getTransform,
-            isTopMost: false,
+            renderDepth: this.props.renderDepth + 1,
             selectOnLoad: layoutDoc[Id] === this._selectOnLoaded,
             PanelWidth: layoutDoc[WidthSym],
             PanelHeight: layoutDoc[HeightSym],
@@ -404,7 +404,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
     @computed get overlayView() {
         return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
-            isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
+            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
     render() {
         return this.overlayView;
@@ -416,7 +416,7 @@ class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps
     @computed get backgroundView() {
         let props = this.props;
         return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
-            isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
+            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
     render() {
         return this.props.Document.backgroundLayout ? this.backgroundView : (null);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index a4316c143..0da4888a1 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -103,6 +103,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     render() {
+        if (this.props.renderDepth > 7) return (null);
         if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
             components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5530d5c01..c586885a3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -72,7 +72,7 @@ export interface DocumentViewProps {
     removeDocument?: (doc: Doc) => boolean;
     moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
-    isTopMost: boolean;
+    renderDepth: number;
     ContentScaling: () => number;
     PanelWidth: () => number;
     PanelHeight: () => number;
@@ -120,7 +120,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     public get ContentDiv() { return this._mainCont.current; }
     @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
-    @computed get topMost(): boolean { return this.props.isTopMost; }
+    @computed get topMost(): boolean { return this.props.renderDepth === 0; }
     @computed get templates(): List<string> {
         let field = this.props.Document.templates;
         if (field && field instanceof List) {
@@ -268,7 +268,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         e.stopPropagation();
         let altKey = e.altKey;
         let ctrlKey = e.ctrlKey;
-        if (this._doubleTap && !this.props.isTopMost) {
+        if (this._doubleTap && !this.props.renderDepth) {
             this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab");
             SelectionManager.DeselectAll();
             this.props.Document.libraryBrush = false;
@@ -436,7 +436,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @action
     addTemplate = (template: Template) => {
         this.templates.push(template.Layout);
-        this.templates = this.templates;
     }
 
     @action
@@ -447,7 +446,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 break;
             }
         }
-        this.templates = this.templates;
     }
 
     freezeNativeDimensions = (): void => {
@@ -549,7 +547,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
         return (
-            <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
+            <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
                 style={{
                     outlineColor: "maroon",
@@ -565,7 +563,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     transform: `scale(${scaling}, ${scaling})`
                 }}
                 onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
-
                 onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
             >
                 {this.contents}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 8879da55f..374a523cb 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -33,7 +33,7 @@ export interface FieldViewProps {
     DataDoc: Doc;
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
-    isTopMost: boolean;
+    renderDepth: number;
     selectOnLoad: boolean;
     addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
     addDocTab: (document: Doc, dataDoc: Doc, where: string) => void;
@@ -97,7 +97,7 @@ export class FieldView extends React.Component<FieldViewProps> {
             //         ContentScaling={returnOne}
             //         PanelWidth={returnHundred}
             //         PanelHeight={returnHundred}
-            //         isTopMost={true} //TODO Why is this top most?
+            //         renderDepth={0} //TODO Why is renderDepth reset?
             //         selectOnLoad={false}
             //         focus={emptyFunction}
             //         isSelected={this.props.isSelected}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 3e03e7e75..3dda81db7 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -32,7 +32,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             fieldKey: this.props.keyName,
             isSelected: returnFalse,
             select: emptyFunction,
-            isTopMost: false,
+            renderDepth: 1,
             selectOnLoad: false,
             active: returnFalse,
             whenActiveChanged: emptyFunction,
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index b9e93b4da..10c66d318 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -492,7 +492,7 @@ interface IAnnotationProps {
 //                         <DocumentView Document={targetDoc}
 //                             ContainingCollectionView={undefined}
 //                             ScreenToLocalTransform={this.props.parent.props.parent.props.ScreenToLocalTransform}
-//                             isTopMost={false}
+//                             renderDepth={1} // what should renderDepth be?
 //                             ContentScaling={() => 1}
 //                             PanelWidth={() => NumCast(this.props.parent.props.parent.Document.nativeWidth)}
 //                             PanelHeight={() => NumCast(this.props.parent.props.parent.Document.nativeHeight)}
-- 
cgit v1.2.3-70-g09d2


From 41cf1e8536964764f18ab752140e484e36cbe464 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Tue, 25 Jun 2019 17:09:36 -0400
Subject: links can save

---
 src/client/documents/Documents.ts                  |  58 +---
 src/client/util/DocumentManager.ts                 |  14 +-
 src/client/util/DragManager.ts                     |  24 +-
 src/client/util/LinkManager.ts                     | 239 +++++++++++----
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/MainView.tsx                      |   7 +
 .../views/collections/CollectionDockingView.tsx    |   4 +-
 .../views/collections/CollectionTreeView.tsx       |  18 +-
 .../CollectionFreeFormLinksView.tsx                |   7 +-
 src/client/views/nodes/DocumentView.tsx            |   8 +-
 src/client/views/nodes/LinkButtonBox.scss          |  34 +--
 src/client/views/nodes/LinkButtonBox.tsx           | 126 ++++----
 src/client/views/nodes/LinkEditor.tsx              | 323 +++++++++------------
 src/client/views/nodes/LinkMenu.tsx                |   2 +-
 src/client/views/nodes/LinkMenuGroup.tsx           |   8 +-
 src/client/views/nodes/LinkMenuItem.tsx            |   6 +-
 src/new_fields/Doc.ts                              |   9 +-
 src/new_fields/LinkButtonField.ts                  |  58 ++--
 18 files changed, 503 insertions(+), 444 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 64032e096..fbd96fb66 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -35,8 +35,8 @@ import { DateField } from "../../new_fields/DateField";
 import { UndoManager } from "../util/UndoManager";
 import { RouteStore } from "../../server/RouteStore";
 import { LinkManager } from "../util/LinkManager";
-import { LinkButtonBox } from "../views/nodes/LinkButtonBox";
-import { LinkButtonField, LinkButtonData } from "../../new_fields/LinkButtonField";
+// import { LinkButtonBox } from "../views/nodes/LinkButtonBox";
+// import { LinkButtonField, LinkButtonData } from "../../new_fields/LinkButtonField";
 import { DocumentManager } from "../util/DocumentManager";
 import { Id } from "../../new_fields/FieldSymbols";
 var requestImageSize = require('request-image-size');
@@ -100,6 +100,7 @@ export namespace DocUtils {
             let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 });
             let linkDocProto = Doc.GetProto(linkDoc);
 
+            linkDocProto.context = targetContext;
             linkDocProto.title = title; //=== "" ? source.title + " to " + target.title : title;
             linkDocProto.linkDescription = description;
             linkDocProto.linkTags = tags;
@@ -111,36 +112,7 @@ export namespace DocUtils {
             linkDocProto.anchor2Page = target.curPage;
             linkDocProto.anchor2Groups = new List<Doc>([]);
 
-            linkDocProto.context = targetContext;
-
-            let sourceViews = DocumentManager.Instance.getDocumentViews(source);
-            let targetViews = DocumentManager.Instance.getDocumentViews(target);
-            sourceViews.forEach(sv => {
-                targetViews.forEach(tv => {
-
-                    // TODO: do only for when diff contexts
-                    let proxy1 = Docs.LinkButtonDocument(
-                        { sourceViewId: StrCast(sv.props.Document[Id]), targetViewId: StrCast(tv.props.Document[Id]) },
-                        { width: 200, height: 100, borderRounding: 0 });
-                    let proxy1Proto = Doc.GetProto(proxy1);
-                    proxy1Proto.sourceViewId = StrCast(sv.props.Document[Id]);
-                    proxy1Proto.targetViewId = StrCast(tv.props.Document[Id]);
-                    proxy1Proto.isLinkButton = true;
-
-                    let proxy2 = Docs.LinkButtonDocument(
-                        { sourceViewId: StrCast(tv.props.Document[Id]), targetViewId: StrCast(sv.props.Document[Id]) },
-                        { width: 200, height: 100, borderRounding: 0 });
-                    let proxy2Proto = Doc.GetProto(proxy2);
-                    proxy2Proto.sourceViewId = StrCast(tv.props.Document[Id]);
-                    proxy2Proto.targetViewId = StrCast(sv.props.Document[Id]);
-                    proxy2Proto.isLinkButton = true;
-
-                    LinkManager.Instance.linkProxies.push(proxy1);
-                    LinkManager.Instance.linkProxies.push(proxy2);
-                });
-            });
-
-            LinkManager.Instance.allLinks.push(linkDoc);
+            LinkManager.Instance.addLink(linkDoc);
 
             return linkDoc;
         }, "make link");
@@ -160,7 +132,7 @@ export namespace Docs {
     let audioProto: Doc;
     let pdfProto: Doc;
     let iconProto: Doc;
-    let linkProto: Doc;
+    // let linkProto: Doc;
     const textProtoId = "textProto";
     const histoProtoId = "histoProto";
     const pdfProtoId = "pdfProto";
@@ -171,7 +143,7 @@ export namespace Docs {
     const videoProtoId = "videoProto";
     const audioProtoId = "audioProto";
     const iconProtoId = "iconProto";
-    const linkProtoId = "linkProto";
+    // const linkProtoId = "linkProto";
 
     export function initProtos(): Promise<void> {
         return DocServer.GetRefFields([textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId]).then(fields => {
@@ -185,7 +157,7 @@ export namespace Docs {
             audioProto = fields[audioProtoId] as Doc || CreateAudioPrototype();
             pdfProto = fields[pdfProtoId] as Doc || CreatePdfPrototype();
             iconProto = fields[iconProtoId] as Doc || CreateIconPrototype();
-            linkProto = fields[linkProtoId] as Doc || CreateLinkPrototype();
+            // linkProto = fields[linkProtoId] as Doc || CreateLinkPrototype();
         });
     }
 
@@ -218,11 +190,11 @@ export namespace Docs {
             { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) });
         return iconProto;
     }
-    function CreateLinkPrototype(): Doc {
-        let linkProto = setupPrototypeOptions(linkProtoId, "LINK_PROTO", LinkButtonBox.LayoutString(),
-            { x: 0, y: 0, width: 300 });
-        return linkProto;
-    }
+    // function CreateLinkPrototype(): Doc {
+    //     let linkProto = setupPrototypeOptions(linkProtoId, "LINK_PROTO", LinkButtonBox.LayoutString(),
+    //         { x: 0, y: 0, width: 300 });
+    //     return linkProto;
+    // }
     function CreateTextPrototype(): Doc {
         let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
             { x: 0, y: 0, width: 300, backgroundColor: "#f1efeb" });
@@ -309,9 +281,9 @@ export namespace Docs {
     export function IconDocument(icon: string, options: DocumentOptions = {}) {
         return CreateInstance(iconProto, new IconField(icon), options);
     }
-    export function LinkButtonDocument(data: LinkButtonData, options: DocumentOptions = {}) {
-        return CreateInstance(linkProto, new LinkButtonField(data), options);
-    }
+    // export function LinkButtonDocument(data: LinkButtonData, options: DocumentOptions = {}) {
+    //     return CreateInstance(linkProto, new LinkButtonField(data), options);
+    // }
     export function PdfDocument(url: string, options: DocumentOptions = {}) {
         return CreateInstance(pdfProto, new PdfField(new URL(url)), options);
     }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 89e6183d6..767abe63f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -85,12 +85,11 @@ export class DocumentManager {
 
     @computed
     public get LinkedDocumentViews() {
-        console.log("link");
-        return DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false)).reduce((pairs, dv) => {
-            let linksList = LinkManager.Instance.findAllRelatedLinks(dv.props.Document);
+        let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false)).reduce((pairs, dv) => {
+            let linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document);
             pairs.push(...linksList.reduce((pairs, link) => {
                 if (link) {
-                    let linkToDoc = LinkManager.Instance.findOppositeAnchor(link, dv.props.Document);
+                    let linkToDoc = LinkManager.Instance.getOppositeAnchor(link, dv.props.Document);
                     DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
                         pairs.push({ a: dv, b: docView1, l: link });
                     });
@@ -100,6 +99,13 @@ export class DocumentManager {
             // }
             return pairs;
         }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
+
+        // console.log("LINKED DOCUMENT VIEWS");
+        // pairs.forEach(p => {
+        //     console.log(StrCast(p.a.Document.title), p.a.props.Document[Id], StrCast(p.b.Document.title), p.b.props.Document[Id]);
+        // });
+
+        return pairs;
     }
 
 
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index f4c8adc8e..1aacf2c53 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -46,7 +46,7 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
 }
 
 export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: number, linkDoc: Doc, sourceDoc: Doc) {
-    let draggeddoc = LinkManager.Instance.findOppositeAnchor(linkDoc, sourceDoc);
+    let draggeddoc = LinkManager.Instance.getOppositeAnchor(linkDoc, sourceDoc);
 
     let moddrag = await Cast(draggeddoc.annotationOn, Doc);
     let dragData = new DragManager.DocumentDragData(moddrag ? [moddrag] : [draggeddoc]);
@@ -66,10 +66,10 @@ export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: n
     // TODO: if not in same context then don't drag
 
     if (srcTarg) {
-        let linkDocs = LinkManager.Instance.findAllRelatedLinks(srcTarg);
+        let linkDocs = LinkManager.Instance.getAllRelatedLinks(srcTarg);
         if (linkDocs) {
             draggedDocs = linkDocs.map(link => {
-                return LinkManager.Instance.findOppositeAnchor(link, sourceDoc);
+                return LinkManager.Instance.getOppositeAnchor(link, sourceDoc);
             });
         }
     }
@@ -236,10 +236,16 @@ export namespace DragManager {
                         if (dv.props.ContainingCollectionView === SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView) {
                             return d;
                         } else {
-                            return Doc.MakeAlias(d);
+                            // return d;
+                            let r = Doc.MakeAlias(d);
+                            // DocUtils.MakeLink(sourceDoc, r);
+                            return r;
                         }
                     } else {
-                        return Doc.MakeAlias(d);
+                        // return d;
+                        let r = Doc.MakeAlias(d);
+                        // DocUtils.MakeLink(sourceDoc, r);
+                        return r;
                     }
                     // return (dv && dv.props.ContainingCollectionView !== SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView) || !dv ?
                     //     Doc.MakeAlias(d) : d;
@@ -282,10 +288,10 @@ export namespace DragManager {
         StartDrag([ele], dragData, downX, downY, options);
     }
 
-    export function StartLinkProxyDrag(ele: HTMLElement, dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
-        runInAction(() => StartDragFunctions.map(func => func()));
-        StartDrag([ele], dragData, downX, downY, options);
-    }
+    // export function StartLinkProxyDrag(ele: HTMLElement, dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
+    //     runInAction(() => StartDragFunctions.map(func => func()));
+    //     StartDrag([ele], dragData, downX, downY, options);
+    // }
 
     export let AbortDrag: () => void = emptyFunction;
 
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 745255f31..82c3a9acd 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,9 +1,10 @@
 import { observable, action } from "mobx";
-import { StrCast, Cast } from "../../new_fields/Types";
+import { StrCast, Cast, FieldValue } from "../../new_fields/Types";
 import { Doc, DocListCast } from "../../new_fields/Doc";
 import { listSpec } from "../../new_fields/Schema";
 import { List } from "../../new_fields/List";
 import { Id } from "../../new_fields/FieldSymbols";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 
 
 /* 
@@ -23,6 +24,11 @@ import { Id } from "../../new_fields/FieldSymbols";
  * - user defined kvps 
  */
 export class LinkManager {
+    // static Instance: LinkManager;
+    // private constructor() {
+    //     LinkManager.Instance = this;
+    // }
+
     private static _instance: LinkManager;
     public static get Instance(): LinkManager {
         return this._instance || (this._instance = new this());
@@ -30,25 +36,138 @@ export class LinkManager {
     private constructor() {
     }
 
-    @observable public allLinks: Array<Doc> = []; // list of link docs
-    @observable public groupMetadataKeys: Map<string, Array<string>> = new Map();
-    // map of group type to list of its metadata keys; serves as a dictionary of groups to what kind of metadata it hodls
-    @observable public linkProxies: Array<Doc> = []; // list of linkbutton docs - used to visualize link when an anchors are not in the same context
+    public get LinkManagerDoc(): Doc | undefined {
+        return FieldValue(Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc));
+    }
+    // @observable public allLinks: Array<Doc> = []; //List<Doc> = new List<Doc>([]); // list of link docs
+    // @observable public groupMetadataKeys: Map<string, Array<string>> = new Map();
+    // map of group type to list of its metadata keys; serves as a dictionary of groups to what kind of metadata it holds
+
+    public getAllLinks(): Doc[] {
+        return LinkManager.Instance.LinkManagerDoc ? LinkManager.Instance.LinkManagerDoc.allLinks ? DocListCast(LinkManager.Instance.LinkManagerDoc.allLinks) : [] : [];
+    }
+
+    public addLink(linkDoc: Doc): boolean {
+        let linkList = LinkManager.Instance.getAllLinks();
+        linkList.push(linkDoc);
+        console.log("link man doc", LinkManager.Instance.LinkManagerDoc);
+        if (LinkManager.Instance.LinkManagerDoc) {
+            LinkManager.Instance.LinkManagerDoc.allLinks = new List<Doc>(linkList);
+            return true;
+        }
+        return false;
+    }
+
+    public deleteLink(linkDoc: Doc): boolean {
+        let linkList = LinkManager.Instance.getAllLinks();
+        let index = LinkManager.Instance.getAllLinks().indexOf(linkDoc);
+        if (index > -1) {
+            linkList.splice(index, 1);
+            if (LinkManager.Instance.LinkManagerDoc) {
+                LinkManager.Instance.LinkManagerDoc.allLinks = new List<Doc>(linkList);
+                return true;
+            }
+        }
+        return false;
+    }
 
     // finds all links that contain the given anchor
-    public findAllRelatedLinks(anchor: Doc): Array<Doc> {
-        return LinkManager.Instance.allLinks.filter(link => {
+    public getAllRelatedLinks(anchor: Doc): Doc[] {//List<Doc> {
+        let related = LinkManager.Instance.getAllLinks().filter(link => {
             let protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc));
             let protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, new Doc));
-            // let idmatch1 = StrCast(anchor[Id]) === StrCast(Cast(link.anchor1, Doc, new Doc)[Id]);
-            // let idmatch2 = StrCast(anchor[Id]) === StrCast(Cast(link.anchor2, Doc, new Doc)[Id]);
-            return protomatch1 || protomatch2;// || idmatch1 || idmatch2;
+            return protomatch1 || protomatch2;
         });
+        return related;
+    }
+
+    public addGroupType(groupType: string): boolean {
+        if (LinkManager.Instance.LinkManagerDoc) {
+            LinkManager.Instance.LinkManagerDoc[groupType] = new List<string>([]);
+            let groupTypes = LinkManager.Instance.getAllGroupTypes();
+            groupTypes.push(groupType);
+            LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>(groupTypes);
+            return true;
+        }
+        return false;
+    }
+
+    // removes all group docs from all links with the given group type
+    public deleteGroupType(groupType: string): boolean {
+        if (LinkManager.Instance.LinkManagerDoc) {
+            if (LinkManager.Instance.LinkManagerDoc[groupType]) {
+                LinkManager.Instance.LinkManagerDoc[groupType] = undefined;
+                LinkManager.Instance.getAllLinks().forEach(linkDoc => {
+                    LinkManager.Instance.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor1, Doc, new Doc), groupType);
+                    LinkManager.Instance.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, new Doc), groupType);
+                });
+            }
+            return true;
+        } else return false;
+    }
+
+    public getAllGroupTypes(): string[] {
+        if (LinkManager.Instance.LinkManagerDoc) {
+            if (LinkManager.Instance.LinkManagerDoc.allGroupTypes) {
+                return Cast(LinkManager.Instance.LinkManagerDoc.allGroupTypes, listSpec("string"), []);
+            } else {
+                LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>([]);
+                return [];
+            }
+        }
+        return [];
+    }
+
+    // gets the groups associates with an anchor in a link
+    public getAnchorGroups(linkDoc: Doc, anchor: Doc): Array<Doc> {
+        if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
+            return DocListCast(linkDoc.anchor1Groups);
+        } else {
+            return DocListCast(linkDoc.anchor2Groups);
+        }
+    }
+
+    // sets the groups of the given anchor in the given link
+    public setAnchorGroups(linkDoc: Doc, anchor: Doc, groups: Doc[]) {
+        if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
+            linkDoc.anchor1Groups = new List<Doc>(groups);
+        } else {
+            linkDoc.anchor2Groups = new List<Doc>(groups);
+        }
+    }
+
+    public addGroupToAnchor(linkDoc: Doc, anchor: Doc, groupDoc: Doc, replace: boolean = false) {
+        let groups = LinkManager.Instance.getAnchorGroups(linkDoc, anchor);
+        let index = groups.findIndex(gDoc => {
+            return StrCast(groupDoc.type).toUpperCase() === StrCast(gDoc.type).toUpperCase();
+        });
+        if (index > -1 && replace) {
+            groups[index] = groupDoc;
+        }
+        if (index === -1) {
+            groups.push(groupDoc);
+        }
+        LinkManager.Instance.setAnchorGroups(linkDoc, anchor, groups);
+    }
+
+    // removes group doc of given group type only from given anchor on given link
+    public removeGroupFromAnchor(linkDoc: Doc, anchor: Doc, groupType: string) {
+        let groups = LinkManager.Instance.getAnchorGroups(linkDoc, anchor);
+        let newGroups = groups.filter(groupDoc => StrCast(groupDoc.type).toUpperCase() !== groupType.toUpperCase());
+        LinkManager.Instance.setAnchorGroups(linkDoc, anchor, newGroups);
     }
 
+    // public doesAnchorHaveGroup(linkDoc: Doc, anchor: Doc, groupDoc: Doc): boolean {
+    //     let groups = LinkManager.Instance.getAnchorGroups(linkDoc, anchor);
+    //     let index = groups.findIndex(gDoc => {
+    //         return StrCast(groupDoc.type).toUpperCase() === StrCast(gDoc.type).toUpperCase();
+    //     });
+    //     return index > -1;
+    // }
+
     // returns map of group type to anchor's links in that group type
-    public findRelatedGroupedLinks(anchor: Doc): Map<string, Array<Doc>> {
-        let related = this.findAllRelatedLinks(anchor);
+    public getRelatedGroupedLinks(anchor: Doc): Map<string, Array<Doc>> {
+        let related = this.getAllRelatedLinks(anchor);
         let anchorGroups = new Map<string, Array<Doc>>();
         related.forEach(link => {
             let groups = LinkManager.Instance.getAnchorGroups(link, anchor);
@@ -73,10 +192,41 @@ export class LinkManager {
         return anchorGroups;
     }
 
+    // public addMetadataKeyToGroup(groupType: string, key: string): boolean {
+    //     if (LinkManager.Instance.LinkManagerDoc) {
+    //         if (LinkManager.Instance.LinkManagerDoc[groupType]) {
+    //             let keyList = LinkManager.Instance.findMetadataKeysInGroup(groupType);
+    //             keyList.push(key);
+    //             LinkManager.Instance.LinkManagerDoc[groupType] = new List<string>(keyList);
+    //             return true;
+    //         }
+    //         return false;
+    //     }
+    //     return false;
+    // }
+
+    public getMetadataKeysInGroup(groupType: string): string[] {
+        if (LinkManager.Instance.LinkManagerDoc) {
+            return LinkManager.Instance.LinkManagerDoc[groupType] ? Cast(LinkManager.Instance.LinkManagerDoc[groupType], listSpec("string"), []) : [];
+        }
+        return [];
+    }
+
+    public setMetadataKeysForGroup(groupType: string, keys: string[]): boolean {
+        if (LinkManager.Instance.LinkManagerDoc) {
+            // if (LinkManager.Instance.LinkManagerDoc[groupType]) {
+            LinkManager.Instance.LinkManagerDoc[groupType] = new List<string>(keys);
+            return true;
+            // }
+            // return false;
+        }
+        return false;
+    }
+
     // returns a list of all metadata docs associated with the given group type
-    public findAllMetadataDocsInGroup(groupType: string): Array<Doc> {
+    public getAllMetadataDocsInGroup(groupType: string): Array<Doc> {
         let md: Doc[] = [];
-        let allLinks = LinkManager.Instance.allLinks;
+        let allLinks = LinkManager.Instance.getAllLinks();
         allLinks.forEach(linkDoc => {
             let anchor1Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor1, Doc, new Doc));
             let anchor2Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor2, Doc, new Doc));
@@ -86,27 +236,9 @@ export class LinkManager {
         return md;
     }
 
-    // removes all group docs from all links with the given group type
-    public deleteGroup(groupType: string): void {
-        let deleted = LinkManager.Instance.groupMetadataKeys.delete(groupType);
-        if (deleted) {
-            LinkManager.Instance.allLinks.forEach(linkDoc => {
-                LinkManager.Instance.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor1, Doc, new Doc), groupType);
-                LinkManager.Instance.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, new Doc), groupType);
-            });
-        }
-    }
-
-    // removes group doc of given group type only from given anchor on given link
-    public removeGroupFromAnchor(linkDoc: Doc, anchor: Doc, groupType: string) {
-        let groups = LinkManager.Instance.getAnchorGroups(linkDoc, anchor);
-        let newGroups = groups.filter(groupDoc => StrCast(groupDoc.type).toUpperCase() !== groupType.toUpperCase());
-        LinkManager.Instance.setAnchorGroups(linkDoc, anchor, newGroups);
-    }
-
     // checks if a link with the given anchors exists
-    public doesLinkExist(anchor1: Doc, anchor2: Doc) {
-        let allLinks = LinkManager.Instance.allLinks;
+    public doesLinkExist(anchor1: Doc, anchor2: Doc): boolean {
+        let allLinks = LinkManager.Instance.getAllLinks();
         let index = allLinks.findIndex(linkDoc => {
             return (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, new Doc), anchor1) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, new Doc), anchor2)) ||
                 (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, new Doc), anchor2) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, new Doc), anchor1));
@@ -115,7 +247,7 @@ export class LinkManager {
     }
 
     // finds the opposite anchor of a given anchor in a link
-    public findOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc {
+    public getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc {
         if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
             return Cast(linkDoc.anchor2, Doc, new Doc);
         } else {
@@ -123,34 +255,17 @@ export class LinkManager {
         }
     }
 
-    // gets the groups associates with an anchor in a link
-    public getAnchorGroups(linkDoc: Doc, anchor: Doc): Array<Doc> {
-        if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
-            return DocListCast(linkDoc.anchor1Groups);
-        } else {
-            return DocListCast(linkDoc.anchor2Groups);
-        }
-    }
 
-    // sets the groups of the given anchor in the given link
-    public setAnchorGroups(linkDoc: Doc, anchor: Doc, groups: Doc[]) {
-        if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
-            linkDoc.anchor1Groups = new List<Doc>(groups);
-        } else {
-            linkDoc.anchor2Groups = new List<Doc>(groups);
-        }
-    }
-
-    @action
-    public addLinkProxy(proxy: Doc) {
-        LinkManager.Instance.linkProxies.push(proxy);
-    }
+    // @action
+    // public addLinkProxy(proxy: Doc) {
+    //     LinkManager.Instance.linkProxies.push(proxy);
+    // }
 
-    public findLinkProxy(sourceViewId: string, targetViewId: string): Doc | undefined {
-        let index = LinkManager.Instance.linkProxies.findIndex(p => {
-            return StrCast(p.sourceViewId) === sourceViewId && StrCast(p.targetViewId) === targetViewId;
-        });
-        return index > -1 ? LinkManager.Instance.linkProxies[index] : undefined;
-    }
+    // public findLinkProxy(sourceViewId: string, targetViewId: string): Doc | undefined {
+    //     let index = LinkManager.Instance.linkProxies.findIndex(p => {
+    //         return StrCast(p.sourceViewId) === sourceViewId && StrCast(p.targetViewId) === targetViewId;
+    //     });
+    //     return index > -1 ? LinkManager.Instance.linkProxies[index] : undefined;
+    // }
 
 }
\ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 926273633..3a2752d7e 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -573,7 +573,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let linkButton = null;
         if (SelectionManager.SelectedDocuments().length > 0) {
             let selFirst = SelectionManager.SelectedDocuments()[0];
-            let linkCount = LinkManager.Instance.findAllRelatedLinks(selFirst.props.Document).length;
+            let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length;
             linkButton = (<Flyout
                 anchorPoint={anchorPoints.RIGHT_TOP}
                 content={<LinkMenu docView={selFirst}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 51630c29b..d90e9d23e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -35,6 +35,8 @@ import { HistoryUtil } from '../util/History';
 import { CollectionBaseView } from './collections/CollectionBaseView';
 import PDFMenu from './pdf/PDFMenu';
 import { InkTool } from '../../new_fields/InkField';
+import { LinkManager } from '../util/LinkManager';
+import { List } from '../../new_fields/List';
 
 
 @observer
@@ -146,6 +148,11 @@ export class MainView extends React.Component {
             let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${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}` }, id);
+            if (!CurrentUserUtils.UserDocument.linkManagerDoc) {
+                let linkManagerDoc = new Doc();
+                linkManagerDoc.allLinks = new List<Doc>([]);
+                CurrentUserUtils.UserDocument.linkManagerDoc = linkManagerDoc;
+            }
             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/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c82027da5..4140f8029 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -354,9 +354,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     tab.reactComponents = [upDiv];
                     tab.element.append(upDiv);
                     counter.DashDocId = tab.contentItem.config.props.documentId;
-                    tab.reactionDisposer = reaction(() => [LinkManager.Instance.findAllRelatedLinks(doc), doc.title],
+                    tab.reactionDisposer = reaction(() => [LinkManager.Instance.getAllRelatedLinks(doc), doc.title],
                         () => {
-                            counter.innerHTML = LinkManager.Instance.findAllRelatedLinks(doc).length;
+                            counter.innerHTML = LinkManager.Instance.getAllRelatedLinks(doc).length;
                             tab.titleElement[0].textContent = doc.title;
                         }, { fireImmediately: true });
                     tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 0b922b3c4..7bc3ad124 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -73,7 +73,7 @@ class TreeView extends React.Component<TreeViewProps> {
     @undoBatch delete = () => this.props.deleteDoc(this.props.document);
     @undoBatch openRight = async () => this.props.addDocTab(this.props.document, "openRight");
 
-    onPointerDown = (e: React.PointerEvent) => e.stopPropagation()
+    onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
     onPointerEnter = (e: React.PointerEvent): void => {
         this.props.active() && (this.props.document.libraryBrush = true);
         if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
@@ -115,7 +115,7 @@ class TreeView extends React.Component<TreeViewProps> {
         return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
     }
     @action
-    indent = () => this.props.addDocument(this.props.document) && this.delete();
+    indent = () => this.props.addDocument(this.props.document) && this.delete()
 
     renderBullet() {
         let docList = Cast(this.props.document["data"], listSpec(Doc));
@@ -167,7 +167,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 keyList.push(key);
             }
         });
-        if (LinkManager.Instance.findAllRelatedLinks(this.props.document).length > 0) keyList.push("links");
+        if (LinkManager.Instance.getAllRelatedLinks(this.props.document).length > 0) keyList.push("links");
         if (keyList.indexOf("data") !== -1) {
             keyList.splice(keyList.indexOf("data"), 1);
         }
@@ -281,9 +281,9 @@ class TreeView extends React.Component<TreeViewProps> {
         let ele: JSX.Element[] = [];
         let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
         let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.document, this._chosenKey, doc, addBefore, before);
-        let groups = LinkManager.Instance.findRelatedGroupedLinks(this.props.document);
+        let groups = LinkManager.Instance.getRelatedGroupedLinks(this.props.document);
         groups.forEach((groupLinkDocs, groupType) => {
-            let destLinks = groupLinkDocs.map(d => LinkManager.Instance.findOppositeAnchor(d, this.props.document));
+            let destLinks = groupLinkDocs.map(d => LinkManager.Instance.getOppositeAnchor(d, this.props.document));
             ele.push(
                 <div key={"treeviewlink-" + groupType + "subtitle"}>
                     <div className="collectionTreeView-subtitle">{groupType}:</div>
@@ -325,7 +325,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         addDocTab={this.props.addDocTab}
                         setPreviewScript={emptyFunction}>
                     </CollectionSchemaPreview>
-                </div>
+                </div>;
             }
         }
         return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onContextMenu={this.onWorkspaceContextMenu}>
@@ -364,14 +364,14 @@ class TreeView extends React.Component<TreeViewProps> {
                     TreeView.AddDocToList(docList[i - 1], fieldKey, child);
                     remove(child);
                 }
-            }
+            };
             let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
                 return add(doc, relativeTo ? relativeTo : docList[i], before !== undefined ? before : false);
-            }
+            };
             let rowHeight = () => {
                 let aspect = NumCast(child.nativeWidth, 0) / NumCast(child.nativeHeight, 0);
                 return aspect ? Math.min(child[WidthSym](), rowWidth()) / aspect : child[HeightSym]();
-            }
+            };
             return <TreeView
                 document={child}
                 treeViewId={treeViewId}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 76e089f08..bb8e8a5c2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -98,17 +98,17 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
         //     console.log("CONNECTION", StrCast(d.a.props.Document.title), StrCast(d.b.props.Document.title));
         // });
 
-        console.log("CONNECTIONS");
+        // console.log("CONNECTIONS");
 
         let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
             let srcViews = this.documentAnchors(connection.a);
             let targetViews = this.documentAnchors(connection.b);
 
-            console.log(srcViews.length, targetViews.length);
+            // console.log(srcViews.length, targetViews.length);
             let possiblePairs: { a: Doc, b: Doc, }[] = [];
             srcViews.map(sv => {
                 targetViews.map(tv => {
-                    console.log("PUSH", StrCast(sv.props.Document.title), StrCast(sv.props.Document.id), StrCast(tv.props.Document.title), StrCast(tv.props.Document.id));
+                    // console.log("PUSH", StrCast(sv.props.Document.title), StrCast(sv.props.Document.id), StrCast(tv.props.Document.title), StrCast(tv.props.Document.id));
                     possiblePairs.push({ a: sv.props.Document, b: tv.props.Document });
                 });
             });
@@ -142,7 +142,6 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
     }
 
     render() {
-        console.log("\n");
         return (
             <div className="collectionfreeformlinksview-container">
                 <svg className="collectionfreeformlinksview-svgCanvas">
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1fc2cf770..7b185336b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -285,7 +285,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 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 linkedDocs = LinkManager.Instance.findAllRelatedLinks(this.props.Document);
+                let linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document);
                 let expandedDocs: Doc[] = [];
                 expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
                 expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
@@ -536,11 +536,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; };
     onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
-    onDragOver = (e: React.DragEvent): void => {
-        this.props.Document.libraryBrush = true;
-        console.log("dragOver");
-    };
-    onDragLeave = (e: React.DragEvent): void => { this.props.Document.libraryBrush = false; };
 
     isSelected = () => SelectionManager.IsSelected(this);
     @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
@@ -585,7 +580,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     // display: display ? "block" : "none"
                 }}
                 onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
-                onDragOver={this.onDragOver} onDragLeave={this.onDragLeave}
                 onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
             >
                 {this.contents}
diff --git a/src/client/views/nodes/LinkButtonBox.scss b/src/client/views/nodes/LinkButtonBox.scss
index 24bfd2c9f..6be2dcf60 100644
--- a/src/client/views/nodes/LinkButtonBox.scss
+++ b/src/client/views/nodes/LinkButtonBox.scss
@@ -1,18 +1,18 @@
-.linkBox-cont {
-    width: 200px;
-    height: 100px;
-    background-color: black;
-    text-align: center;
-    color: white;
-    padding: 10px;
-    border-radius: 5px;
-    position: relative;
+// .linkBox-cont {
+//     width: 200px;
+//     height: 100px;
+//     background-color: black;
+//     text-align: center;
+//     color: white;
+//     padding: 10px;
+//     border-radius: 5px;
+//     position: relative;
 
-    .linkBox-cont-wrapper {
-        width: calc(100% - 20px);
-        position: absolute;
-        left: 50%;
-        top: 50%;
-        transform: translate(-50%, -50%);
-    }
-}
\ No newline at end of file
+//     .linkBox-cont-wrapper {
+//         width: calc(100% - 20px);
+//         position: absolute;
+//         left: 50%;
+//         top: 50%;
+//         transform: translate(-50%, -50%);
+//     }
+// }
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkButtonBox.tsx b/src/client/views/nodes/LinkButtonBox.tsx
index 8a7c1ed8b..440847ead 100644
--- a/src/client/views/nodes/LinkButtonBox.tsx
+++ b/src/client/views/nodes/LinkButtonBox.tsx
@@ -1,63 +1,63 @@
-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 } from "mobx";
-import { observer } from "mobx-react";
-import { FieldView, FieldViewProps } from './FieldView';
-import "./LinkButtonBox.scss";
-import { DocumentView } from "./DocumentView";
-import { Doc } from "../../../new_fields/Doc";
-import { LinkButtonField } from "../../../new_fields/LinkButtonField";
-import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { DocumentManager } from "../../util/DocumentManager";
-import { Id } from "../../../new_fields/FieldSymbols";
-
-library.add(faCaretUp);
-library.add(faObjectGroup);
-library.add(faStickyNote);
-library.add(faFilePdf);
-library.add(faFilm);
-
-@observer
-export class LinkButtonBox extends React.Component<FieldViewProps> {
-    public static LayoutString() { return FieldView.LayoutString(LinkButtonBox); }
-
-    followLink = (): void => {
-        console.log("follow link???");
-        let field = Cast(this.props.Document[this.props.fieldKey], LinkButtonField, new LinkButtonField({ sourceViewId: "-1", targetViewId: "-1" }));
-        let targetView = DocumentManager.Instance.getDocumentViewById(field.data.targetViewId);
-        if (targetView && targetView.props.ContainingCollectionView) {
-            CollectionDockingView.Instance.AddRightSplit(targetView.props.ContainingCollectionView.props.Document);
-        }
-    }
-
-    render() {
-
-        let field = Cast(this.props.Document[this.props.fieldKey], LinkButtonField, new LinkButtonField({ sourceViewId: "-1", targetViewId: "-1" }));
-        let targetView = DocumentManager.Instance.getDocumentViewById(field.data.targetViewId);
-
-        let text = "Could not find link";
-        if (targetView) {
-            let context = targetView.props.ContainingCollectionView ? (" in the context of " + StrCast(targetView.props.ContainingCollectionView.props.Document.title)) : "";
-            text = "Link to " + StrCast(targetView.props.Document.title) + context;
-        }
-
-        let activeDvs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false));
-        let display = activeDvs.reduce((found, dv) => {
-            let matchSv = field.data.sourceViewId === StrCast(dv.props.Document[Id]);
-            let matchTv = field.data.targetViewId === StrCast(dv.props.Document[Id]);
-            let match = matchSv || matchTv;
-            return match || found;
-        }, false);
-
-        return (
-            <div className="linkBox-cont" style={{ display: display ? "block" : "none" }}>
-                <div className="linkBox-cont-wrapper">
-                    <p>{text}</p>
-                </div>
-            </div >
-        );
-    }
-}
\ No newline at end of file
+// 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 } from "mobx";
+// import { observer } from "mobx-react";
+// import { FieldView, FieldViewProps } from './FieldView';
+// import "./LinkButtonBox.scss";
+// import { DocumentView } from "./DocumentView";
+// import { Doc } from "../../../new_fields/Doc";
+// import { LinkButtonField } from "../../../new_fields/LinkButtonField";
+// import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
+// import { CollectionDockingView } from "../collections/CollectionDockingView";
+// import { DocumentManager } from "../../util/DocumentManager";
+// import { Id } from "../../../new_fields/FieldSymbols";
+
+// library.add(faCaretUp);
+// library.add(faObjectGroup);
+// library.add(faStickyNote);
+// library.add(faFilePdf);
+// library.add(faFilm);
+
+// @observer
+// export class LinkButtonBox extends React.Component<FieldViewProps> {
+//     public static LayoutString() { return FieldView.LayoutString(LinkButtonBox); }
+
+//     followLink = (): void => {
+//         console.log("follow link???");
+//         let field = Cast(this.props.Document[this.props.fieldKey], LinkButtonField, new LinkButtonField({ sourceViewId: "-1", targetViewId: "-1" }));
+//         let targetView = DocumentManager.Instance.getDocumentViewById(field.data.targetViewId);
+//         if (targetView && targetView.props.ContainingCollectionView) {
+//             CollectionDockingView.Instance.AddRightSplit(targetView.props.ContainingCollectionView.props.Document);
+//         }
+//     }
+
+//     render() {
+
+//         let field = Cast(this.props.Document[this.props.fieldKey], LinkButtonField, new LinkButtonField({ sourceViewId: "-1", targetViewId: "-1" }));
+//         let targetView = DocumentManager.Instance.getDocumentViewById(field.data.targetViewId);
+
+//         let text = "Could not find link";
+//         if (targetView) {
+//             let context = targetView.props.ContainingCollectionView ? (" in the context of " + StrCast(targetView.props.ContainingCollectionView.props.Document.title)) : "";
+//             text = "Link to " + StrCast(targetView.props.Document.title) + context;
+//         }
+
+//         let activeDvs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false));
+//         let display = activeDvs.reduce((found, dv) => {
+//             let matchSv = field.data.sourceViewId === StrCast(dv.props.Document[Id]);
+//             let matchTv = field.data.targetViewId === StrCast(dv.props.Document[Id]);
+//             let match = matchSv || matchTv;
+//             return match || found;
+//         }, false);
+
+//         return (
+//             <div className="linkBox-cont" style={{ display: display ? "block" : "none" }}>
+//                 <div className="linkBox-cont-wrapper">
+//                     <p>{text}</p>
+//                 </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 95199bae2..5f4f7d4f0 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -17,9 +17,8 @@ library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, fa
 
 
 interface GroupTypesDropdownProps {
-    groupId: string;
     groupType: string;
-    setGroup: (groupId: string, group: string) => void;
+    setGroupType: (group: string) => void;
 }
 // this dropdown could be generalized
 @observer
@@ -32,20 +31,20 @@ class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> {
 
     @action
     createGroup = (groupType: string): void => {
-        this.props.setGroup(this.props.groupId, groupType);
-        LinkManager.Instance.groupMetadataKeys.set(groupType, []);
+        this.props.setGroupType(groupType);
+        LinkManager.Instance.addGroupType(groupType);
     }
 
     renderOptions = (): JSX.Element[] | JSX.Element => {
         if (this._searchTerm === "") return <></>;
 
-        let allGroupTypes = Array.from(LinkManager.Instance.groupMetadataKeys.keys());
+        let allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes());
         let groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
         let exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()) > -1;
 
         let options = groupOptions.map(groupType => {
             return <div key={groupType} className="linkEditor-option"
-                onClick={() => { this.props.setGroup(this.props.groupId, groupType); this.setGroupType(groupType); this.setSearchTerm(""); }}>{groupType}</div>;
+                onClick={() => { this.props.setGroupType(groupType); this.setGroupType(groupType); this.setSearchTerm(""); }}>{groupType}</div>;
         });
 
         // if search term does not already exist as a group type, give option to create new group type
@@ -85,7 +84,7 @@ class LinkMetadataEditor extends React.Component<LinkMetadataEditorProps> {
 
     @action
     setMetadataKey = (value: string): void => {
-        let groupMdKeys = new Array(...LinkManager.Instance.groupMetadataKeys.get(this.props.groupType)!);
+        let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);
 
         // don't allow user to create existing key
         let newIndex = groupMdKeys.findIndex(key => key.toUpperCase() === value.toUpperCase());
@@ -98,12 +97,15 @@ class LinkMetadataEditor extends React.Component<LinkMetadataEditorProps> {
         }
 
         // set new value for key
-        let currIndex = groupMdKeys.findIndex(key => key.toUpperCase() === this._key.toUpperCase());
+        let currIndex = groupMdKeys.findIndex(key => {
+            console.log("finding index this", key.toUpperCase(), "that", this._key.toUpperCase());
+            return StrCast(key).toUpperCase() === this._key.toUpperCase();
+        });
         if (currIndex === -1) console.error("LinkMetadataEditor: key was not found");
         groupMdKeys[currIndex] = value;
 
         this._key = value;
-        LinkManager.Instance.groupMetadataKeys.set(this.props.groupType, groupMdKeys);
+        LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, groupMdKeys);
     }
 
     @action
@@ -116,13 +118,13 @@ class LinkMetadataEditor extends React.Component<LinkMetadataEditorProps> {
 
     @action
     removeMetadata = (): void => {
-        let groupMdKeys = new Array(...LinkManager.Instance.groupMetadataKeys.get(this.props.groupType)!);
+        let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);
 
         let index = groupMdKeys.findIndex(key => key.toUpperCase() === this._key.toUpperCase());
         if (index === -1) console.error("LinkMetadataEditor: key was not found");
         groupMdKeys.splice(index, 1);
 
-        LinkManager.Instance.groupMetadataKeys.set(this.props.groupType, groupMdKeys);
+        LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, groupMdKeys);
         this._key = "";
     }
 
@@ -137,215 +139,176 @@ class LinkMetadataEditor extends React.Component<LinkMetadataEditorProps> {
     }
 }
 
-
-interface LinkEditorProps {
+interface LinkGroupEditorProps {
     sourceDoc: Doc;
     linkDoc: Doc;
-    showLinks: () => void;
+    groupDoc: Doc;
 }
 @observer
-export class LinkEditor extends React.Component<LinkEditorProps> {
+export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
 
-    // map of temporary group id to the corresponding group doc
-    @observable private _groups: Map<string, Doc> = new Map();
-
-    constructor(props: LinkEditorProps) {
-        super(props);
+    @action
+    setGroupType = (groupType: string): void => {
+        console.log("SET GROUP TYPE TO", groupType);
+        this.props.groupDoc.type = groupType;
+        console.log("GROUP TYPE HAS BEEN SET TO ", StrCast(this.props.groupDoc.type));
+    }
 
-        let groups = new Map<string, Doc>();
-        let groupList = LinkManager.Instance.getAnchorGroups(props.linkDoc, props.sourceDoc);
-        groupList.forEach(groupDoc => {
-            let id = Utils.GenerateGuid();
-            groups.set(id, groupDoc);
-        });
-        this._groups = groups;
+    removeGroupFromLink = (groupType: string): void => {
+        LinkManager.Instance.removeGroupFromAnchor(this.props.linkDoc, this.props.sourceDoc, groupType);
     }
 
-    @action
-    deleteLink = (): void => {
-        let index = LinkManager.Instance.allLinks.indexOf(this.props.linkDoc);
-        LinkManager.Instance.allLinks.splice(index, 1);
-        this.props.showLinks();
+    deleteGroup = (groupType: string): void => {
+        LinkManager.Instance.deleteGroupType(groupType);
     }
 
-    @action
-    addGroup = (): void => {
-        // new group only gets added if there is not already a group with type "new group"
-        let index = Array.from(this._groups.values()).findIndex(groupDoc => {
-            return groupDoc.type === "New Group";
-        });
-        if (index > -1) return;
+    copyGroup = (groupType: string): void => {
+        let sourceGroupDoc = this.props.groupDoc;
+        let sourceMdDoc = Cast(sourceGroupDoc.metadata, Doc, new Doc);
 
-        // create new metadata document for group
-        let mdDoc = Docs.TextDocument();
-        mdDoc.proto!.anchor1 = this.props.sourceDoc.title;
-        mdDoc.proto!.anchor2 = LinkManager.Instance.findOppositeAnchor(this.props.linkDoc, this.props.sourceDoc).title;
+        let destDoc = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
+        // let destGroupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, destDoc);
+        let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
 
-        // create new group document
-        let groupDoc = Docs.TextDocument();
-        groupDoc.proto!.type = "New Group";
-        groupDoc.proto!.metadata = mdDoc;
+        // create new metadata doc with copied kvp
+        let destMdDoc = new Doc();
+        destMdDoc.anchor1 = StrCast(sourceMdDoc.anchor2);
+        destMdDoc.anchor2 = StrCast(sourceMdDoc.anchor1);
+        keys.forEach(key => {
+            let val = sourceMdDoc[key] === undefined ? "" : StrCast(sourceMdDoc[key]);
+            destMdDoc[key] = val;
+        });
 
-        this._groups.set(Utils.GenerateGuid(), groupDoc);
+        // create new group doc with new metadata doc
+        let destGroupDoc = new Doc();
+        destGroupDoc.type = groupType;
+        destGroupDoc.metadata = destMdDoc;
 
-        let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc;
-        LinkManager.Instance.setAnchorGroups(linkDoc, this.props.sourceDoc, Array.from(this._groups.values()));
+        LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, destDoc, destGroupDoc, true);
     }
 
     @action
-    setGroupType = (groupId: string, groupType: string): void => {
-        let groupDoc = this._groups.get(groupId);
-        if (groupDoc) {
-            groupDoc.proto!.type = groupType;
-            this._groups.set(groupId, groupDoc);
-            LinkManager.Instance.setAnchorGroups(this.props.linkDoc, this.props.sourceDoc, Array.from(this._groups.values()));
-        }
-    }
-
-    removeGroupFromLink = (groupId: string, groupType: string): void => {
-        let groupDoc = this._groups.get(groupId);
-        if (!groupDoc) console.error("LinkEditor: group not found");
-        LinkManager.Instance.removeGroupFromAnchor(this.props.linkDoc, this.props.sourceDoc, groupType);
-        this._groups.delete(groupId);
-    }
-
-    deleteGroup = (groupId: string, groupType: string): void => {
-        let groupDoc = this._groups.get(groupId);
-        if (!groupDoc) console.error("LinkEditor: group not found");
-        LinkManager.Instance.deleteGroup(groupType);
-        this._groups.delete(groupId);
+    addMetadata = (groupType: string): void => {
+        let mdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
+        // only add "new key" if there is no other key with value "new key"; prevents spamming
+        if (mdKeys.indexOf("new key") === -1) mdKeys.push("new key");
+        LinkManager.Instance.setMetadataKeysForGroup(groupType, mdKeys);
     }
 
-    copyGroup = (groupId: string, groupType: string): void => {
-        let sourceGroupDoc = this._groups.get(groupId);
-        let sourceMdDoc = Cast(sourceGroupDoc!.metadata, Doc, new Doc);
-        let destDoc = LinkManager.Instance.findOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
-        let destGroupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, destDoc);
-        let keys = LinkManager.Instance.groupMetadataKeys.get(groupType);
-
-        // create new metadata doc with copied kvp
-        let destMdDoc = Docs.TextDocument();
-        destMdDoc.proto!.anchor1 = StrCast(sourceMdDoc.anchor2);
-        destMdDoc.proto!.anchor2 = StrCast(sourceMdDoc.anchor1);
-        if (keys) {
-            keys.forEach(key => {
-                let val = sourceMdDoc[key] === undefined ? "" : StrCast(sourceMdDoc[key]);
-                destMdDoc[key] = val;
+    renderMetadata = (): JSX.Element[] => {
+        let metadata: Array<JSX.Element> = [];
+        let groupDoc = this.props.groupDoc;
+        let mdDoc = Cast(groupDoc.metadata, Doc, new Doc);
+        let groupType = StrCast(groupDoc.type);
+        let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
+        if (groupMdKeys) {
+            groupMdKeys.forEach((key, index) => {
+                metadata.push(
+                    <LinkMetadataEditor key={"mded-" + index} groupType={groupType} mdDoc={mdDoc} mdKey={key} mdValue={(mdDoc[key] === undefined) ? "" : StrCast(mdDoc[key])} />
+                );
             });
         }
-
-        // create new group doc with new metadata doc
-        let destGroupDoc = Docs.TextDocument();
-        destGroupDoc.proto!.type = groupType;
-        destGroupDoc.proto!.metadata = destMdDoc;
-
-        // if group does not already exist on opposite anchor, create group doc
-        let index = destGroupList.findIndex(groupDoc => { StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase(); });
-        if (index > -1) {
-            destGroupList[index] = destGroupDoc;
-        } else {
-            destGroupList.push(destGroupDoc);
-        }
-
-        LinkManager.Instance.setAnchorGroups(this.props.linkDoc, destDoc, destGroupList);
+        return metadata;
     }
 
-    viewGroupAsTable = (groupId: string, groupType: string): JSX.Element => {
-        let keys = LinkManager.Instance.groupMetadataKeys.get(groupType);
-        let groupDoc = this._groups.get(groupId);
-        if (keys && groupDoc) {
-            let docs: Doc[] = LinkManager.Instance.findAllMetadataDocsInGroup(groupType);
-            let createTable = action(() => Docs.SchemaDocument(["anchor1", "anchor2", ...keys!], docs, { width: 200, height: 200, title: groupType + " table" }));
-            let ref = React.createRef<HTMLDivElement>();
-            return <div ref={ref}><button className="linkEditor-button" onPointerDown={SetupDrag(ref, createTable)}><FontAwesomeIcon icon="table" size="sm" /></button></div>;
-        } else {
-            return <button className="linkEditor-button" disabled><FontAwesomeIcon icon="table" size="sm" /></button>;
-        }
+    viewGroupAsTable = (groupType: string): JSX.Element => {
+        let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
+        let docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType);
+        let createTable = action(() => Docs.SchemaDocument(["anchor1", "anchor2", ...keys], docs, { width: 500, height: 300, title: groupType + " table" }));
+        let ref = React.createRef<HTMLDivElement>();
+        return <div ref={ref}><button className="linkEditor-button" onPointerDown={SetupDrag(ref, createTable)}><FontAwesomeIcon icon="table" size="sm" /></button></div>;
     }
 
-    renderGroup = (groupId: string, groupDoc: Doc): JSX.Element => {
-        let type = StrCast(groupDoc.type);
-        if ((type && LinkManager.Instance.groupMetadataKeys.get(type)) || type === "New Group") {
-            let buttons;
-            if (type === "New Group") {
-                buttons = (
-                    <>
-                        <button className="linkEditor-button" disabled={true} title="Add KVP"><FontAwesomeIcon icon="plus" size="sm" /></button>
-                        <button className="linkEditor-button" disabled title="Copy group to opposite anchor"><FontAwesomeIcon icon="exchange-alt" size="sm" /></button>
-                        <button className="linkEditor-button" onClick={() => this.removeGroupFromLink(groupId, type)} title="Remove group from link"><FontAwesomeIcon icon="times" size="sm" /></button>
-                        <button className="linkEditor-button" disabled title="Delete group"><FontAwesomeIcon icon="trash" size="sm" /></button>
-                        <button className="linkEditor-button" disabled><FontAwesomeIcon icon="table" size="sm" /></button>
-                    </>
-                );
-            } else {
-                buttons = (
-                    <>
-                        <button className="linkEditor-button" onClick={() => this.addMetadata(StrCast(groupDoc.proto!.type))} title="Add KVP"><FontAwesomeIcon icon="plus" size="sm" /></button>
-                        <button className="linkEditor-button" onClick={() => this.copyGroup(groupId, type)} title="Copy group to opposite anchor"><FontAwesomeIcon icon="exchange-alt" size="sm" /></button>
-                        <button className="linkEditor-button" onClick={() => this.removeGroupFromLink(groupId, type)} title="Remove group from link"><FontAwesomeIcon icon="times" size="sm" /></button>
-                        <button className="linkEditor-button" onClick={() => this.deleteGroup(groupId, type)} title="Delete group"><FontAwesomeIcon icon="trash" size="sm" /></button>
-                        {this.viewGroupAsTable(groupId, type)}
-                    </>
-                );
-            }
-
-            return (
-                <div key={groupId} className="linkEditor-group">
-                    <div className="linkEditor-group-row">
-                        <p className="linkEditor-group-row-label">type:</p>
-                        <GroupTypesDropdown groupId={groupId} groupType={StrCast(groupDoc.proto!.type)} setGroup={this.setGroupType} />
-                    </div>
-                    {this.renderMetadata(groupId)}
-                    <div className="linkEditor-group-buttons">
-                        {buttons}
-                    </div>
-                </div>
+    render() {
+        let groupType = StrCast(this.props.groupDoc.type);
+        // if ((groupType && LinkManager.Instance.getMetadataKeysInGroup(groupType).length > 0) || groupType === "") {
+        let buttons;
+        if (groupType === "") {
+            buttons = (
+                <>
+                    <button className="linkEditor-button" disabled={true} title="Add KVP"><FontAwesomeIcon icon="plus" size="sm" /></button>
+                    <button className="linkEditor-button" disabled title="Copy group to opposite anchor"><FontAwesomeIcon icon="exchange-alt" size="sm" /></button>
+                    <button className="linkEditor-button" onClick={() => this.removeGroupFromLink(groupType)} title="Remove group from link"><FontAwesomeIcon icon="times" size="sm" /></button>
+                    <button className="linkEditor-button" disabled title="Delete group"><FontAwesomeIcon icon="trash" size="sm" /></button>
+                    <button className="linkEditor-button" disabled><FontAwesomeIcon icon="table" size="sm" /></button>
+                </>
             );
         } else {
-            return <></>;
+            buttons = (
+                <>
+                    <button className="linkEditor-button" onClick={() => this.addMetadata(groupType)} title="Add KVP"><FontAwesomeIcon icon="plus" size="sm" /></button>
+                    <button className="linkEditor-button" onClick={() => this.copyGroup(groupType)} title="Copy group to opposite anchor"><FontAwesomeIcon icon="exchange-alt" size="sm" /></button>
+                    <button className="linkEditor-button" onClick={() => this.removeGroupFromLink(groupType)} title="Remove group from link"><FontAwesomeIcon icon="times" size="sm" /></button>
+                    <button className="linkEditor-button" onClick={() => this.deleteGroup(groupType)} title="Delete group"><FontAwesomeIcon icon="trash" size="sm" /></button>
+                    {this.viewGroupAsTable(groupType)}
+                </>
+            );
         }
+
+        return (
+            <div className="linkEditor-group">
+                <div className="linkEditor-group-row">
+                    <p className="linkEditor-group-row-label">type:</p>
+                    <GroupTypesDropdown groupType={groupType} setGroupType={this.setGroupType} />
+                </div>
+                {this.renderMetadata()}
+                <div className="linkEditor-group-buttons">
+                    {buttons}
+                </div>
+            </div>
+        );
     }
+    // else {
+    //     return <></>;
+    // }
+    // }
+}
+
 
+interface LinkEditorProps {
+    sourceDoc: Doc;
+    linkDoc: Doc;
+    showLinks: () => void;
+}
+@observer
+export class LinkEditor extends React.Component<LinkEditorProps> {
 
     @action
-    addMetadata = (groupType: string): void => {
-        console.log("ADD MD");
-        let mdKeys = LinkManager.Instance.groupMetadataKeys.get(groupType);
-        if (mdKeys) {
-            // only add "new key" if there is no other key with value "new key"; prevents spamming
-            if (mdKeys.indexOf("new key") === -1) mdKeys.push("new key");
-        } else {
-            mdKeys = ["new key"];
-        }
-        LinkManager.Instance.groupMetadataKeys.set(groupType, mdKeys);
+    deleteLink = (): void => {
+        LinkManager.Instance.deleteLink(this.props.linkDoc);
+        this.props.showLinks();
     }
 
-    renderMetadata = (groupId: string): JSX.Element[] => {
-        let metadata: Array<JSX.Element> = [];
-        let groupDoc = this._groups.get(groupId);
-        if (groupDoc) {
-            let mdDoc = Cast(groupDoc.proto!.metadata, Doc, new Doc);
-            let groupType = StrCast(groupDoc.proto!.type);
-            let groupMdKeys = LinkManager.Instance.groupMetadataKeys.get(groupType);
-            if (groupMdKeys) {
-                groupMdKeys.forEach((key, index) => {
-                    metadata.push(
-                        <LinkMetadataEditor key={"mded-" + index} groupType={groupType} mdDoc={mdDoc} mdKey={key} mdValue={(mdDoc[key] === undefined) ? "" : StrCast(mdDoc[key])} />
-                    );
-                });
-            }
-        }
-        return metadata;
+    @action
+    addGroup = (): void => {
+        // create new metadata document for group
+        let mdDoc = new Doc();
+        mdDoc.anchor1 = this.props.sourceDoc.title;
+        mdDoc.anchor2 = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc).title;
+
+        // create new group document
+        let groupDoc = new Doc();
+        groupDoc.type = "";
+        groupDoc.metadata = mdDoc;
+
+        LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, this.props.sourceDoc, groupDoc);
     }
 
     render() {
-        let destination = LinkManager.Instance.findOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
+        let destination = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
 
-        let groups: Array<JSX.Element> = [];
-        this._groups.forEach((groupDoc, groupId) => {
-            groups.push(this.renderGroup(groupId, groupDoc));
+        let groupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, this.props.sourceDoc);
+        console.log("NUM GROUPS", groupList.length);
+        let groups = groupList.map(groupDoc => {
+            return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.type)} linkDoc={this.props.linkDoc} sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
         });
 
+
+        // let groups: Array<JSX.Element> = [];
+        // this._groups.forEach((groupDoc, groupId) => {
+        //     groups.push(this.renderGroup(groupId, groupDoc));
+        // });
+
         return (
             <div className="linkEditor">
                 <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button>
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index f96c7d2e4..04ca47db3 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -38,7 +38,7 @@ export class LinkMenu extends React.Component<Props> {
 
     render() {
         let sourceDoc = this.props.docView.props.Document;
-        let groups: Map<string, Doc[]> = LinkManager.Instance.findRelatedGroupedLinks(sourceDoc);
+        let groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
         if (this._editingLink === undefined) {
             return (
                 <div className="linkMenu">
diff --git a/src/client/views/nodes/LinkMenuGroup.tsx b/src/client/views/nodes/LinkMenuGroup.tsx
index 229143d99..71326f703 100644
--- a/src/client/views/nodes/LinkMenuGroup.tsx
+++ b/src/client/views/nodes/LinkMenuGroup.tsx
@@ -42,10 +42,10 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
             document.removeEventListener("pointermove", this.onLinkButtonMoved);
             document.removeEventListener("pointerup", this.onLinkButtonUp);
 
-            let draggedDocs = this.props.group.map(linkDoc => LinkManager.Instance.findOppositeAnchor(linkDoc, this.props.sourceDoc));
+            let draggedDocs = this.props.group.map(linkDoc => LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc));
             let dragData = new DragManager.DocumentDragData(draggedDocs);
 
-            DragManager.StartLinkedDocumentDrag([this._drag.current], dragData, e.x, e.y, {
+            DragManager.StartLinkedDocumentDrag([this._drag.current], this.props.sourceDoc, dragData, e.x, e.y, {
                 handlers: {
                     dragComplete: action(emptyFunction),
                 },
@@ -57,7 +57,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
 
     render() {
         let groupItems = this.props.group.map(linkDoc => {
-            let destination = LinkManager.Instance.findOppositeAnchor(linkDoc, this.props.sourceDoc);
+            let destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc);
             return <LinkMenuItem key={destination[Id] + this.props.sourceDoc[Id]} groupType={this.props.groupType}
                 linkDoc={linkDoc} sourceDoc={this.props.sourceDoc} destinationDoc={destination} showEditor={this.props.showEditor} />;
         });
@@ -69,6 +69,6 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
                     {groupItems}
                 </div>
             </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx
index 42ef353b7..28694721d 100644
--- a/src/client/views/nodes/LinkMenuItem.tsx
+++ b/src/client/views/nodes/LinkMenuItem.tsx
@@ -53,8 +53,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         let mdRows: Array<JSX.Element> = [];
         if (groupDoc) {
             let mdDoc = Cast(groupDoc.metadata, Doc, new Doc);
-            let keys = LinkManager.Instance.groupMetadataKeys.get(this.props.groupType);
-            mdRows = keys!.map(key => {
+            let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
+            mdRows = keys.map(key => {
                 return (<div key={key} className="link-metadata-row"><b>{key}</b>: {StrCast(mdDoc[key])}</div>);
             });
         }
@@ -88,7 +88,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
 
     render() {
 
-        let keys = LinkManager.Instance.groupMetadataKeys.get(this.props.groupType);
+        let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
         let canExpand = keys ? keys.length > 0 : false;
 
         return (
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index fda788f2d..9b104184f 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -234,23 +234,20 @@ export namespace Doc {
     export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc {
         const copy = new Doc;
         Object.keys(doc).forEach(key => {
-            console.log(key);
             const field = doc[key];
             if (key === "proto" && copyProto) {
-                console.log(1);
                 if (field instanceof Doc) {
-                    console.log(2);
                     copy[key] = Doc.MakeCopy(field);
                 }
             } else {
                 if (field instanceof RefField) {
-                    console.log(3);
+                    console.log("equals field, ref", key);
                     copy[key] = field;
                 } else if (field instanceof ObjectField) {
-                    console.log(4);
+                    console.log("copy field, object", key);
                     copy[key] = ObjectField.MakeCopy(field);
                 } else {
-                    console.log(5);
+                    console.log("equals field", key);
                     copy[key] = field;
                 }
             }
diff --git a/src/new_fields/LinkButtonField.ts b/src/new_fields/LinkButtonField.ts
index 92e1ed922..e6d1de749 100644
--- a/src/new_fields/LinkButtonField.ts
+++ b/src/new_fields/LinkButtonField.ts
@@ -1,35 +1,35 @@
-import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, primitive, createSimpleSchema, object } from "serializr";
-import { ObjectField } from "./ObjectField";
-import { Copy, ToScriptString } from "./FieldSymbols";
-import { Doc } from "./Doc";
-import { DocumentView } from "../client/views/nodes/DocumentView";
+// import { Deserializable } from "../client/util/SerializationHelper";
+// import { serializable, primitive, createSimpleSchema, object } from "serializr";
+// import { ObjectField } from "./ObjectField";
+// import { Copy, ToScriptString } from "./FieldSymbols";
+// import { Doc } from "./Doc";
+// import { DocumentView } from "../client/views/nodes/DocumentView";
 
-export type LinkButtonData = {
-    sourceViewId: string,
-    targetViewId: string
-};
+// export type LinkButtonData = {
+//     sourceViewId: string,
+//     targetViewId: string
+// };
 
-const LinkButtonSchema = createSimpleSchema({
-    sourceViewId: true,
-    targetViewId: true
-});
+// const LinkButtonSchema = createSimpleSchema({
+//     sourceViewId: true,
+//     targetViewId: true
+// });
 
-@Deserializable("linkButton")
-export class LinkButtonField extends ObjectField {
-    @serializable(object(LinkButtonSchema))
-    readonly data: LinkButtonData;
+// @Deserializable("linkButton")
+// export class LinkButtonField extends ObjectField {
+//     @serializable(object(LinkButtonSchema))
+//     readonly data: LinkButtonData;
 
-    constructor(data: LinkButtonData) {
-        super();
-        this.data = data;
-    }
+//     constructor(data: LinkButtonData) {
+//         super();
+//         this.data = data;
+//     }
 
-    [Copy]() {
-        return new LinkButtonField(this.data);
-    }
+//     [Copy]() {
+//         return new LinkButtonField(this.data);
+//     }
 
-    [ToScriptString]() {
-        return "invalid";
-    }
-}
+//     [ToScriptString]() {
+//         return "invalid";
+//     }
+// }
-- 
cgit v1.2.3-70-g09d2


From 79f301a2f74e88f1cf59064de320c199b5154827 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samuel_wilkins@brown.edu>
Date: Fri, 28 Jun 2019 21:47:26 -0400
Subject: implemented global key handler

---
 src/client/util/SelectionManager.ts                |   5 +-
 src/client/views/GlobalKeyHandler.ts               | 141 +++++++++++++++++++++
 src/client/views/MainView.tsx                      |  62 ++-------
 .../views/collections/CollectionDockingView.tsx    |  20 ++-
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 5 files changed, 176 insertions(+), 56 deletions(-)
 create mode 100644 src/client/views/GlobalKeyHandler.ts

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

diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 7dbb81e76..3bc71ad42 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,11 +1,13 @@
-import { observable, action, runInAction } from "mobx";
+import { observable, action, runInAction, IReactionDisposer, reaction, autorun } from "mobx";
 import { Doc } from "../../new_fields/Doc";
 import { DocumentView } from "../views/nodes/DocumentView";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { NumCast } from "../../new_fields/Types";
 
 export namespace SelectionManager {
+
     class Manager {
+
         @observable IsDragging: boolean = false;
         @observable SelectedDocuments: Array<DocumentView> = [];
 
@@ -18,6 +20,7 @@ export namespace SelectionManager {
                 }
 
                 manager.SelectedDocuments.push(docView);
+                // console.log(manager.SelectedDocuments);
                 docView.props.whenActiveChanged(true);
             }
         }
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
new file mode 100644
index 000000000..bb10f27cf
--- /dev/null
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -0,0 +1,141 @@
+import { UndoManager } from "../util/UndoManager";
+import { SelectionManager } from "../util/SelectionManager";
+import { CollectionDockingView } from "./collections/CollectionDockingView";
+import { MainView } from "./MainView";
+import { DragManager } from "../util/DragManager";
+import { action } from "mobx";
+
+const modifiers = ["Control", "Meta", "Shift", "Alt"];
+type KeyHandler = (keycode: string) => KeyControlInfo;
+type KeyControlInfo = {
+    preventDefault: boolean,
+    stopPropagation: boolean
+};
+
+export default class KeyManager {
+    public static Handler: KeyManager;
+    private mainView: MainView;
+    private router = new Map<string, KeyHandler>();
+
+    constructor(mainView: MainView) {
+        this.mainView = mainView;
+        this.router.set("0000", this.unmodified);
+        this.router.set("0100", this.ctrl);
+        this.router.set("0010", this.alt);
+        this.router.set("1100", this.ctrl_shift);
+    }
+
+    public handle = (e: KeyboardEvent) => {
+        let keyname = e.key.toLowerCase();
+        this.handleGreedy(keyname);
+
+        if (modifiers.includes(keyname)) {
+            return;
+        }
+
+        let bit = (value: boolean) => value ? "1" : "0";
+        let modifierIndex = bit(e.shiftKey) + bit(e.ctrlKey) + bit(e.altKey) + bit(e.metaKey);
+
+        let handleConstrained = this.router.get(modifierIndex);
+        if (!handleConstrained) {
+            return;
+        }
+
+        let control = handleConstrained(keyname);
+
+        control.stopPropagation && e.stopPropagation();
+        control.preventDefault && e.preventDefault();
+    }
+
+    private handleGreedy = action((keyname: string) => {
+        switch (keyname) {
+        }
+    });
+
+    private unmodified = action((keyname: string) => {
+        switch (keyname) {
+            case "escape":
+                if (CollectionDockingView.Instance.HasFullScreen()) {
+                    CollectionDockingView.Instance.CloseFullScreen();
+                } else {
+                    SelectionManager.DeselectAll();
+                }
+                DragManager.AbortDrag();
+                break;
+        }
+
+        return {
+            stopPropagation: false,
+            preventDefault: false
+        };
+    });
+
+    private alt = action((keyname: string) => {
+        let stopPropagation = true;
+        let preventDefault = true;
+
+        switch (keyname) {
+            case "n":
+                let toggle = this.mainView.addMenuToggle.current!;
+                toggle.checked = !toggle.checked;
+                break;
+        }
+
+        return {
+            stopPropagation: stopPropagation,
+            preventDefault: preventDefault
+        };
+    });
+
+    private ctrl = action((keyname: string) => {
+        let stopPropagation = true;
+        let preventDefault = true;
+
+        switch (keyname) {
+            case "arrowright":
+                this.mainView.mainFreeform && CollectionDockingView.Instance.AddRightSplit(this.mainView.mainFreeform, undefined);
+                break;
+            case "arrowleft":
+                this.mainView.mainFreeform && CollectionDockingView.Instance.CloseRightSplit(this.mainView.mainFreeform);
+                break;
+            case "f":
+                this.mainView.isSearchVisible = !this.mainView.isSearchVisible;
+                break;
+            case "o":
+                let target = SelectionManager.SelectedDocuments()[0];
+                target && target.fullScreenClicked();
+                break;
+            case "r":
+                preventDefault = false;
+                break;
+            case "y":
+                UndoManager.Redo();
+                break;
+            case "z":
+                UndoManager.Undo();
+                break;
+        }
+
+        return {
+            stopPropagation: stopPropagation,
+            preventDefault: preventDefault
+        };
+    });
+
+    private ctrl_shift = action((keyname: string) => {
+        let stopPropagation = true;
+        let preventDefault = true;
+
+        switch (keyname) {
+            case "z":
+                UndoManager.Redo();
+                break;
+        }
+
+        return {
+            stopPropagation: stopPropagation,
+            preventDefault: preventDefault
+        };
+    });
+
+}
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 6290d8985..157512aa0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -36,18 +36,20 @@ import { CollectionBaseView } from './collections/CollectionBaseView';
 import { List } from '../../new_fields/List';
 import PDFMenu from './pdf/PDFMenu';
 import { InkTool } from '../../new_fields/InkField';
-import * as _ from "lodash";
+import _ from "lodash";
+import KeyManager from './GlobalKeyHandler';
 
 @observer
 export class MainView extends React.Component {
     public static Instance: MainView;
+    @observable addMenuToggle = React.createRef<HTMLInputElement>();
     @observable private _workspacesShown: boolean = false;
     @observable public pwidth: number = 0;
     @observable public pheight: number = 0;
     @computed private get mainContainer(): Opt<Doc> {
         return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc));
     }
-    @computed private get mainFreeform(): Opt<Doc> {
+    @computed get mainFreeform(): Opt<Doc> {
         let docs = DocListCast(this.mainContainer!.data);
         return (docs && docs.length > 1) ? docs[1] : undefined;
     }
@@ -64,12 +66,13 @@ export class MainView extends React.Component {
     }
 
     componentWillMount() {
-        document.removeEventListener("keydown", this.globalKeyHandler);
-        document.addEventListener("keydown", this.globalKeyHandler);
+        KeyManager.Handler = new KeyManager(this);
+        document.removeEventListener("keydown", KeyManager.Handler.handle);
+        document.addEventListener("keydown", KeyManager.Handler.handle);
     }
 
     componentWillUnMount() {
-        document.removeEventListener("keydown", this.globalKeyHandler);
+        document.removeEventListener("keydown", KeyManager.Handler.handle);
     }
 
     constructor(props: Readonly<{}>) {
@@ -122,18 +125,6 @@ export class MainView extends React.Component {
         // 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("keydown", (e) => {
-            if (e.key === "Escape") {
-                DragManager.AbortDrag();
-                SelectionManager.DeselectAll();
-            } else if (e.key === "z" && e.ctrlKey) {
-                e.preventDefault();
-                UndoManager.Undo();
-            } else if ((e.key === "y" && e.ctrlKey) || (e.key === "z" && e.ctrlKey && e.shiftKey)) {
-                e.preventDefault();
-                UndoManager.Redo();
-            }
-        }, false); // drag event handler
         // click interactions for the context menu
         document.addEventListener("pointerdown", action(function (e: PointerEvent) {
 
@@ -292,7 +283,7 @@ export class MainView extends React.Component {
         ];
 
         return < div id="add-nodes-menu" >
-            <input type="checkbox" id="add-menu-toggle" />
+            <input type="checkbox" id="add-menu-toggle" ref={this.addMenuToggle} />
             <label htmlFor="add-menu-toggle" title="Add Node"><p>+</p></label>
 
             <div id="add-options-content">
@@ -300,8 +291,7 @@ export class MainView extends React.Component {
                     <li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li>
                     <li key="undo"><button className="add-button round-button" title="Undo" onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
                     <li key="redo"><button className="add-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
-                    <li key="color"><button className="add-button round-button" title="Redo" onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
-
+                    <li key="color"><button className="add-button round-button" title="Select Color" onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
                         <div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
                             <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
                         </div>
@@ -362,38 +352,6 @@ export class MainView extends React.Component {
         this.isSearchVisible = !this.isSearchVisible;
     }
 
-    @action
-    globalKeyHandler = (e: KeyboardEvent) => {
-        if (e.key === "Control" || !e.ctrlKey) return;
-
-        if (e.key === "v") return;
-        if (e.key === "c") return;
-
-        e.preventDefault();
-        e.stopPropagation();
-
-        switch (e.key) {
-            case "ArrowRight":
-                if (this.mainFreeform) {
-                    CollectionDockingView.Instance.AddRightSplit(this.mainFreeform, undefined);
-                }
-                break;
-            case "ArrowLeft":
-                if (this.mainFreeform) {
-                    CollectionDockingView.Instance.CloseRightSplit(this.mainFreeform);
-                }
-                break;
-            case "o":
-                this.globalDisplayFlags.jumpToVisible = true;
-                break;
-            case "escape":
-                _.mapValues(this.globalDisplayFlags, () => false);
-                break;
-            case "f":
-                this.isSearchVisible = !this.isSearchVisible;
-        }
-    }
-
 
     render() {
         return (
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6564c38a0..71093df85 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -52,6 +52,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     private _flush: boolean = false;
     private _ignoreStateChange = "";
     private _isPointerDown = false;
+    private _maximizedSrc: Opt<DocumentView>;
 
     constructor(props: SubCollectionViewProps) {
         super(props);
@@ -71,7 +72,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
 
     @action
-    public OpenFullScreen(document: Doc, dataDoc: Doc) {
+    public OpenFullScreen(docView: DocumentView) {
+        let document = Doc.MakeAlias(docView.props.Document);
+        let dataDoc = docView.dataDoc;
         let newItemStackConfig = {
             type: 'stack',
             content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)]
@@ -80,10 +83,25 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         this._goldenLayout.root.contentItems[0].addChild(docconfig);
         docconfig.callDownwards('_$init');
         this._goldenLayout._$maximiseItem(docconfig);
+        this._maximizedSrc = docView;
         this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         this.stateChanged();
     }
 
+    public CloseFullScreen = () => {
+        let target = this._goldenLayout._maximisedItem;
+        if (target !== null && this._maximizedSrc) {
+            this._goldenLayout._maximisedItem.remove();
+            SelectionManager.SelectDoc(this._maximizedSrc, false);
+            this._maximizedSrc = undefined;
+            this.stateChanged();
+        }
+    }
+
+    public HasFullScreen = () => {
+        return this._goldenLayout._maximisedItem !== null;
+    }
+
     @undoBatch
     @action
     public CloseRightSplit = (document: Doc): boolean => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4ac43ef4d..473e79023 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -406,8 +406,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             doc.nativeWidth = doc.nativeHeight = undefined;
         }
     }
-    fullScreenClicked = (): void => {
-        CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(Doc.MakeAlias(this.props.Document), this.dataDoc);
+    public fullScreenClicked = (): void => {
+        CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this);
         SelectionManager.DeselectAll();
     }
 
-- 
cgit v1.2.3-70-g09d2


From 6b9131637c242dafba429460f5d1bf2aa31e136f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 28 Jun 2019 21:49:48 -0400
Subject: fixed a bunch of things related to issues with templates.

---
 src/client/views/MainOverlayTextBox.tsx                     |  2 +-
 src/client/views/collections/CollectionDockingView.tsx      | 13 +++++++------
 src/client/views/collections/CollectionSchemaView.tsx       |  2 +-
 src/client/views/collections/CollectionTreeView.tsx         |  6 +++---
 src/client/views/collections/CollectionView.tsx             |  2 +-
 src/client/views/collections/ParentDocumentSelector.tsx     |  4 ++--
 .../collectionFreeForm/CollectionFreeFormView.tsx           |  2 +-
 src/client/views/nodes/DocumentView.tsx                     |  8 ++++----
 src/client/views/nodes/FieldView.tsx                        |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx                 |  2 +-
 10 files changed, 22 insertions(+), 21 deletions(-)

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

diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 1933ab94b..d31319429 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -108,7 +108,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         document.removeEventListener('pointerup', this.textBoxUp);
     }
 
-    addDocTab = (doc: Doc, dataDoc: Doc, location: string) => {
+    addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => {
         if (true) { // location === "onRight") { need to figure out stack to add "inTab"
             CollectionDockingView.Instance.AddRightSplit(doc, dataDoc);
         }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index e675e82fc..938674c48 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -66,7 +66,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         this.hack = true;
         this.undohack = UndoManager.StartBatch("goldenDrag");
         dragDocs.map((dragDoc, i) =>
-            this.AddRightSplit(dragDoc, dragDataDocs ? dragDataDocs[i] : dragDoc, true).contentItems[0].tab._dragListener.
+            this.AddRightSplit(dragDoc, dragDataDocs ? dragDataDocs[i] : undefined, true).contentItems[0].tab._dragListener.
                 onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
     }
 
@@ -167,7 +167,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         return newContentItem;
     }
     @action
-    public AddTab = (stack: any, document: Doc, dataDocument: Doc) => {
+    public AddTab = (stack: any, document: Doc, dataDocument: Doc | undefined) => {
         let docs = Cast(this.props.Document.data, listSpec(Doc));
         if (docs) {
             docs.push(document);
@@ -469,7 +469,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     constructor(props: any) {
         super(props);
         DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => {
-            this._dataDoc = this._document = f as Doc;
+            this._document = f as Doc;
             if (this.props.dataDocumentId && this.props.documentId !== this.props.dataDocumentId) {
                 DocServer.GetRefField(this.props.dataDocumentId).then(action((f: Opt<Field>) => this._dataDoc = f as Doc));
             }
@@ -510,7 +510,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
     get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
 
-    addDocTab = (doc: Doc, dataDoc: Doc, location: string) => {
+    addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => {
         if (doc.dockingConfig) {
             MainView.Instance.openWorkspace(doc);
         } else if (location === "onRight") {
@@ -520,15 +520,16 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
     }
     get content() {
-        if (!this._document || !this._dataDoc) {
+        if (!this._document) {
             return (null);
         }
+        let resolvedDataDoc = this._document.layout instanceof Doc ? this._document : this._dataDoc;
         return (
             <div className="collectionDockingView-content" ref={this._mainCont}
                 style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier})` }}>
                 <DocumentView key={this._document[Id]}
                     Document={this._document}
-                    DataDoc={this._dataDoc}
+                    DataDoc={resolvedDataDoc}
                     bringToFront={emptyFunction}
                     addDocument={undefined}
                     removeDocument={undefined}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 5562676e9..6529074f8 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -414,7 +414,7 @@ interface CollectionSchemaPreviewProps {
     removeDocument: (document: Doc) => boolean;
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
-    addDocTab: (document: Doc, dataDoc: Doc, where: string) => void;
+    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
     setPreviewScript: (script: string) => void;
     previewScript?: string;
 }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 9e0130281..dd165e90c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -36,7 +36,7 @@ export interface TreeViewProps {
     deleteDoc: (doc: Doc) => boolean;
     moveDocument: DragManager.MoveFunction;
     dropAction: "alias" | "copy" | undefined;
-    addDocTab: (doc: Doc, dataDoc: Doc, where: string) => void;
+    addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
     panelWidth: () => number;
     panelHeight: () => number;
     addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
@@ -97,7 +97,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     @undoBatch delete = () => this.props.deleteDoc(this.resolvedDataDoc);
-    @undoBatch openRight = async () => this.props.addDocTab(this.props.document, this.props.document, "onRight");
+    @undoBatch openRight = async () => this.props.addDocTab(this.props.document, undefined, "onRight");
 
     onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
     onPointerEnter = (e: React.PointerEvent): void => {
@@ -369,7 +369,7 @@ class TreeView extends React.Component<TreeViewProps> {
         remove: ((doc: Doc) => boolean),
         move: DragManager.MoveFunction,
         dropAction: dropActionType,
-        addDocTab: (doc: Doc, dataDoc: Doc, where: string) => void,
+        addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void,
         screenToLocalXf: () => Transform,
         outerXf: () => { translateX: number, translateY: number },
         active: () => boolean,
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 0517c17bf..e500e5c70 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -63,7 +63,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
                     otherdoc.height = 50;
                     Doc.GetProto(otherdoc).title = "applied(" + this.props.Document.title + ")";
                     Doc.GetProto(otherdoc).layout = Doc.MakeDelegate(this.props.Document);
-                    this.props.addDocTab && this.props.addDocTab(otherdoc, otherdoc, "onRight");
+                    this.props.addDocTab && this.props.addDocTab(otherdoc, undefined, "onRight");
                 }), icon: "project-diagram"
             });
         }
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index fa7058d5a..b29a30069 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -9,7 +9,7 @@ import { CollectionDockingView } from "./CollectionDockingView";
 import { NumCast } from "../../../new_fields/Types";
 import { CollectionViewType } from "./CollectionBaseView";
 
-type SelectorProps = { Document: Doc, addDocTab(doc: Doc, dataDoc: Doc, location: string): void };
+type SelectorProps = { Document: Doc, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void };
 @observer
 export class SelectorContextMenu extends React.Component<SelectorProps> {
     @observable private _docs: { col: Doc, target: Doc }[] = [];
@@ -43,7 +43,7 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
                 col.panX = newPanX;
                 col.panY = newPanY;
             }
-            this.props.addDocTab(col, col, "inTab"); // bcz: dataDoc?
+            this.props.addDocTab(col, undefined, "inTab"); // bcz: dataDoc?
         };
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bbec33ba3..4c8fe3c26 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -340,7 +340,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         let resolvedDataDoc = this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
         let layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
         return {
-            DataDoc: resolvedDataDoc !== layoutDoc && resolvedDataDoc ? resolvedDataDoc : layoutDoc,
+            DataDoc: resolvedDataDoc !== layoutDoc && resolvedDataDoc ? resolvedDataDoc : undefined,
             Document: layoutDoc,
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2ee694225..ada28d57b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -84,7 +84,7 @@ export interface DocumentViewProps {
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     bringToFront: (doc: Doc) => void;
-    addDocTab: (doc: Doc, dataDoc: Doc, where: string) => void;
+    addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
     collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
     zoomToScale: (scale: number) => void;
     getScale: () => number;
@@ -254,7 +254,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 DocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
             }
             let isMinimized: boolean | undefined;
-            expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
+            expandedDocs.map(maximizedDoc => {
                 let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
                 if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
                     if (isMinimized === undefined) {
@@ -321,7 +321,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         if (dataDocs) {
                             expandedDocs.forEach(maxDoc =>
                                 (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) &&
-                                    this.props.addDocTab(getDispDoc(maxDoc), getDispDoc(maxDoc), maxLocation)));
+                                    this.props.addDocTab(getDispDoc(maxDoc), undefined, maxLocation)));
                         }
                     } else {
                         let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2);
@@ -351,7 +351,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     //     if (!linkedFwdDocs.some(l => l instanceof Promise)) {
                     //         let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab");
                     //         let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
-                    //         DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, document => this.props.addDocTab(document, document, maxLocation), linkedFwdPage[altKey ? 1 : 0], targetContext);
+                    //         DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, document => this.props.addDocTab(document, undefined, maxLocation), linkedFwdPage[altKey ? 1 : 0], targetContext);
                     //     }
                 }
             }
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 7c8509722..89a8cad56 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -39,7 +39,7 @@ export interface FieldViewProps {
     renderDepth: number;
     selectOnLoad: boolean;
     addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
-    addDocTab: (document: Doc, dataDoc: Doc, where: string) => void;
+    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
     removeDocument?: (document: Doc) => boolean;
     moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ba6808737..07cd43ce3 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -281,7 +281,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
                     if (this._linkClicked) {
                         DocServer.GetRefField(this._linkClicked).then(f => {
-                            (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, false, document => this.props.addDocTab(document, document, "inTab"));
+                            (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, false, document => this.props.addDocTab(document, undefined, "inTab"));
                         });
                         e.stopPropagation();
                         e.preventDefault();
-- 
cgit v1.2.3-70-g09d2


From 4ae406a219a53d35d7f4a621b41ec7901f4413f0 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 29 Jun 2019 09:50:28 -0400
Subject: fixed dockingview tab issues caused by template refactor

---
 src/client/views/collections/CollectionDockingView.tsx | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 64e0ececb..13b0579d2 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -24,7 +24,6 @@ import { SubCollectionViewProps } from "./CollectionSubView";
 import { ParentDocSelector } from './ParentDocumentSelector';
 import React = require("react");
 import { MainView } from '../MainView';
-import { LinkManager } from '../../util/LinkManager';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faFile } from '@fortawesome/free-solid-svg-icons';
@@ -367,7 +366,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             }
             let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId) as Doc;
             let dataDoc = await DocServer.GetRefField(tab.contentItem.config.props.dataDocumentId) as Doc;
-            if (doc instanceof Doc && dataDoc instanceof Doc) {
+            if (doc instanceof Doc) {
                 let dragSpan = document.createElement("span");
                 dragSpan.style.position = "relative";
                 dragSpan.style.bottom = "6px";
-- 
cgit v1.2.3-70-g09d2


From d101b95f647f39fe50a97d332cb107ae84d25759 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 1 Jul 2019 14:30:31 -0400
Subject: Added a tooltip

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

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 13b0579d2..a97fd1e69 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -383,7 +383,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     }
                     tab.setActive(true);
                 };
-                ReactDOM.render(<span onPointerDown={
+                ReactDOM.render(<span title="Drag as document" onPointerDown={
                     e => {
                         e.preventDefault();
                         e.stopPropagation();
-- 
cgit v1.2.3-70-g09d2


From 055b823ac472c3831d9cca9629cd2c505310c664 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 1 Jul 2019 22:02:56 -0400
Subject: added library view pullout

---
 src/.DS_Store                                      | Bin 6148 -> 6148 bytes
 src/client/views/Main.scss                         |  16 +++
 src/client/views/MainView.tsx                      | 117 ++++++++++++++++-----
 .../views/collections/CollectionDockingView.tsx    |  10 +-
 4 files changed, 116 insertions(+), 27 deletions(-)

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

diff --git a/src/.DS_Store b/src/.DS_Store
index ff00f097e..fc746835f 100644
Binary files a/src/.DS_Store and b/src/.DS_Store differ
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 0271edcd2..59c70c674 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -224,4 +224,20 @@ ul#add-options-list {
         display: inline-block;
         padding: 0;
     }
+}
+
+.mainView-libraryFlyout {
+    height: 100%;
+    position: absolute;
+}
+
+.mainView-libraryHandle {
+    opacity: 0.6;
+    width: 20px;
+    height: 40px;
+    top: 50%;
+    border-radius: 20px;
+    position: absolute;
+    z-index: 1;
+    background: gray;
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d28e3b837..654175523 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -160,12 +160,13 @@ export class MainView extends React.Component {
         }
     }
 
+
     @action
     createNewWorkspace = async (id?: string) => {
         const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
         if (list) {
             let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` });
-            var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] };
+            var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] };
             let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` }, id);
             if (!CurrentUserUtils.UserDocument.linkManagerDoc) {
                 let linkManagerDoc = new Doc();
@@ -224,40 +225,106 @@ export class MainView extends React.Component {
     getPHeight = () => {
         return this.pheight;
     }
-    @computed
-    get mainContent() {
+
+    @observable flyoutWidth: number = 250;
+    @computed get dockingContent() {
+        let flyoutWidth = this.flyoutWidth;
         let mainCont = this.mainContainer;
-        let content = !mainCont ? (null) :
-            <DocumentView Document={mainCont}
-                DataDoc={undefined}
-                addDocument={undefined}
-                addDocTab={emptyFunction}
-                removeDocument={undefined}
-                ScreenToLocalTransform={Transform.Identity}
-                ContentScaling={returnOne}
-                PanelWidth={this.getPWidth}
-                PanelHeight={this.getPHeight}
-                renderDepth={0}
-                selectOnLoad={false}
-                focus={emptyFunction}
-                parentActive={returnTrue}
-                whenActiveChanged={emptyFunction}
-                bringToFront={emptyFunction}
-                ContainingCollectionView={undefined}
-                zoomToScale={emptyFunction}
-                getScale={returnOne}
-            />;
         let castRes = mainCont ? FieldValue(Cast(mainCont.presentationView, listSpec(Doc))) : undefined;
         return <Measure offset onResize={this.onResize}>
             {({ measureRef }) =>
-                <div ref={measureRef} id="mainContent-div" onDrop={this.onDrop}>
-                    {content}
+                <div ref={measureRef} id="mainContent-div" style={{ width: `calc(100% - ${flyoutWidth}px`, transform: `translate(${flyoutWidth}px, 0px)` }} onDrop={this.onDrop}>
+                    {!mainCont ? (null) :
+                        <DocumentView Document={mainCont}
+                            DataDoc={undefined}
+                            addDocument={undefined}
+                            addDocTab={emptyFunction}
+                            removeDocument={undefined}
+                            ScreenToLocalTransform={Transform.Identity}
+                            ContentScaling={returnOne}
+                            PanelWidth={this.getPWidth}
+                            PanelHeight={this.getPHeight}
+                            renderDepth={0}
+                            selectOnLoad={false}
+                            focus={emptyFunction}
+                            parentActive={returnTrue}
+                            whenActiveChanged={emptyFunction}
+                            bringToFront={emptyFunction}
+                            ContainingCollectionView={undefined}
+                            zoomToScale={emptyFunction}
+                            getScale={returnOne}
+                        />}
                     {castRes ? <PresentationView Documents={castRes} key="presentation" /> : null}
                 </div>
             }
         </Measure>;
     }
 
+    _downsize = 0;
+    onPointerDown = (e: React.PointerEvent) => {
+        this._downsize = e.clientX;
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        document.addEventListener("pointermove", this.onPointerMove);
+        document.addEventListener("pointerup", this.onPointerUp);
+        e.stopPropagation();
+        e.preventDefault();
+    }
+    @action
+    onPointerMove = (e: PointerEvent) => {
+        this.flyoutWidth = e.clientX;
+    }
+    @action
+    onPointerUp = (e: PointerEvent) => {
+        if (Math.abs(e.clientX - this._downsize) < 4) {
+            if (this.flyoutWidth < 5) this.flyoutWidth = 250;
+            else this.flyoutWidth = 0;
+        }
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+    }
+    @computed
+    get mainContent() {
+        let addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => {
+            if (doc.dockingConfig) {
+                this.openWorkspace(doc);
+            } else {
+                // CollectionDockingView.Instance.AddRightSplit(doc, dataDoc);
+            }
+        }
+        let flyout = <DocumentView
+            Document={CurrentUserUtils.UserDocument}
+            DataDoc={undefined}
+            addDocument={undefined}
+            addDocTab={(doc: Doc) => addDocTab(doc, undefined, "onRight")}
+            removeDocument={undefined}
+            ScreenToLocalTransform={Transform.Identity}
+            ContentScaling={returnOne}
+            PanelWidth={this.getPWidth}
+            PanelHeight={this.getPHeight}
+            renderDepth={0}
+            selectOnLoad={false}
+            focus={emptyFunction}
+            parentActive={returnTrue}
+            whenActiveChanged={emptyFunction}
+            bringToFront={emptyFunction}
+            ContainingCollectionView={undefined}
+            zoomToScale={emptyFunction}
+            getScale={returnOne}>
+        </DocumentView>;
+        return <div>
+            <div className="mainView-libraryHandle"
+                style={{ left: `${this.flyoutWidth - 10}px` }}
+                onPointerDown={this.onPointerDown}>
+                <span title="library View Dragger" style={{ width: "100%", height: "100%", position: "absolute" }} />
+            </div>
+            <div className="mainView-libraryFlyout" style={{ width: `${this.flyoutWidth}px` }}>
+                {flyout}
+            </div>
+            {this.dockingContent}
+        </div>;
+    }
+
     selected = (tool: InkTool) => {
         if (!InkingControl.Instance || InkingControl.Instance.selectedTool === InkTool.None) return { display: "none" };
         if (InkingControl.Instance.selectedTool === tool) {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index a97fd1e69..ad5472a4a 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -456,8 +456,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     render() {
         return (
-            <div className="collectiondockingview-container" id="menuContainer"
-                onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
+            <Measure offset onResize={this.onResize}>
+                {({ measureRef }) =>
+                    <div ref={measureRef}>
+                        <div className="collectiondockingview-container" id="menuContainer"
+                            onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
+                    </div>
+                }
+            </Measure>
         );
     }
 
-- 
cgit v1.2.3-70-g09d2


From 16822d88f981e7a86729d942a92b6ad8590680e4 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 1 Jul 2019 22:32:44 -0400
Subject: fixed title template view issue

---
 src/client/views/TemplateMenu.tsx                      | 8 ++++++++
 src/client/views/Templates.tsx                         | 4 ++--
 src/client/views/collections/CollectionDockingView.tsx | 2 +-
 src/client/views/nodes/DocumentView.tsx                | 5 +++++
 4 files changed, 16 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index d63f45e5a..1b32f0ddd 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -64,6 +64,13 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         }
     }
 
+    @undoBatch
+    @action
+    clearTemplates = (event: React.MouseEvent) => {
+        this.props.docs.map(d => d.clearTemplates());
+        Array.from(this.props.templates.keys()).map(t => this.props.templates.set(t, false));
+    }
+
     @action
     componentWillReceiveProps(nextProps: TemplateMenuProps) {
         // this._templates = nextProps.templates;
@@ -84,6 +91,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 <div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
                 <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
                     {templateMenu}
+                    <button style={{ display: this._hidden ? "none" : "block" }} onClick={this.clearTemplates}>Clear</button>
                 </ul>
             </div>
         );
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 5bb8d454a..4843a70a6 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -50,7 +50,7 @@ export namespace Templates {
     export const Title = new Template("Title", TemplatePosition.InnerTop,
         `<div>
             <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; z-index: 100">
-                <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.DataDoc.title}</span>
+                <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 style="height:calc(100% - 25px);">
                 <div style="width:100%;overflow:auto">{layout}</div>
@@ -58,7 +58,7 @@ export namespace Templates {
         </div>` );
 
     export const Header = new Template("Header", TemplatePosition.InnerTop,
-        `< div style = "display:flex; flex-direction:column; height:100%;" >
+        `<div style = "display:flex; flex-direction:column; height:100%;" >
             <div style="width:100%; background-color: rgba(0, 0, 0, .4); color: white; ">
                 <FormattedTextBox {...props} height={"min-content"} color={"white"} fieldKey={"header"} />
             </div>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index ad5472a4a..8724216f5 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -269,7 +269,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         var cur = this._containerRef.current;
 
         // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
-        this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
+        this._goldenLayout && this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
     }
 
     @action
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 687c7f73d..3673cb0e2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -473,6 +473,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         this.templates = this.templates;
     }
+    @action
+    clearTemplates = () => {
+        this.templates.length = 0;
+        this.templates = this.templates;
+    }
 
     @undoBatch
     @action
-- 
cgit v1.2.3-70-g09d2


From 20e57b499d309052d4a12ef2d6a2288eec8bf04f Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 3 Jul 2019 19:05:41 -0400
Subject: Couple of fixes

---
 src/client/views/collections/CollectionDockingView.tsx | 3 +++
 src/client/views/search/FilterBox.tsx                  | 2 +-
 src/client/views/search/SearchItem.tsx                 | 2 +-
 3 files changed, 5 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 8724216f5..8b6f5b6a6 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -455,6 +455,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
 
     render() {
+        if (this.props.renderDepth > 0) {
+            return <div style={{ width: "100%", height: "100%" }}>Nested workspaces can't be rendered</div>;
+        }
         return (
             <Measure offset onResize={this.onResize}>
                 {({ measureRef }) =>
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 02789fb27..23a1b31d8 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -58,7 +58,7 @@ export class FilterBox extends React.Component {
     componentDidMount = () => {
         document.addEventListener("pointerdown", (e) => {
             if (!e.defaultPrevented && e.timeStamp !== this._pointerTime) {
-                // SearchBox.Instance.closeSearch();
+                SearchBox.Instance.closeSearch();
             }
         });
     }
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 7d45f9dfb..601f4032d 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -179,7 +179,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
     }
 
     @action
-    pointerDown = (e: React.PointerEvent) => { e.preventDefault; e.button === 0 && SearchBox.Instance.openSearch(e); }
+    pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); }
 
     highlightDoc = (e: React.PointerEvent) => {
         if (this.props.doc.type === DocTypes.LINK) {
-- 
cgit v1.2.3-70-g09d2


From 575ee72cd6ddd65d6c02c6f50a3266f632b1b858 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 4 Jul 2019 00:17:15 -0400
Subject: highlights current workspace in treeview

---
 src/client/views/collections/CollectionDockingView.tsx    | 2 ++
 src/client/views/collections/CollectionTreeView.tsx       | 3 ++-
 src/client/views/nodes/DocumentView.tsx                   | 4 ++--
 src/client/views/presentationview/PresentationElement.tsx | 2 +-
 4 files changed, 7 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 8b6f5b6a6..c5f8fb728 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -240,6 +240,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         // Because this is in a set timeout, if this component unmounts right after mounting,
                         // we will leak a GoldenLayout, because we try to destroy it before we ever create it
                         setTimeout(() => this.setupGoldenLayout(), 1);
+                        this.props.Document.workspaceBrush = true;
                     }
                     this._ignoreStateChange = "";
                 }, { fireImmediately: true });
@@ -249,6 +250,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
     componentWillUnmount: () => void = () => {
         try {
+            this.props.Document.workspaceBrush = false;
             this._goldenLayout.unbind('itemDropped', this.itemDropped);
             this._goldenLayout.unbind('tabCreated', this.tabCreated);
             this._goldenLayout.unbind('stackCreated', this.stackCreated);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 622ba37d1..3db786043 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -217,7 +217,8 @@ class TreeView extends React.Component<TreeViewProps> {
         return <>
             <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
                 style={{
-                    background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0",
+                    background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0",
+                    outline: BoolCast(this.props.document.workspaceBrush, false) ? "dashed 1px #06123232" : undefined,
                     pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
                 }}
             >
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 69dc31fec..f751a45fe 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -589,9 +589,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 style={{
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    outlineWidth: (BoolCast(this.props.Document.libraryBrush) || BoolCast(this.props.Document.protoBrush)) && !NumCast(this.props.Document.borderRounding) ?
+                    outlineWidth: BoolCast(this.props.Document.libraryBrush) && !NumCast(this.props.Document.borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
-                    border: (BoolCast(this.props.Document.libraryBrush) || BoolCast(this.props.Document.protoBrush)) && NumCast(this.props.Document.borderRounding) ?
+                    border: BoolCast(this.props.Document.libraryBrush) && NumCast(this.props.Document.borderRounding) ?
                         `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
                     borderRadius: "inherit",
                     background: backgroundColor,
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index d63c0b066..6896ee452 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -377,7 +377,7 @@ export default class PresentationElement extends React.Component<PresentationEle
                 style={{
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    outlineWidth: BoolCast(p.document.libraryBrush, false) || BoolCast(p.document.protoBrush, false) ? `1px` : "0px",
+                    outlineWidth: BoolCast(p.document.libraryBrush, false) ? `1px` : "0px",
                 }}
                 onClick={e => { p.gotoDocument(p.index, NumCast(this.props.mainDocument.selectedDoc)); e.stopPropagation(); }}>
                 <strong className="presentationView-name">
-- 
cgit v1.2.3-70-g09d2


From 1b02511185e22349e68bab1f6b8ce74b874a3ba5 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Thu, 4 Jul 2019 19:54:27 -0400
Subject: Made some changes to clean up some GoldenLayout stuff

---
 src/client/goldenLayout.js                         | 11 ++++
 src/client/util/DragManager.ts                     |  1 +
 src/client/views/_nodeModuleOverrides.scss         | 18 +++----
 .../views/collections/CollectionDockingView.tsx    | 58 ++++++++++------------
 src/client/views/nodes/DocumentView.tsx            | 20 ++++----
 src/client/views/search/SearchItem.tsx             |  2 +-
 6 files changed, 57 insertions(+), 53 deletions(-)

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

diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index 54c9c6068..28c009645 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -2271,6 +2271,17 @@
             this._dragListener.on('dragStop', this._createDragListener, this);
         },
 
+        destroy: function () {
+            this._dragListener.destroy();
+            this._element = null;
+            this._itemConfig = null;
+            this._dragListener = null;
+            const index = this._layoutManager._dragSources.indexOf(this);
+            if (index > -1) {
+                this._layoutManager._dragSources.splice(index, 1);
+            }
+        },
+
 		/**
 		 * Callback for the DragListener's dragStart event
 		 *
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index ce1f9d890..2fd83ae56 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -43,6 +43,7 @@ export function SetupDrag(
             e.stopPropagation();
             e.preventDefault();
             if (e.shiftKey && CollectionDockingView.Instance) {
+                e.persist();
                 CollectionDockingView.Instance.StartOtherDrag(e, [await docFunc()]);
             } else {
                 document.addEventListener("pointermove", onRowMove);
diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss
index 3594ac9f4..b8a7db034 100644
--- a/src/client/views/_nodeModuleOverrides.scss
+++ b/src/client/views/_nodeModuleOverrides.scss
@@ -2,21 +2,21 @@
 
 // goldenlayout stuff
 div .lm_header {
-  background: $dark-color;
+    background: $dark-color;
 }
 
 .lm_tab {
-  margin-top: 0.6em !important;
-  padding-top: 0.5em !important;
-  min-height: 1.35em;
-  padding-bottom: 0px;
-  border-radius: 5px;
-  font-family: $sans-serif !important;
+    //   margin-top: 0.6em !important;
+    //   padding-top: 0.5em !important;
+    //   min-height: 1.35em;
+    //   padding-bottom: 0px;
+    //   border-radius: 5px;
+    font-family: $sans-serif !important;
 }
 
 .lm_header .lm_controls {
-  right: 1em !important;
+    right: 1em !important;
 }
 
 // @TODO the ril__navgiation buttons in the img gallery are a lil messed up but I can't figure out
-// why. Low priority for now but it's bugging me. --Julie
+// why. Low priority for now but it's bugging me. --Julie
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c5f8fb728..f146ec62b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -62,12 +62,30 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
     hack: boolean = false;
     undohack: any = null;
-    public StartOtherDrag(e: any, dragDocs: Doc[], dragDataDocs?: (Doc | undefined)[]) {
-        this.hack = true;
-        this.undohack = UndoManager.StartBatch("goldenDrag");
-        dragDocs.map((dragDoc, i) =>
-            this.AddRightSplit(dragDoc, dragDataDocs ? dragDataDocs[i] : undefined, true).contentItems[0].tab._dragListener.
-                onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
+    public StartOtherDrag(e: any, dragDocs: Doc[], dragDataDocs: (Doc | undefined)[] = []) {
+        let config: any;
+        if (dragDocs.length === 1) {
+            config = CollectionDockingView.makeDocumentConfig(dragDocs[0], dragDataDocs[0]);
+        } else {
+            config = {
+                type: 'row',
+                content: dragDocs.map((doc, i) => {
+                    CollectionDockingView.makeDocumentConfig(doc, dragDataDocs[i]);
+                })
+            };
+        }
+        const div = document.createElement("div");
+        const dragSource = this._goldenLayout.createDragSource(div, config);
+        dragSource._dragListener.on("dragStop", () => {
+            dragSource.destroy();
+        });
+        dragSource._dragListener.onMouseDown(e);
+        // dragSource.destroy();
+        // this.hack = true;
+        // this.undohack = UndoManager.StartBatch("goldenDrag");
+        // dragDocs.map((dragDoc, i) =>
+        //     this.AddRightSplit(dragDoc, dragDataDocs[i], true).contentItems[0].tab._dragListener.
+        //         onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
     }
 
     @action
@@ -299,33 +317,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             let tab = (e.target as any).parentElement as HTMLElement;
             DocServer.GetRefField(docid).then(action(async (sourceDoc: Opt<Field>) =>
                 (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
-        } else
-            if ((className === "lm_title" || className === "lm_tab lm_active") && e.shiftKey) {
-                e.stopPropagation();
-                e.preventDefault();
-                let x = e.clientX;
-                let y = e.clientY;
-                let docid = (e.target as any).DashDocId;
-                let datadocid = (e.target as any).DashDataDocId;
-                let tab = (e.target as any).parentElement as HTMLElement;
-                let glTab = (e.target as any).Tab;
-                if (glTab && glTab.contentItem && glTab.contentItem.parent) {
-                    glTab.contentItem.parent.setActiveContentItem(glTab.contentItem);
-                }
-                DocServer.GetRefField(docid).then(action(async (f: Opt<Field>) => {
-                    if (f instanceof Doc) {
-                        let dataDoc = (datadocid !== docid) ? await DocServer.GetRefField(datadocid) : f;
-                        DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f], [dataDoc instanceof Doc ? dataDoc : f]), x, y,
-                            {
-                                handlers: {
-                                    dragComplete: emptyFunction,
-                                },
-                                hideSource: false,
-                                withoutShiftDrag: true
-                            });
-                    }
-                }));
-            }
+        }
         if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
             this._flush = true;
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a84cac37f..2610d0e6d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -362,16 +362,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
-        if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) {
-            CollectionDockingView.Instance.StartOtherDrag(e, [Doc.MakeAlias(this.props.Document)], [this.dataDoc]);
-            e.stopPropagation();
-        } else {
-            if (this.active) e.stopPropagation(); // events stop at the lowest document that is active.  
-            document.removeEventListener("pointermove", this.onPointerMove);
-            document.addEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            document.addEventListener("pointerup", this.onPointerUp);
-        }
+        // if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) {
+        // CollectionDockingView.Instance.StartOtherDrag(e, [Doc.MakeAlias(this.props.Document)], [this.dataDoc]);
+        // e.stopPropagation();
+        // } else {
+        if (this.active) e.stopPropagation(); // events stop at the lowest document that is active.  
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.addEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        document.addEventListener("pointerup", this.onPointerUp);
+        // }
     }
     onPointerMove = (e: PointerEvent): void => {
         if (!e.cancelBubble && this.active) {
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 79a06c81a..606ac4e85 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -231,7 +231,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
 
     onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
         e.stopPropagation();
-        e.preventDefault();
+        // e.preventDefault();
         const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc;
         DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc], []), e.clientX, e.clientY, {
             handlers: { dragComplete: emptyFunction },
-- 
cgit v1.2.3-70-g09d2


From ad87140f4a52984d754c06fcb55e66a0cc935ce2 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 4 Jul 2019 23:18:13 -0400
Subject: fixed shift-dragging of documents

---
 src/client/util/DragManager.ts                         | 1 -
 src/client/views/DocumentDecorations.tsx               | 1 -
 src/client/views/collections/CollectionDockingView.tsx | 3 ---
 src/client/views/collections/CollectionSchemaView.tsx  | 1 -
 src/client/views/nodes/ImageBox.tsx                    | 2 +-
 src/client/views/nodes/VideoBox.tsx                    | 1 -
 src/client/views/search/SearchItem.tsx                 | 1 -
 7 files changed, 1 insertion(+), 9 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 2fd83ae56..f9492f452 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -41,7 +41,6 @@ export function SetupDrag(
     let onItemDown = async (e: React.PointerEvent) => {
         if (e.button === 0) {
             e.stopPropagation();
-            e.preventDefault();
             if (e.shiftKey && CollectionDockingView.Instance) {
                 e.persist();
                 CollectionDockingView.Instance.StartOtherDrag(e, [await docFunc()]);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c7990647a..48b89ff4e 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -172,7 +172,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         document.addEventListener("pointermove", this.onBackgroundMove);
         document.addEventListener("pointerup", this.onBackgroundUp);
         e.stopPropagation();
-        e.preventDefault();
     }
 
     @action
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f146ec62b..19d07ecdc 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -321,9 +321,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
             this._flush = true;
         }
-        if (this.props.active()) {
-            e.stopPropagation();
-        }
     }
 
     @undoBatch
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 568949efb..b54e8aff0 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -235,7 +235,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
             if (this.props.isSelected()) e.stopPropagation();
-            else e.preventDefault();
         }
     }
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index dd4e4262d..5da52f4c2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -95,7 +95,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.shiftKey && e.ctrlKey) {
             e.stopPropagation(); // allows default system drag drop of images with shift+ctrl only
-        } else e.preventDefault();
+        }
         // if (Date.now() - this._lastTap < 300) {
         //     if (e.buttons === 1) {
         //         this._downX = e.clientX;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 1239b498f..264d3c1f7 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -119,7 +119,6 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         });
     }
     onPointerDown = (e: React.PointerEvent) => {
-        e.preventDefault();
         e.stopPropagation();
     }
 
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 606ac4e85..51f0ed607 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -231,7 +231,6 @@ export class SearchItem extends React.Component<SearchItemProps> {
 
     onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
         e.stopPropagation();
-        // e.preventDefault();
         const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc;
         DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc], []), e.clientX, e.clientY, {
             handlers: { dragComplete: emptyFunction },
-- 
cgit v1.2.3-70-g09d2


From 8b9fa667816378cc6cae2977f1c2d80c204b28d0 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 5 Jul 2019 14:45:15 -0400
Subject: added more options to sidebar

---
 src/client/views/Main.scss                         | 18 ++++++++++++++
 src/client/views/MainView.tsx                      | 28 ++++++++++++++++++----
 .../views/collections/CollectionDockingView.tsx    |  4 ++++
 .../views/collections/CollectionTreeView.tsx       |  7 ++++--
 src/new_fields/Doc.ts                              | 11 +++++----
 .../authentication/models/current_user_utils.ts    | 10 ++++++++
 6 files changed, 67 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index b85a8040a..f52e3b658 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -230,6 +230,8 @@ ul#add-options-list {
 .mainView-libraryFlyout {
     height: 100%;
     position: absolute;
+    display: flex;
+    flex-direction:column;
 }
 
 .mainView-libraryHandle {
@@ -241,4 +243,20 @@ ul#add-options-list {
     position: absolute;
     z-index: 1;
     background: gray;
+}
+
+.mainView-workspace {
+    height:200px;
+    position:relative;
+    display:flex;
+}
+.mainView-library {
+    height:75%;
+    position:relative;
+    display:flex;
+}
+.mainView-recentlyClosed {
+    height:25%;
+    position:relative;
+    display:flex;
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 61ccf4c1d..92468e3fd 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -8,12 +8,12 @@ import * as React from 'react';
 import { SketchPicker } from 'react-color';
 import Measure from 'react-measure';
 import * as request from 'request';
-import { Doc, DocListCast, Opt } from '../../new_fields/Doc';
+import { Doc, DocListCast, Opt, HeightSym } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
 import { InkTool } from '../../new_fields/InkField';
 import { List } from '../../new_fields/List';
 import { listSpec } from '../../new_fields/Schema';
-import { Cast, FieldValue } from '../../new_fields/Types';
+import { Cast, FieldValue, NumCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
 import { emptyFunction, returnOne, returnTrue } from '../../Utils';
@@ -163,7 +163,9 @@ export class MainView extends React.Component {
 
     @action
     createNewWorkspace = async (id?: string) => {
-        const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
+        let workspaces = Cast(CurrentUserUtils.UserDocument.workspaces, Doc);
+        if (!(workspaces instanceof Doc)) return;
+        const list = Cast((CurrentUserUtils.UserDocument.workspaces as Doc).data, listSpec(Doc));
         if (list) {
             let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` });
             var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] };
@@ -285,8 +287,24 @@ export class MainView extends React.Component {
     };
     @computed
     get flyout() {
+        let sidebar = CurrentUserUtils.UserDocument.sidebar;
+        let workspaces = CurrentUserUtils.UserDocument.workspaces;
+        let recent = CurrentUserUtils.UserDocument.recentlyClosed;
+        if (!(sidebar instanceof Doc)) return (null);
+        if (!(recent instanceof Doc)) return (null);
+        if (!(workspaces instanceof Doc)) return (null);
+        let workspacesDoc = workspaces as Doc;
+        let sidebarDoc = sidebar as Doc;
+        let recentDoc = recent as Doc;
+        let library = CurrentUserUtils.UserDocument;
+        let gridGap = NumCast(sidebar.gridGap, 10);
+        let yMargin = NumCast(sidebar.yMargin, 2 * gridGap);
+        let libraryHeight = this.getPHeight() - workspacesDoc[HeightSym]() - recentDoc[HeightSym]() - 2 * gridGap - 2 * yMargin;
+        if (library[HeightSym]() != libraryHeight) {
+            setTimeout(() => CurrentUserUtils.UserDocument.height = libraryHeight, 0);
+        }
         return <DocumentView
-            Document={CurrentUserUtils.UserDocument}
+            Document={sidebarDoc}
             DataDoc={undefined}
             addDocument={undefined}
             addDocTab={this.addDocTabFunc}
@@ -304,7 +322,7 @@ export class MainView extends React.Component {
             ContainingCollectionView={undefined}
             zoomToScale={emptyFunction}
             getScale={returnOne}>
-        </DocumentView>;
+        </DocumentView>
     }
     @computed
     get mainContent() {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c5f8fb728..e0270fab3 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -27,6 +27,7 @@ import { MainView } from '../MainView';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faFile } from '@fortawesome/free-solid-svg-icons';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 library.add(faFile);
 
 @observer
@@ -416,6 +417,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 if (doc instanceof Doc) {
                     let theDoc = doc;
                     CollectionDockingView.Instance._removedDocs.push(theDoc);
+                    if (CurrentUserUtils.UserDocument.recentlyClosed instanceof Doc) {
+                        Doc.AddDocToList(CurrentUserUtils.UserDocument.recentlyClosed, "data", doc, undefined, true, true);
+                    }
                     SelectionManager.DeselectAll();
                 }
                 tab.contentItem.remove();
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 93a1ec1eb..c83a2d2c6 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -471,9 +471,12 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     }
     onContextMenu = (e: React.MouseEvent): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
-        if (!e.isPropagationStopped() && this.props.Document.excludeFromLibrary) { // excludeFromLibrary means this is the user document
+        if (!e.isPropagationStopped() && this.props.Document.workspaceLibrary) { // excludeFromLibrary means this is the user document
             ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => MainView.Instance.createNewWorkspace()) });
             ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)) });
+            e.stopPropagation();
+            e.preventDefault();
+            ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
         }
     }
 
@@ -531,7 +534,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                         TreeView.loadId = doc[Id];
                         Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
                     }} />
-                {this.props.Document.excludeFromLibrary ? this.notifsButton : (null)}
+                {this.props.Document.workspaceLibrary ? this.notifsButton : (null)}
                 <ul className="no-indent" style={{ width: "max-content" }} >
                     {
                         TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove,
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index c361e3032..092205f52 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -241,12 +241,15 @@ export namespace Doc {
         return Array.from(results);
     }
 
-    export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean) {
+    export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean) {
         let list = Cast(target[key], listSpec(Doc));
         if (list) {
-            let ind = relativeTo ? list.indexOf(relativeTo) : -1;
-            if (ind === -1) list.push(doc);
-            else list.splice(before ? ind : ind + 1, 0, doc);
+            if (first) list.splice(0, 0, doc);
+            else {
+                let ind = relativeTo ? list.indexOf(relativeTo) : -1;
+                if (ind === -1) list.push(doc);
+                else list.splice(before ? ind : ind + 1, 0, doc);
+            }
         }
         return true;
     }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 816c5f269..3134ecaff 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -31,6 +31,16 @@ export class CurrentUserUtils {
         doc.viewType = CollectionViewType.Tree;
         doc.dropAction = "alias";
         doc.layout = CollectionView.LayoutString();
+        doc.workspaces = Docs.TreeDocument([], { title: "Workspaces", height: 100 });
+        (doc.workspaces as Doc).excludeFromLibrary = true;
+        (doc.workspaces as Doc).workspaceLibrary = true;
+        doc.recentlyClosed = Docs.TreeDocument([], { title: "Recently Closed", height: 75 });
+        (doc.workspaces as Doc).excludeFromLibrary = true;
+        doc.sidebar = Docs.StackingDocument([doc.workspaces as Doc, doc, doc.recentlyClosed as Doc], { title: "Sidebar" });
+        (doc.sidebar as Doc).excludeFromLibrary = true;
+        (doc.sidebar as Doc).gridGap = 5;
+        (doc.sidebar as Doc).xMargin = 5;
+        (doc.sidebar as Doc).yMargin = 5;
         doc.title = this.email;
         doc.data = new List<Doc>();
         doc.excludeFromLibrary = true;
-- 
cgit v1.2.3-70-g09d2


From 892608273cdfeba4cfb55c5c604bee4361b3be0e Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 10 Jul 2019 11:53:00 -0400
Subject: documentation tweaks

---
 src/client/DocServer.ts                                | 13 +++++++------
 src/client/documents/Documents.ts                      |  6 +++---
 src/client/views/collections/CollectionDockingView.tsx |  8 ++++----
 3 files changed, 14 insertions(+), 13 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 3b33657eb..067d89c51 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -146,9 +146,10 @@ export namespace DocServer {
         const promises: Promise<Opt<RefField>>[] = [];
         const map: { [id: string]: Opt<RefField> } = {};
 
-        // 1) an initial pass through the cache to determine which documents need to be fetched,
-        // which are already in the process of being fetched and which already exist in the
-        // cache
+        // 1) an initial pass through the cache to determine
+        // i) which documents need to be fetched
+        // ii) which are already in the process of being fetched
+        // iii) which already exist in the cache
         for (const id of ids) {
             const cached = _cache[id];
             if (cached === undefined) {
@@ -204,7 +205,7 @@ export namespace DocServer {
 
         // 6) with this confidence, we can now go through and update the cache at the ids of the fields that
         // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given
-        // id to the soon to be returned field mapping.
+        // id to the soon-to-be-returned field mapping.
         requestedIds.forEach(id => {
             const field = fields[id];
             // either way, overwrite or delete any promises (that we inserted as flags
@@ -232,8 +233,8 @@ export namespace DocServer {
 
         // now, we return our completed mapping from all of the ids that were passed into the method
         // to their actual RefField | undefined values. This return value either becomes the input
-        // argument to the caller's promise (i.e. GetRefFields.then(map => //do something with map...))
-        // or it is the direct return result if the promise is awaited.
+        // argument to the caller's promise (i.e. GetRefFields(["_id1_", "_id2_", "_id3_"]).then(map => //do something with map...))
+        // or it is the direct return result if the promise is awaited (i.e. let fields = await GetRefFields(["_id1_", "_id2_", "_id3_"])).
         return map;
     }
 
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 758291b9b..782c133ff 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -96,7 +96,7 @@ export namespace Docs {
          */
         export async function initialize(): Promise<void> {
             // non-guid string ids for each document prototype
-            let protoIds = [textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId]
+            let protoIds = [textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId];
             // fetch the actual prototype documents from the server
             let actualProtos = await DocServer.getRefFields(protoIds);
 
@@ -238,7 +238,7 @@ export namespace Docs {
                 y: 0,
                 width: 300,
                 height: 150
-            }
+            };
             return buildPrototype(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(), defaultAttrs);
         }
     }
@@ -419,7 +419,7 @@ export namespace Docs {
         export type DocConfig = {
             doc: Doc,
             initialWidth?: number
-        }
+        };
 
         export function StandardCollectionDockingDocument(configs: Array<DocConfig>, options: DocumentOptions, id?: string, type: string = "row") {
             let layoutConfig = {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4f5837590..5270a4624 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -141,11 +141,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
 
     injectParentProp = (contentArray: any[], reverse: boolean = false) => {
-        if (!contentArray || contentArray.length == 0) return;
+        if (!contentArray || contentArray.length === 0) return;
         contentArray.forEach(member => {
             let baseCase = Object.keys(member).includes("props");
             if (!baseCase) {
-                this.injectParentProp(member.content, reverse)
+                this.injectParentProp(member.content, reverse);
             } else {
                 reverse ? delete member.props.parent : member.props.parent = this;
             }
@@ -386,10 +386,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                             tab.header.parent.setActiveContentItem(tab.contentItem);
                         }
                         tab.setActive(true);
-                    }
+                    };
                     tab.header.element[0].ondrop = (e: any) => {
                         console.log("DROPPPP THE BASS!", e);
-                    }
+                    };
                     ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.AddTab(stack, doc)} />, upDiv);
                     tab.reactComponents = [upDiv];
                     tab.element.append(upDiv);
-- 
cgit v1.2.3-70-g09d2


From abb53ad8a5cbf2439aac420e9ec16a3a461dec1e Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 10 Jul 2019 15:58:04 -0400
Subject: Added color for ReadOnly mode

---
 src/client/DocServer.ts                                | 2 ++
 src/client/views/collections/CollectionDockingView.tsx | 1 +
 2 files changed, 3 insertions(+)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 443f70bb1..97147f5e4 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -32,6 +32,8 @@ export namespace DocServer {
         // _cache = {};
     }
 
+    export function isReadOnly() { return _isReadOnly; }
+
     export function prepend(extension: string): string {
         return window.location.origin + extension;
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index d477f96f0..57964442d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -426,6 +426,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     stackCreated = (stack: any) => {
         //stack.header.controlsContainer.find('.lm_popout').hide();
+        stack.header.element[0].style.backgroundColor = DocServer.isReadOnly() ? "#228540" : undefined;
         stack.header.controlsContainer.find('.lm_close') //get the close icon
             .off('click') //unbind the current click handler
             .click(action(function () {
-- 
cgit v1.2.3-70-g09d2


From aeb0521e8ad8f1efc8c3a39af9eb5443c5368453 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Thu, 11 Jul 2019 16:47:22 -0400
Subject: Removed rendering of collapsed tabs

---
 .../views/collections/CollectionDockingView.tsx    | 26 +++++++++++++++++++++-
 src/client/views/nodes/VideoBox.tsx                | 11 +++------
 2 files changed, 28 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 57964442d..abc9bbeba 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -26,7 +26,7 @@ import React = require("react");
 import { MainView } from '../MainView';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faFile } from '@fortawesome/free-solid-svg-icons';
+import { faFile, faUnlockAlt } from '@fortawesome/free-solid-svg-icons';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 library.add(faFile);
 
@@ -471,6 +471,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 interface DockedFrameProps {
     documentId: FieldId;
     dataDocumentId: FieldId;
+    glContainer: any;
     //collectionDockingView: CollectionDockingView
 }
 @observer
@@ -480,6 +481,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
     @observable private _dataDoc: Opt<Doc>;
+
+    @observable private _isActive: boolean = false;
+
     get _stack(): any {
         let parent = (this.props as any).glContainer.parent.parent;
         if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) {
@@ -497,6 +501,25 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }));
     }
 
+    componentDidMount() {
+        this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
+        this.props.glContainer.on("tab", this.onActiveContentItemChanged);
+        this.onActiveContentItemChanged();
+    }
+
+    componentWillUnmount() {
+        this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged);
+        this.props.glContainer.off("tab", this.onActiveContentItemChanged);
+    }
+
+    @action.bound
+    private onActiveContentItemChanged() {
+        if (this.props.glContainer.tab) {
+            this._isActive = this.props.glContainer.tab.isActive;
+        }
+    }
+
+
     nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
     nativeHeight = () => {
         let nh = NumCast(this._document!.nativeHeight, this._panelHeight);
@@ -571,6 +594,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
 
     render() {
+        if (!this._isActive) return null;
         let theContent = this.content;
         return !this._document ? (null) :
             <Measure offset onResize={action((r: any) => { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}>
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index a5e145856..c65dfe0bd 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -148,14 +148,9 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
         let style = "videoBox-cont" + (this._fullScreen ? "-fullScreen" : interactive);
         return !field ? <div>Loading</div> :
-            VideoBox._showControls ?
-                <video className={`${style}`} ref={this.setVideoRef} onCanPlay={this.videoLoad} onPointerDown={this.onPointerDown} onContextMenu={this.specificContextMenu} controls>
-                    <source src={field.url.href} type="video/mp4" />
-                    Not supported.
-            </video> :
-                <video className={`${style}`} ref={this.setVideoRef} onCanPlay={this.videoLoad} onPointerDown={this.onPointerDown} onContextMenu={this.specificContextMenu}>
-                    <source src={field.url.href} type="video/mp4" />
-                    Not supported.
+            <video className={`${style}`} ref={this.setVideoRef} onCanPlay={this.videoLoad} onPointerDown={this.onPointerDown} onContextMenu={this.specificContextMenu} controls={VideoBox._showControls}>
+                <source src={field.url.href} type="video/mp4" />
+                Not supported.
             </video>;
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From a7fa75a9b8e5811f81d6cdf92d680ab2cfb38fed Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Thu, 11 Jul 2019 17:07:33 -0400
Subject: Added middle click on stack to add new tab

---
 src/client/views/collections/CollectionDockingView.tsx | 6 ++++++
 1 file changed, 6 insertions(+)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index abc9bbeba..48aedc187 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -28,6 +28,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faFile, faUnlockAlt } from '@fortawesome/free-solid-svg-icons';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { Docs } from '../../documents/Documents';
 library.add(faFile);
 
 @observer
@@ -427,6 +428,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     stackCreated = (stack: any) => {
         //stack.header.controlsContainer.find('.lm_popout').hide();
         stack.header.element[0].style.backgroundColor = DocServer.isReadOnly() ? "#228540" : undefined;
+        stack.header.element.on('mousedown', (e: any) => {
+            if (e.target === stack.header.element[0] && e.button === 1) {
+                this.AddTab(stack, Docs.FreeformDocument([], { width: this.props.PanelWidth(), height: this.props.PanelHeight(), title: "Untitled Collection" }), undefined);
+            }
+        });
         stack.header.controlsContainer.find('.lm_close') //get the close icon
             .off('click') //unbind the current click handler
             .click(action(function () {
-- 
cgit v1.2.3-70-g09d2


From 956bef7a41ff4a01c517dbe9304bf4b84a9d809d Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 12 Jul 2019 13:44:57 -0400
Subject: fixed middle click to create new tab

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

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1a7ccffa5..e5cee188f 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -430,7 +430,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         stack.header.element[0].style.backgroundColor = DocServer.Control.isReadOnly() ? "#228540" : undefined;
         stack.header.element.on('mousedown', (e: any) => {
             if (e.target === stack.header.element[0] && e.button === 1) {
-                this.AddTab(stack, Docs.FreeformDocument([], { width: this.props.PanelWidth(), height: this.props.PanelHeight(), title: "Untitled Collection" }), undefined);
+                this.AddTab(stack, Docs.Create.FreeformDocument([], { width: this.props.PanelWidth(), height: this.props.PanelHeight(), title: "Untitled Collection" }), undefined);
             }
         });
         stack.header.controlsContainer.find('.lm_close') //get the close icon
-- 
cgit v1.2.3-70-g09d2


From 4db507d08249ccddf664798ab59c3b729c3d1065 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 12 Jul 2019 22:19:03 -0400
Subject: fixed a few youtube video things.  changed title of library doc.

---
 .../views/collections/CollectionDockingView.tsx    |  55 ++++++----
 .../views/collections/CollectionStackingView.tsx   |   2 +-
 src/client/views/nodes/VideoBox.scss               |  15 ++-
 src/client/views/nodes/VideoBox.tsx                | 116 +++++++++++++--------
 .../authentication/models/current_user_utils.ts    |   3 +-
 5 files changed, 116 insertions(+), 75 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index e5cee188f..34b8eb8e1 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,6 +1,6 @@
 import 'golden-layout/src/css/goldenlayout-base.css';
 import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, Lambda, observable, reaction } from "mobx";
+import { action, Lambda, observable, reaction, trace, computed } from "mobx";
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
 import Measure from "react-measure";
@@ -412,7 +412,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     }
                     SelectionManager.DeselectAll();
                 }
+                CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
                 tab.contentItem.remove();
+                CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
             });
     }
 
@@ -569,33 +571,40 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc);
         }
     }
-    get content() {
+    @computed get docView() {
         if (!this._document) {
             return (null);
         }
         let resolvedDataDoc = this._document.layout instanceof Doc ? this._document : this._dataDoc;
+        return <DocumentView key={this._document[Id]}
+            Document={this._document}
+            DataDoc={resolvedDataDoc}
+            bringToFront={emptyFunction}
+            addDocument={undefined}
+            removeDocument={undefined}
+            ContentScaling={this.contentScaling}
+            PanelWidth={this.nativeWidth}
+            PanelHeight={this.nativeHeight}
+            ScreenToLocalTransform={this.ScreenToLocalTransform}
+            renderDepth={0}
+            selectOnLoad={false}
+            parentActive={returnTrue}
+            whenActiveChanged={emptyFunction}
+            focus={emptyFunction}
+            addDocTab={this.addDocTab}
+            ContainingCollectionView={undefined}
+            zoomToScale={emptyFunction}
+            getScale={returnOne} />
+    }
+
+    @computed get content() {
+        if (!this._document) {
+            return (null);
+        }
         return (
             <div className="collectionDockingView-content" ref={this._mainCont}
                 style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier})` }}>
-                <DocumentView key={this._document[Id]}
-                    Document={this._document}
-                    DataDoc={resolvedDataDoc}
-                    bringToFront={emptyFunction}
-                    addDocument={undefined}
-                    removeDocument={undefined}
-                    ContentScaling={this.contentScaling}
-                    PanelWidth={this.nativeWidth}
-                    PanelHeight={this.nativeHeight}
-                    ScreenToLocalTransform={this.ScreenToLocalTransform}
-                    renderDepth={0}
-                    selectOnLoad={false}
-                    parentActive={returnTrue}
-                    whenActiveChanged={emptyFunction}
-                    focus={emptyFunction}
-                    addDocTab={this.addDocTab}
-                    ContainingCollectionView={undefined}
-                    zoomToScale={emptyFunction}
-                    getScale={returnOne} />
+                {this.docView}
             </div >);
     }
 
@@ -604,7 +613,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         let theContent = this.content;
         return !this._document ? (null) :
             <Measure offset onResize={action((r: any) => { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}>
-                {({ measureRef }) => <div ref={measureRef}>  {theContent} </div>}
+                {({ measureRef }) => <div ref={measureRef}>
+                    {theContent}
+                </div>}
             </Measure>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index a84fd9cfe..fe01103d6 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -50,7 +50,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     overlays = (doc: Doc) => {
-        return doc.type === DocumentType.IMG ? { title: "title", caption: "caption" } : {};
+        return doc.type === DocumentType.IMG || doc.type === DocumentType.VID ? { title: "title", caption: "caption" } : {};
     }
 
     getDisplayDoc(layoutDoc: Doc, d: Doc, dxf: () => Transform) {
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 55f2fe7c5..d651a8621 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,12 +1,17 @@
-.videoBox-cont, .videoBox-cont-interactive, .videoBox-cont-fullScreen {
-    width: 100%; 
+.videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen,
+.videoBox-content, .videoBox-content-interactive, .videoBox-cont-fullScreen {
+    width: 100%;
+}
+
+.videoBox-content, .videoBox-content-interactive, .videoBox-content-fullScreen {
     height: Auto; 
 }
 
-.videoBox-cont-interactive {
-    pointer-events: all;
+.videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen  {
+    height: 100%;
 }
 
-.videoBox-cont-fullScreen {
+.videoBox-content-interactive, .videoBox-content-fullScreen,
+.videoBox-content-YouTube-fullScreen  {
     pointer-events: all;
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 1b9138cfd..c9179db47 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,21 +1,18 @@
 import React = require("react");
-import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { action, IReactionDisposer, observable, reaction, trace, computed } from "mobx";
 import { observer } from "mobx-react";
-import * as rp from "request-promise";
 import { makeInterface } from "../../../new_fields/Schema";
 import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
 import { VideoField } from "../../../new_fields/URLField";
-import { RouteStore } from "../../../server/RouteStore";
-import { DocServer } from "../../DocServer";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
 import { DocComponent } from "../DocComponent";
+import { InkingControl } from "../InkingControl";
 import { positionSchema } from "./DocumentView";
 import { FieldView, FieldViewProps } from './FieldView';
 import { pageSchema } from "./ImageBox";
 import "./VideoBox.scss";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { InkingControl } from "../InkingControl";
-import * as $ from "jquery";
+import { InkTool } from "../../../new_fields/InkField";
 
 type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
 const VideoDocument = makeInterface(positionSchema, pageSchema);
@@ -23,6 +20,7 @@ const VideoDocument = makeInterface(positionSchema, pageSchema);
 @observer
 export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
     private _reactionDisposer?: IReactionDisposer;
+    private _youtubeReactionDisposer?: IReactionDisposer;
     private _youtubePlayer: any = undefined;
     private _videoRef: HTMLVideoElement | null = null;
     @observable _playTimer?: NodeJS.Timeout = undefined;
@@ -47,45 +45,37 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
 
     @action public Play() {
         this.Playing = true;
-        if (this.player) {
-            this.player.play();
-            if (!this._playTimer) this._playTimer = setInterval(this.updateTimecode, 500);
-        } else if (this._youtubePlayer) {
-            this._youtubePlayer.playVideo();
-            if (!this._playTimer) this._playTimer = setInterval(this.updateYoutubeTimecode, 1000);
+        this.player && this.player.play();
+        this._youtubePlayer && this._youtubePlayer.playVideo();
+        !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 500));
+        this._youtubeSeekTo = false;
+    }
+
+    @action public Seek(time: number) {
+        if (this._youtubePlayer && !this.Playing) {
+            this._youtubeSeekTo = true;
+            this._youtubePlayer.seekTo(time);
         }
     }
 
     @action public Pause() {
         this.Playing = false;
-        if (this.player) {
-            this.player.pause();
-            if (this._playTimer) {
-                clearInterval(this._playTimer);
-                this._playTimer = undefined;
-            }
-        } else if (this._youtubePlayer) {
-            // let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
-            // this._youtubePlayer.getIframe().style.pointerEvents = interactive ? "all" : "none";
-            this._youtubePlayer.pauseVideo();
-            if (this._playTimer) {
-                clearInterval(this._playTimer);
-                this._playTimer = undefined;
-            }
-        }
+        this.player && this.player.pause();
+        this._youtubePlayer && this._youtubePlayer.pauseVideo();
+        this._playTimer && clearInterval(this._playTimer);
+        this._playTimer = undefined;
+        this._youtubeSeekTo = false;
     }
 
     @action public FullScreen() {
         this._fullScreen = true;
         this.player && this.player.requestFullscreen();
+        this._youtubePlayer && this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab");
     }
 
     @action
     updateTimecode = () => {
         this.player && (this.props.Document.curPage = this.player.currentTime);
-    }
-    @action
-    updateYoutubeTimecode = () => {
         this._youtubePlayer && (this.props.Document.curPage = this._youtubePlayer.getCurrentTime());
     }
     componentDidMount() {
@@ -108,9 +98,15 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
                 videoId: videoid.toString(),
                 playerVars: { 'controls': VideoBox._showControls ? 1 : 0 },
                 events: {
-                    'onStateChange': this.onPlayerStateChange,
+                    'onStateChange': this.onYoutubePlayerStateChange,
+                    'onReady': this.onYoutubePlayerReady,
                 }
             });
+            this._reactionDisposer = reaction(() => this.props.Document.curPage, () => this.Seek(this.Document.curPage || 0), { fireImmediately: true });
+            this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), InkingControl.Instance.selectedTool], () => {
+                let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected();
+                this._youtubePlayer.getIframe().style.pointerEvents = interactive ? "all" : "none";
+            }, { fireImmediately: true })
             // let iframe = $(document.getElementById(`${videoid}-player`)!);
             // iframe.on("load", function () {
             //     iframe.contents().find("head")
@@ -120,13 +116,30 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
     }
 
     @action
-    onPlayerStateChange = (event: any) => {
+    onYoutubePlayerStateChange = (event: any) => {
+        console.log("event.data = " + event.data);
         this.Playing = event.data == YT.PlayerState.PLAYING;
+        if (this._youtubeSeekTo && this.Playing) {
+            this._youtubePlayer.pauseVideo();
+            this._youtubeSeekTo = false;
+        } else this.Playing && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 500));
+        event.data === YT.PlayerState.PAUSED && this._playTimer && clearInterval(this._playTimer);
+    }
+    _youtubeSeekTo = false;
+    @action
+    onYoutubePlayerReady = (event: any) => {
+        this.Playing = false;
+        this._youtubePlayer && (this._youtubePlayer.getIframe().style.pointerEvents = "none");
+        if (this.Document.curPage) {
+            this.Seek(this.Document.curPage);
+            this._youtubeSeekTo = true;
+        }
     }
 
     componentWillUnmount() {
         this.Pause();
-        if (this._reactionDisposer) this._reactionDisposer();
+        this._reactionDisposer && this._reactionDisposer();
+        this._youtubeReactionDisposer && this._youtubeReactionDisposer();
     }
 
     @action
@@ -143,6 +156,11 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
 
     @observable static _showControls: boolean = false;
 
+    @computed get youtubeVideoId() {
+        let field = Cast(this.Document[this.props.fieldKey], VideoField);
+        return field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : "";
+    }
+
     specificContextMenu = (e: React.MouseEvent): void => {
         let field = Cast(this.Document[this.props.fieldKey], VideoField);
         if (field) {
@@ -152,19 +170,25 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         }
     }
 
-    render() {
+    @computed get content() {
         let field = Cast(this.Document[this.props.fieldKey], VideoField);
         let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
-        let style = "videoBox-cont" + (this._fullScreen ? "-fullScreen" : interactive);
-        let videoid = field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : "";
-
-        if (this._youtubePlayer) this._youtubePlayer.getIframe().style.pointerEvents = interactive ? "all" : "none";
+        let style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
         return !field ? <div>Loading</div> :
-            videoid ?
-                <div id={`${videoid}-player`} className={`${style}`} style={{ height: "100%" }} /> :
-                <video className={`${style}`} ref={this.setVideoRef} onCanPlay={this.videoLoad} onContextMenu={this.specificContextMenu} controls={VideoBox._showControls}>
-                    <source src={field.url.href} type="video/mp4" />
-                    Not supported.
-                </video>;
+            <video className={`${style}`} ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls}>
+                <source src={field.url.href} type="video/mp4" />
+                Not supported.
+            </video>;
+    }
+
+    @computed get youtubeContent() {
+        let style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
+        return <div id={`${this.youtubeVideoId}-player`} className={`${style}`} />;
+    }
+
+    render() {
+        return <div style={{ pointerEvents: "all", width: "100%", height: "100%" }} onContextMenu={this.specificContextMenu}>
+            {this.youtubeVideoId ? this.youtubeContent : this.content}
+        </div>;
     }
 }
\ 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 39a973e08..384c579de 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -10,7 +10,7 @@ import { CollectionView } from "../../../client/views/collections/CollectionView
 import { Doc } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
-import { Cast, FieldValue } from "../../../new_fields/Types";
+import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { RouteStore } from "../../RouteStore";
 
 export class CurrentUserUtils {
@@ -69,6 +69,7 @@ export class CurrentUserUtils {
             sidebar.boxShadow = "1 1 3";
             doc.sidebar = sidebar;
         }
+        StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library");
 
     }
 
-- 
cgit v1.2.3-70-g09d2


From 5ee47fa3533c769364c91561a0be5ee388f855d3 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 12 Jul 2019 23:49:37 -0400
Subject: fixed resizing youtube videos.  changed dockingview to not call
 setupGoldenLayout on  dropping or closing a tab.

---
 src/client/views/collections/CollectionDockingView.tsx | 7 ++++++-
 src/client/views/nodes/VideoBox.tsx                    | 5 +++--
 2 files changed, 9 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 34b8eb8e1..fe8288b28 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -298,7 +298,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     onPointerUp = (e: React.PointerEvent): void => {
         if (this._flush) {
             this._flush = false;
-            setTimeout(() => this.stateChanged(), 10);
+            setTimeout(() => {
+                CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
+                this.stateChanged()
+            }, 10);
         }
     }
     @action
@@ -342,6 +345,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
 
     itemDropped = () => {
+        CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
         this.stateChanged();
     }
 
@@ -357,6 +361,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             if (tab.contentItem.config.fixed) {
                 tab.contentItem.parent.config.fixed = true;
             }
+
             let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId) as Doc;
             let dataDoc = await DocServer.GetRefField(tab.contentItem.config.props.dataDocumentId) as Doc;
             if (doc instanceof Doc) {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 861e91e41..e86348241 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -13,6 +13,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { pageSchema } from "./ImageBox";
 import "./VideoBox.scss";
 import { InkTool } from "../../../new_fields/InkField";
+import { DocumentDecorations } from "../DocumentDecorations";
 
 type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
 const VideoDocument = makeInterface(positionSchema, pageSchema);
@@ -103,8 +104,8 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
                 }
             });
             this._reactionDisposer = reaction(() => this.props.Document.curPage, () => this.Seek(this.Document.curPage || 0), { fireImmediately: true });
-            this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), InkingControl.Instance.selectedTool], () => {
-                let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected();
+            this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => {
+                let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting;
                 this._youtubePlayer.getIframe().style.pointerEvents = interactive ? "all" : "none";
             }, { fireImmediately: true })
             // let iframe = $(document.getElementById(`${videoid}-player`)!);
-- 
cgit v1.2.3-70-g09d2


From 66f0d72f07d3cf28e01abd0d9b2659c951defcd9 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sun, 14 Jul 2019 20:16:09 -0400
Subject: Added closed stack's tabs to recently closed docs

---
 src/client/views/collections/CollectionDockingView.tsx | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index fe8288b28..781bafec0 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -412,8 +412,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 if (doc instanceof Doc) {
                     let theDoc = doc;
                     CollectionDockingView.Instance._removedDocs.push(theDoc);
-                    if (CurrentUserUtils.UserDocument.recentlyClosed instanceof Doc) {
-                        Doc.AddDocToList(CurrentUserUtils.UserDocument.recentlyClosed, "data", doc, undefined, true, true);
+
+                    const recent = await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc);
+                    if (recent) {
+                        Doc.AddDocToList(recent, "data", doc, undefined, true, true);
                     }
                     SelectionManager.DeselectAll();
                 }
@@ -442,12 +444,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         });
         stack.header.controlsContainer.find('.lm_close') //get the close icon
             .off('click') //unbind the current click handler
-            .click(action(function () {
+            .click(action(async function () {
                 //if (confirm('really close this?')) {
+                const recent = await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc);
                 stack.remove();
-                stack.contentItems.map(async (contentItem: any) => {
+                stack.contentItems.forEach(async (contentItem: any) => {
                     let doc = await DocServer.GetRefField(contentItem.config.props.documentId);
                     if (doc instanceof Doc) {
+                        if (recent) {
+                            Doc.AddDocToList(recent, "data", doc, undefined, true, true);
+                        }
                         let theDoc = doc;
                         CollectionDockingView.Instance._removedDocs.push(theDoc);
                     }
-- 
cgit v1.2.3-70-g09d2


From c9bf8852a77298e1e958cfc72559418b6cb5981a Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 15 Jul 2019 11:57:23 -0400
Subject: tweaks

---
 src/client/util/TooltipTextMenu.tsx                    | 2 +-
 src/client/views/collections/CollectionDockingView.tsx | 1 +
 src/server/Search.ts                                   | 2 +-
 3 files changed, 3 insertions(+), 2 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 0d3adef2e..9ba339285 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -243,7 +243,7 @@ export class TooltipTextMenu {
                         handlers: {
                             dragComplete: action(() => {
                                 let m = dragData.droppedDocuments;
-                                this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
+                                m.length && this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
                             }),
                         },
                         hideSource: false
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 781bafec0..1069ebbdb 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -260,6 +260,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         // Because this is in a set timeout, if this component unmounts right after mounting,
                         // we will leak a GoldenLayout, because we try to destroy it before we ever create it
                         setTimeout(() => this.setupGoldenLayout(), 1);
+                        DocListCast((CurrentUserUtils.UserDocument.workspaces as Doc).data).map(d => d.workspaceBrush = false);
                         this.props.Document.workspaceBrush = true;
                     }
                     this._ignoreStateChange = "";
diff --git a/src/server/Search.ts b/src/server/Search.ts
index 98f421937..11092c5be 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -26,7 +26,7 @@ export class Search {
             });
             return res;
         } catch (e) {
-            // console.warn("Search error: " + e + document);
+            // console.warn("Search error: ", e, documents);
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 533c48010ca668a944962bd61cd65115fc5a6fa6 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samuel_wilkins@brown.edu>
Date: Tue, 16 Jul 2019 12:04:21 -0400
Subject: linter and error fixes

---
 src/client/documents/Documents.ts                      | 2 +-
 src/client/views/InkingControl.tsx                     | 4 ++--
 src/client/views/Main.tsx                              | 2 +-
 src/client/views/collections/CollectionDockingView.tsx | 4 ++--
 src/client/views/collections/CollectionTreeView.tsx    | 2 +-
 src/client/views/nodes/ImageBox.tsx                    | 2 +-
 src/client/views/nodes/VideoBox.tsx                    | 4 ++--
 src/client/views/nodes/WebBox.tsx                      | 2 +-
 src/client/views/search/SearchBox.tsx                  | 2 +-
 9 files changed, 12 insertions(+), 12 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ada9f3610..177810444 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -98,7 +98,7 @@ export namespace Docs {
 
         type LayoutSource = { LayoutString: () => string };
         type CollectionLayoutSource = { LayoutString: (fieldStr: string, fieldExt?: string) => string };
-        type CollectionViewType = [CollectionLayoutSource, string, string?]
+        type CollectionViewType = [CollectionLayoutSource, string, string?];
         type PrototypeTemplate = {
             layout: {
                 view: LayoutSource,
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index c7f7bdb66..1910e409b 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,5 +1,5 @@
 import { observable, action, computed, runInAction } from "mobx";
-import { ColorResult } from 'react-color';
+import { ColorState } from 'react-color';
 import React = require("react");
 import { observer } from "mobx-react";
 import "./InkingControl.scss";
@@ -41,7 +41,7 @@ export class InkingControl extends React.Component {
     }
 
     @undoBatch
-    switchColor = action((color: ColorResult): void => {
+    switchColor = action((color: ColorState): void => {
         this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
         if (InkingControl.Instance.selectedTool === InkTool.None) {
             if (MainOverlayTextBox.Instance.SetColor(color.hex)) return;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 80399e24b..86578af3e 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -26,7 +26,7 @@ let swapDocs = async () => {
         }
         CurrentUserUtils.UserDocument.linkManagerDoc = undefined;
     }
-}
+};
 
 (async () => {
     const info = await CurrentUserUtils.loadCurrentUser();
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1069ebbdb..a193ff677 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -301,7 +301,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             this._flush = false;
             setTimeout(() => {
                 CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
-                this.stateChanged()
+                this.stateChanged();
             }, 10);
         }
     }
@@ -606,7 +606,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             addDocTab={this.addDocTab}
             ContainingCollectionView={undefined}
             zoomToScale={emptyFunction}
-            getScale={returnOne} />
+            getScale={returnOne} />;
     }
 
     @computed get content() {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 0196fecff..c212cc97c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -153,7 +153,7 @@ class TreeView extends React.Component<TreeViewProps> {
         let docList = Cast(this.resolvedDataDoc[this.fieldKey], listSpec(Doc));
         let doc = Cast(this.resolvedDataDoc[this.fieldKey], Doc);
         let isDoc = doc instanceof Doc || docList;
-        let c
+        let c;
         return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
             {<FontAwesomeIcon icon={this._collapsed ? (isDoc ? "caret-square-right" : "caret-right") : (isDoc ? "caret-square-down" : "caret-down")} />}
         </div>;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 73ae8955d..1df955f1f 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -33,7 +33,7 @@ export const pageSchema = createSchema({
     curPage: "number",
 });
 
-interface window {
+interface Window {
     MediaRecorder: MediaRecorder;
 }
 
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 66844cdd6..e90efb31c 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -108,7 +108,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
             this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => {
                 let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting;
                 this._youtubePlayer.getIframe().style.pointerEvents = interactive ? "all" : "none";
-            }, { fireImmediately: true })
+            }, { fireImmediately: true });
             // let iframe = $(document.getElementById(`${videoid}-player`)!);
             // iframe.on("load", function () {
             //     iframe.contents().find("head")
@@ -120,7 +120,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
     @action
     onYoutubePlayerStateChange = (event: any) => {
         console.log("event.data = " + event.data);
-        this.Playing = event.data == YT.PlayerState.PLAYING;
+        this.Playing = event.data === YT.PlayerState.PLAYING;
         if (this._youtubeSeekTo && this.Playing) {
             this._youtubePlayer.pauseVideo();
             this._youtubeSeekTo = false;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 96b972a1c..f0a9ec6d8 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -19,7 +19,7 @@ export function onYouTubeIframeAPIReady() {
     });
 }
 // must cast as any to set property on window
-const _global = (window /* browser */ || global /* node */) as any
+const _global = (window /* browser */ || global /* node */) as any;
 _global.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady;
 
 function onPlayerReady(event: any) {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 3830a6d16..ec778b346 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -282,7 +282,7 @@ export class SearchBox extends React.Component {
 
     @computed
     get resFull() {
-        console.log(this._numTotalResults)
+        console.log(this._numTotalResults);
         return this._numTotalResults <= 8;
     }
 
-- 
cgit v1.2.3-70-g09d2

