From 7db0071a1f1232112597176b0b077e6b7e37679d Mon Sep 17 00:00:00 2001
From: Eleanor Eng <eleanor_eng@brown.edu>
Date: Mon, 18 Mar 2019 19:00:13 -0400
Subject: Added icons

---
 src/client/views/nodes/DocumentView.tsx | 33 +++++++++++++++++++++++++--------
 1 file changed, 25 insertions(+), 8 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index dc793c16d..53a3aaa18 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -27,6 +27,23 @@ import React = require("react");
 import { TextField } from "../../../fields/TextField";
 import { DocumentManager } from "../../util/DocumentManager";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faTrash } from '@fortawesome/free-solid-svg-icons';
+import { faExpandArrowsAlt } from '@fortawesome/free-solid-svg-icons';
+import { faCompressArrowsAlt } from '@fortawesome/free-solid-svg-icons';
+import { faLayerGroup } from '@fortawesome/free-solid-svg-icons';
+import { faAlignCenter } from '@fortawesome/free-solid-svg-icons';
+import { faCaretSquareRight } from '@fortawesome/free-solid-svg-icons';
+import { faSquare } from '@fortawesome/free-solid-svg-icons';
+
+library.add(faTrash);
+library.add(faExpandArrowsAlt);
+library.add(faCompressArrowsAlt);
+library.add(faLayerGroup);
+library.add(faAlignCenter);
+library.add(faCaretSquareRight);
+library.add(faSquare);
 
 
 export interface DocumentViewProps {
@@ -212,14 +229,14 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     fullScreenClicked = (e: React.MouseEvent): void => {
         CollectionDockingView.Instance.OpenFullScreen(this.props.Document);
         ContextMenu.Instance.clearItems();
-        ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
+        ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked, icon: "compress-arrows-alt" });
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
     }
 
     closeFullScreenClicked = (e: React.MouseEvent): void => {
         CollectionDockingView.Instance.CloseFullScreen();
         ContextMenu.Instance.clearItems();
-        ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
+        ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "expand-arrows-alt" })
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
     }
 
@@ -261,18 +278,18 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         }
         e.preventDefault()
 
-        ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
-        ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked })
-        ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) })
-        ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) })
-        //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
+        ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "expand-arrows-alt" })
+        ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" })
+        ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document), icon: "align-center" })
+        ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" })
+        ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking), icon: "square" })
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
         if (!this.topMost) {
             // DocumentViews should stop propagation of this event
             e.stopPropagation();
         }
 
-        ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
+        ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" })
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
         SelectionManager.SelectDoc(this, e.ctrlKey);
     }
-- 
cgit v1.2.3-70-g09d2


From 19fca408a19c5f7a759ff6c3bfefe27b96ec3563 Mon Sep 17 00:00:00 2001
From: Eleanor Eng <eleanor_eng@brown.edu>
Date: Tue, 19 Mar 2019 17:51:25 -0400
Subject: contextmenu

---
 src/client/views/ContextMenu.scss               | 2 +-
 src/client/views/ContextMenuItem.tsx            | 2 +-
 src/client/views/collections/CollectionView.tsx | 1 -
 src/client/views/nodes/DocumentView.tsx         | 1 -
 src/client/views/nodes/FormattedTextBox.tsx     | 1 -
 src/client/views/nodes/ImageBox.tsx             | 1 -
 6 files changed, 2 insertions(+), 6 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index e5d41fcb6..8bc7708f7 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -18,7 +18,7 @@
     width: 11vw; //10vw
     height: 2.5vh; //2vh
     background: #DDDDDD;
-    display: flex;
+    display: flex; //comment out to allow search icon to be inline with search text
     justify-content: left;
     align-items: center;
     -webkit-touch-callout: none;
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 80d9ff6a5..fac7342aa 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,7 +1,7 @@
 import React = require("react");
 import { observable, action } from "mobx";
 import { observer } from "mobx-react";
-import { library, IconProp } from '@fortawesome/fontawesome-svg-core';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 
 export interface OriginalMenuProps {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index b76426429..d0e2162b1 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -14,7 +14,6 @@ import { CollectionViewProps } from "./CollectionViewBase";
 import { CollectionTreeView } from "./CollectionTreeView";
 import { Field, FieldId } from "../../../fields/Field";
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faTh } from '@fortawesome/free-solid-svg-icons';
 import { faTree } from '@fortawesome/free-solid-svg-icons';
 import { faSquare } from '@fortawesome/free-solid-svg-icons';
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 53a3aaa18..907762837 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -28,7 +28,6 @@ import { TextField } from "../../../fields/TextField";
 import { DocumentManager } from "../../util/DocumentManager";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faTrash } from '@fortawesome/free-solid-svg-icons';
 import { faExpandArrowsAlt } from '@fortawesome/free-solid-svg-icons';
 import { faCompressArrowsAlt } from '@fortawesome/free-solid-svg-icons';
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7a8dbddcc..648037e31 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -15,7 +15,6 @@ import { Decoration, DecorationSet } from 'prosemirror-view'
 import { TooltipTextMenu } from "../../util/TooltipTextMenu"
 import { ContextMenu } from "../../views/ContextMenu";
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faEdit } from '@fortawesome/free-solid-svg-icons';
 import { faSmile } from '@fortawesome/free-solid-svg-icons';
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 62ecea583..7532e23c8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,4 +1,3 @@
-
 import { action, observable } from 'mobx';
 import { observer } from "mobx-react";
 import Lightbox from 'react-image-lightbox';
-- 
cgit v1.2.3-70-g09d2


From 09a471ac693e8178598c0d953b83868db9e4c8b4 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 5 May 2019 18:24:13 -0400
Subject: fixed link titles, kvp title, and closing a stack of tabs

---
 src/client/views/collections/CollectionDockingView.tsx | 10 ++++++++++
 src/client/views/nodes/DocumentView.tsx                |  2 +-
 src/client/views/nodes/LinkEditor.tsx                  |  5 +++--
 3 files changed, 14 insertions(+), 3 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 3bee24c2f..278065479 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -309,6 +309,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 var url = DocServer.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
                 let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");
             }));
+        stack.header.controlsContainer.find('.lm_close') //unbind the current click handler
+            .click(async function () {
+                stack.contentItems.map(async (contentItem: any) => {
+                    let doc = await DocServer.GetRefField(contentItem.config.props.documentId);
+                    if (doc instanceof Doc) {
+                        let theDoc = doc;
+                        CollectionDockingView.Instance._removedDocs.push(theDoc);
+                    }
+                });
+            });
     }
 
     render() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fd012e7ea..096a02d9b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -198,7 +198,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.props.removeDocument && this.props.removeDocument(this.props.Document);
     }
     fieldsClicked = (e: React.MouseEvent): void => {
-        let kvp = Docs.KVPDocument(this.props.Document, { width: 300, height: 300 });
+        let kvp = Docs.KVPDocument(this.props.Document, { title: this.props.Document.title + ".kvp", width: 300, height: 300 });
         CollectionDockingView.Instance.AddRightSplit(kvp);
     }
     makeButton = (e: React.MouseEvent): void => {
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index f82c6e9cb..71a423338 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -24,8 +24,9 @@ export class LinkEditor extends React.Component<Props> {
     onSaveButtonPressed = (e: React.PointerEvent): void => {
         e.stopPropagation();
 
-        this.props.linkDoc.title = this._nameInput;
-        this.props.linkDoc.linkDescription = this._descriptionInput;
+        let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc;
+        linkDoc.title = this._nameInput;
+        linkDoc.linkDescription = this._descriptionInput;
 
         this.props.showLinks();
     }
-- 
cgit v1.2.3-70-g09d2


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

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

(limited to 'src/client/views/nodes/DocumentView.tsx')

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


From 911a8b6af776e58e7bf9d2653c3b604c583a458d Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 6 May 2019 15:44:26 -0400
Subject: fixed dragging document to new tab to drag an alias.

---
 src/client/views/nodes/DocumentView.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9d356cc30..a20a8a93b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -165,8 +165,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (e.shiftKey && e.buttons === 1) {
             if (this.props.isTopMost) {
                 this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined);
-            } else {
-                CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
+            } else if (this.props.Document) {
+                CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
             }
             e.stopPropagation();
         } else if (this.active) {
-- 
cgit v1.2.3-70-g09d2


From 6f9316683929b86e27fd4e2e30609670cd89f964 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 8 May 2019 12:10:07 -0400
Subject: adjusted titling of icons and summaries.

---
 .../collectionFreeForm/MarqueeView.scss            |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 55 ++++++++++++----------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  1 +
 src/client/views/nodes/DocumentView.tsx            | 23 ++++++---
 src/client/views/nodes/IconBox.tsx                 | 11 -----
 5 files changed, 48 insertions(+), 44 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index ae0a9fd48..6e8ec8662 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -21,6 +21,6 @@
         white-space:nowrap;
     }
     .marquee-legend::after {
-        content: "Press: C (collection), or Delete"
+        content: "Press: c (collection), s (summary), r (replace) or Delete"
     }
 }
\ 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 b9a6792ed..9764db1d4 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -9,7 +9,7 @@ import { PreviewCursor } from "../../PreviewCursor";
 import { CollectionFreeFormView } from "./CollectionFreeFormView";
 import "./MarqueeView.scss";
 import React = require("react");
-import { Utils } from "../../../../Utils";
+import { Utils, deepCopy } from "../../../../Utils";
 import { Doc } from "../../../../new_fields/Doc";
 import { NumCast, Cast } from "../../../../new_fields/Types";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
@@ -68,6 +68,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                         continue;
                     }
                     while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") ||
+                        ns[i].endsWith(";\r") || ns[i].endsWith(";") ||
                         ns[i].endsWith(".\r") || ns[i].endsWith(".") ||
                         ns[i].endsWith(":\r") || ns[i].endsWith(":")) && i < ns.length - 1) {
                         let sub = ns[i].endsWith("\r") ? 1 : 0;
@@ -182,12 +183,19 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             this.cleanupInteractions(false);
             e.stopPropagation();
         }
-        if (e.key === "c" || e.key === "r" || e.key === "R" || e.key === "e") {
+        if (e.key === "c" || e.key === "r" || e.key === "s" || e.key === "e") {
             this._commandExecuted = true;
             e.stopPropagation();
             let bounds = this.Bounds;
             let selected = this.marqueeSelect().map(d => {
-                if (e.key !== "R") {
+                if (e.key === "s") {
+                    let dCopy = Doc.MakeCopy(d);
+                    dCopy.x = NumCast(d.x) - bounds.left - bounds.width / 2;
+                    dCopy.y = NumCast(d.y) - bounds.top - bounds.height / 2;
+                    dCopy.page = -1;
+                    return dCopy;
+                }
+                else if (e.key !== "r") {
                     this.props.removeDocument(d);
                     d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
                     d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
@@ -208,25 +216,20 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 width: bounds.width * zoomBasis,
                 height: bounds.height * zoomBasis,
                 ink: inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined,
-                title: "a nested collection"
+                title: "a nested collection",
             });
 
             this.marqueeInkDelete(inkData);
             // SelectionManager.DeselectAll();
-            if (e.key === "r" || e.key === "R") {
+            if (e.key === "s" || e.key === "r") {
                 e.preventDefault();
                 let scrpt = this.props.getTransform().inverse().transformPoint(bounds.left, bounds.top);
                 let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
 
-                if (e.key === "r") {
+                if (e.key === "s") {
                     summary.proto!.maximizeOnRight = true;
-                    let list = Cast(newCollection.data, listSpec(Doc));
-                    if (list && list.length === 1) {
-                        selected = list;
-                    } else {
-                        selected = [newCollection];
-                        this.props.addDocument(newCollection, false);
-                    }
+                    newCollection.proto!.summaryDoc = summary;
+                    selected = [newCollection];
                 }
                 summary.proto!.maximizedDocs = new List<Doc>(selected);
                 summary.proto!.isButton = true;
@@ -243,20 +246,20 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 this.props.addDocument(newCollection, false);
             }
             this.cleanupInteractions(false);
-        }
-        if (e.key === "s") {
-            this._commandExecuted = true;
-            e.stopPropagation();
-            e.preventDefault();
-            let bounds = this.Bounds;
-            let selected = this.marqueeSelect();
-            SelectionManager.DeselectAll();
-            let summary = Docs.TextDocument({ x: bounds.left + bounds.width + 25, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
-            this.props.addLiveTextDocument(summary);
-            selected.forEach(select => Doc.MakeLink(summary.proto!, select.proto!));
+        } else
+            if (e.key === "s") {
+                // this._commandExecuted = true;
+                // e.stopPropagation();
+                // e.preventDefault();
+                // let bounds = this.Bounds;
+                // let selected = this.marqueeSelect();
+                // SelectionManager.DeselectAll();
+                // let summary = Docs.TextDocument({ x: bounds.left + bounds.width + 25, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
+                // this.props.addLiveTextDocument(summary);
+                // selected.forEach(select => Doc.MakeLink(summary.proto!, select.proto!));
 
-            this.cleanupInteractions(false);
-        }
+                // this.cleanupInteractions(false);
+            }
     }
     @action
     marqueeInkSelect(ink: Map<any, any>) {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index a9c53933e..1f8d22ab0 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -185,6 +185,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                         if (dataDocs) {
                             SelectionManager.DeselectAll();
                             maximizedDocs.forEach(maxDoc => {
+                                maxDoc.isMinimized = false;
                                 if (!dataDocs || dataDocs.indexOf(maxDoc) == -1) {
                                     CollectionDockingView.Instance.AddRightSplit(maxDoc);
                                 } else {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a20a8a93b..38efeeba5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, runInAction } from "mobx";
+import { action, computed, runInAction, reaction, IReactionDisposer } from "mobx";
 import { observer } from "mobx-react";
 import { emptyFunction, Utils } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
@@ -16,10 +16,10 @@ import { Template, Templates } from "./../Templates";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
-import { Opt, Doc, WidthSym, HeightSym } from "../../../new_fields/Doc";
+import { Opt, Doc, WidthSym, HeightSym, DocListCast } from "../../../new_fields/Doc";
 import { DocComponent } from "../DocComponent";
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { FieldValue, StrCast, BoolCast } from "../../../new_fields/Types";
+import { FieldValue, StrCast, BoolCast, Cast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
@@ -99,6 +99,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     set templates(templates: List<string>) { this.props.Document.templates = templates; }
     screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
 
+    _reactionDisposer?: IReactionDisposer;
     @action
     componentDidMount() {
         if (this._mainCont.current) {
@@ -106,6 +107,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 handlers: { drop: this.drop.bind(this) }
             });
         }
+        this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
+            async () => {
+                let maxDoc = await DocListCast(this.props.Document.maximizedDocs);
+                if (maxDoc && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
+                    this.props.Document.title = (maxDoc && maxDoc.length === 1 ? maxDoc[0].title + ".icon" : "");
+                }
+                let sumDoc = Cast(this.props.Document.summaryDoc, Doc);
+                if (sumDoc instanceof Doc) {
+                    this.props.Document.title = sumDoc.title + ".expanded";
+                }
+            }, { fireImmediately: true });
         DocumentManager.Instance.DocumentViews.push(this);
     }
     @action
@@ -121,9 +133,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
     @action
     componentWillUnmount() {
-        if (this._dropDisposer) {
-            this._dropDisposer();
-        }
+        if (this._reactionDisposer) this._reactionDisposer();
+        if (this._dropDisposer) this._dropDisposer();
         DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
     }
 
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 19abec4af..4bcb4c636 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -24,17 +24,6 @@ library.add(faFilm);
 @observer
 export class IconBox extends React.Component<FieldViewProps> {
     public static LayoutString() { return FieldView.LayoutString(IconBox); }
-    _reactionDisposer?: IReactionDisposer;
-    componentDidMount() {
-        this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs],
-            async () => {
-                let maxDoc = await DocListCast(this.props.Document.maximizedDocs);
-                this.props.Document.title = (maxDoc && maxDoc.length === 1 ? maxDoc[0].title + ".icon" : "");
-            }, { fireImmediately: true });
-    }
-    componentWillUnmount() {
-        if (this._reactionDisposer) this._reactionDisposer();
-    }
 
     @computed get layout(): string { const field = Cast(this.props.Document[this.props.fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
     @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); }
-- 
cgit v1.2.3-70-g09d2


From 9015421a227ab58f309906eabf884654d3a31a17 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 9 May 2019 11:58:36 -0400
Subject: added start of hyperlinks to text boxes

---
 src/client/util/DragManager.ts          |   3 +-
 src/client/util/TooltipTextMenu.tsx     | 101 +++++++++++++++++++++++++++++++-
 src/client/views/nodes/DocumentView.tsx |   1 +
 3 files changed, 102 insertions(+), 3 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index ec192eaff..fbf20e244 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -172,6 +172,7 @@ export namespace DragManager {
         droppedDocuments: Doc[] = [];
         linkSourceDocument: Doc;
         blacklist: Doc[];
+        dontClearTextBox?: boolean;
         [id: string]: any;
     }
 
@@ -188,7 +189,7 @@ export namespace DragManager {
             dragDiv.style.pointerEvents = "none";
             DragManager.Root().appendChild(dragDiv);
         }
-        MainOverlayTextBox.Instance.SetTextDoc();
+        if (!dragData.dontClearTextBox) MainOverlayTextBox.Instance.SetTextDoc();
 
         let scaleXs: number[] = [];
         let scaleYs: number[] = [];
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index e33c53a1a..15895fd1c 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -6,19 +6,28 @@ import { keymap } from "prosemirror-keymap";
 import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { schema } from "./RichTextSchema";
-import { Schema, NodeType, MarkType } from "prosemirror-model";
+import { Schema, NodeType, MarkType, Mark } from "prosemirror-model";
 import React = require("react");
 import "./TooltipTextMenu.scss";
 const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { wrapInList, bulletList, liftListItem, listItem, } from 'prosemirror-schema-list';
-import { liftTarget } from 'prosemirror-transform';
+import { liftTarget, RemoveMarkStep, AddMarkStep } from 'prosemirror-transform';
 import {
     faListUl,
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { FieldViewProps } from "../views/nodes/FieldView";
 import { throwStatement } from "babel-types";
+import { View } from "@react-pdf/renderer";
+import { DragManager } from "./DragManager";
+import { Doc, Opt, Field } from "../../new_fields/Doc";
+import { Id } from "../../new_fields/RefField";
+import { Utils } from "../northstar/utils/Utils";
+import { DocServer } from "../DocServer";
+import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { DocumentManager } from "./DocumentManager";
 
 const SVG = "http://www.w3.org/2000/svg";
 
@@ -37,6 +46,8 @@ export class TooltipTextMenu {
     private fontStylesToName: Map<MarkType, string>;
     private listTypeToIcon: Map<NodeType, string>;
     private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
+    private linkEditor?: HTMLDivElement;
+    private linkText?: HTMLDivElement;
     //dropdown doms
     private fontSizeDom?: Node;
     private fontStyleDom?: Node;
@@ -150,6 +161,90 @@ export class TooltipTextMenu {
         this.tooltip.appendChild(this.fontStyleDom);
     }
 
+    updateLinkMenu() {
+        if (!this.linkEditor || !this.linkText) {
+            this.linkEditor = document.createElement("div");
+            this.linkEditor.style.color = "white";
+            this.linkText = document.createElement("div");
+            this.linkText.style.cssFloat = "left";
+            this.linkText.style.marginRight = "5px";
+            this.linkText.setAttribute("contenteditable", "true");
+            this.linkText.style.color = "white";
+            let linkBtn = document.createElement("div");
+            linkBtn.textContent = ">>";
+            linkBtn.style.width = "20px";
+            linkBtn.style.height = "20px";
+            linkBtn.style.color = "white";
+            linkBtn.style.cssFloat = "left";
+            linkBtn.onpointerdown = (e: PointerEvent) => {
+                let node = this.view.state.selection.$from.nodeAfter;
+                let link = node && node.marks.find(m => m.type.name === "link");
+                if (link) {
+                    console.log("Link to : " + link.attrs.href);
+                    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 (f instanceof Doc) {
+                                if (DocumentManager.Instance.getDocumentView(f))
+                                    DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
+                                else CollectionDockingView.Instance.AddRightSplit(f);
+                            }
+                        }));
+                    }
+                    e.stopPropagation();
+                    e.preventDefault();
+                }
+            }
+            let linkDrag = document.createElement("div");
+            linkDrag.textContent = "O";
+            linkDrag.style.width = "20px";
+            linkDrag.style.height = "20px";
+            linkDrag.style.color = "white";
+            linkDrag.style.cssFloat = "left";
+            linkDrag.onpointerdown = (e: PointerEvent) => {
+                let dragData = new DragManager.LinkDragData(this.editorProps.Document);
+                dragData.dontClearTextBox = true;
+                DragManager.StartLinkDrag(this.linkEditor!, dragData, e.clientX, e.clientY,
+                    {
+                        handlers: {
+                            dragComplete: action(() => {
+                                let m = dragData.droppedDocuments as Doc[];
+                                this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
+                            }),
+                        },
+                        hideSource: false
+                    })
+            };
+            this.linkEditor.appendChild(this.linkText);
+            this.linkEditor.appendChild(linkBtn);
+            this.linkEditor.appendChild(linkDrag)
+            this.tooltip.appendChild(this.linkEditor);
+        }
+
+        let node = this.view.state.selection.$from.nodeAfter;
+        let link = node && node.marks.find(m => m.type.name === "link");
+        this.linkText.textContent = link ? link.attrs.href : "-empty-";
+
+        this.linkText.onkeydown = (e: KeyboardEvent) => {
+            if (e.key === "Enter") {
+                this.makeLink(this.linkText!.textContent!);
+                e.stopPropagation();
+                e.preventDefault();
+            }
+        }
+        this.tooltip.appendChild(this.linkEditor);
+    }
+
+    makeLink = (target: string) => {
+        let node = this.view.state.selection.$from.nodeAfter;
+        let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target });
+        this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
+        this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
+        node = this.view.state.selection.$from.nodeAfter;
+        link = node && node.marks.find(m => m.type.name === "link");
+    }
+
     //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
     updateListItemDropdown(label: string, listTypeBtn: Node) {
         //remove old btn
@@ -348,6 +443,8 @@ export class TooltipTextMenu {
         } else { //multiple font sizes selected
             this.updateFontSizeDropdown("Various");
         }
+
+        this.updateLinkMenu();
     }
 
     //finds all active marks on selection in given group
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 38efeeba5..c3bf36553 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -238,6 +238,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             const protoDest = destDoc.proto;
             const protoSrc = sourceDoc.proto;
             Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
+            de.data.droppedDocuments.push(destDoc);
             e.stopPropagation();
         }
     }
-- 
cgit v1.2.3-70-g09d2


From 39fd912fd4cd33f30a943290295a59992b9868eb Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 9 May 2019 17:39:44 -0400
Subject: various cleanup to icons, summaries...

---
 src/client/util/DocumentManager.ts                 | 18 ++++++++++++--
 src/client/util/SelectionManager.ts                | 17 +++++++++++++
 src/client/views/DocumentDecorations.tsx           | 28 ++++++++++++++++++----
 src/client/views/TemplateMenu.tsx                  | 16 +++++++++----
 .../CollectionFreeFormLinkView.scss                |  8 +++----
 .../CollectionFreeFormLinkView.tsx                 |  6 ++---
 .../CollectionFreeFormLinksView.tsx                |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 17 ++++++-------
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 17 ++++++-------
 src/client/views/nodes/DocumentView.tsx            | 14 +++++++----
 10 files changed, 104 insertions(+), 39 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 4c264c7ec..779b07ce5 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,8 +1,9 @@
 import { computed, observable } from 'mobx';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { Doc } from '../../new_fields/Doc';
-import { FieldValue, Cast } from '../../new_fields/Types';
+import { FieldValue, Cast, BoolCast } from '../../new_fields/Types';
 import { listSpec } from '../../new_fields/Schema';
+import { SelectionManager } from './SelectionManager';
 
 
 export class DocumentManager {
@@ -70,7 +71,7 @@ export class DocumentManager {
 
     @computed
     public get LinkedDocumentViews() {
-        return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
+        return DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false)).reduce((pairs, dv) => {
             let linksList = Cast(dv.props.Document.linkedToDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
             if (linksList && linksList.length) {
                 pairs.push(...linksList.reduce((pairs, link) => {
@@ -84,6 +85,19 @@ export class DocumentManager {
                     return pairs;
                 }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
             }
+            linksList = Cast(dv.props.Document.linkedFromDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+            if (linksList && linksList.length) {
+                pairs.push(...linksList.reduce((pairs, link) => {
+                    if (link) {
+                        let linkFromDoc = FieldValue(Cast(link.linkedFrom, Doc));
+                        if (linkFromDoc) {
+                            DocumentManager.Instance.getDocumentViews(linkFromDoc).map(docView1 =>
+                                pairs.push({ a: dv, b: docView1, l: link }));
+                        }
+                    }
+                    return pairs;
+                }, pairs));
+            }
             return pairs;
         }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
     }
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index a3a8172c7..8c92c2023 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -2,6 +2,7 @@ import { observable, action } 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 {
@@ -68,4 +69,20 @@ export namespace SelectionManager {
     export function SelectedDocuments(): Array<DocumentView> {
         return manager.SelectedDocuments;
     }
+    export function ViewsSortedVertically(): DocumentView[] {
+        let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
+            if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1;
+            if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
+            return 0;
+        });
+        return sorted;
+    }
+    export function ViewsSortedHorizontally(): DocumentView[] {
+        let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
+            if (NumCast(doc1.props.Document.y) > NumCast(doc2.props.Document.y)) return 1;
+            if (NumCast(doc1.props.Document.y) < NumCast(doc2.props.Document.y)) return -1;
+            return 0;
+        });
+        return sorted;
+    }
 }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 705e7a6d8..4786b4de6 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -249,7 +249,22 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (this._iconDoc && selectedDocs.length === 1 && this._removeIcon) {
                 selectedDocs[0].props.removeDocument && selectedDocs[0].props.removeDocument(this._iconDoc);
             }
-            !this._removeIcon && selectedDocs.length === 1 && this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
+            if (!this._removeIcon) {
+                if (selectedDocs.length === 1)
+                    this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
+                else {
+                    let docViews = SelectionManager.ViewsSortedVertically();
+                    let topDocView = docViews[0];
+                    let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
+                    if (ind !== -1) {
+                        topDocView.templates.splice(ind, 1);
+                        topDocView.props.Document.subBulletDocs = undefined;
+                    } else {
+                        topDocView.addTemplate(Templates.Bullet);
+                        topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document));
+                    }
+                }
+            }
             this._removeIcon = false;
         }
         runInAction(() => this._minimizedX = this._minimizedY = 0);
@@ -410,7 +425,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 break;
         }
 
-        FormattedTextBox.InputBoxOverlay = undefined;
+        runInAction(() => FormattedTextBox.InputBoxOverlay = undefined);
         SelectionManager.SelectedDocuments().forEach(element => {
             const rect = element.ContentDiv ? element.ContentDiv.getBoundingClientRect() : new DOMRect();
 
@@ -507,7 +522,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
         let templates: Map<Template, boolean> = new Map();
         Array.from(Object.values(Templates.TemplateList)).map(template => {
-            let docTemps = SelectionManager.SelectedDocuments().reduce((res: string[], doc: DocumentView, i) => {
+            let sorted = SelectionManager.ViewsSortedVertically().slice().sort((doc1, doc2) => {
+                if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1;
+                if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
+                return 0;
+            });
+            let docTemps = sorted.reduce((res: string[], doc: DocumentView, i) => {
                 let temps = doc.props.Document.templates;
                 if (temps instanceof List) {
                     temps.map(temp => {
@@ -568,7 +588,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                             <FontAwesomeIcon className="fa-icon-link" icon="link" size="sm" />
                         </div>
                     </div>
-                    <TemplateMenu docs={SelectionManager.SelectedDocuments()} templates={templates} />
+                    <TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} />
                 </div>
             </div >
         </div>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index d74982ef8..e2b3bd07a 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -6,6 +6,7 @@ import { Template } from "./Templates";
 import { DocumentView } from "./nodes/DocumentView";
 import { List } from "../../new_fields/List";
 import { Doc } from "../../new_fields/Doc";
+import { NumCast } from "../../new_fields/Types";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -35,20 +36,27 @@ export interface TemplateMenuProps {
 export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @observable private _hidden: boolean = true;
 
+    constructor(props: TemplateMenuProps) {
+        super(props);
+        console.log("");
+    }
+
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
         if (event.target.checked) {
             if (template.Name == "Bullet") {
-                this.props.docs[0].addTemplate(template);
-                this.props.docs[0].props.Document.maximizedDocs = new List<Doc>(this.props.docs.filter((v, i) => i !== 0).map(v => v.props.Document));
+                let topDocView = this.props.docs[0];
+                topDocView.addTemplate(template);
+                topDocView.props.Document.subBulletDocs = new List<Doc>(this.props.docs.filter(v => v !== topDocView).map(v => v.props.Document));
             } else {
                 this.props.docs.map(d => d.addTemplate(template));
             }
             this.props.templates.set(template, true);
         } else {
             if (template.Name == "Bullet") {
-                this.props.docs[0].removeTemplate(template);
-                this.props.docs[0].props.Document.maximizedDocs = undefined;
+                let topDocView = this.props.docs[0];
+                topDocView.removeTemplate(template);
+                topDocView.props.Document.subBulletDocs = undefined;
             } else {
                 this.props.docs.map(d => d.removeTemplate(template));
             }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index 3e8a8a442..737ffba7d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -1,12 +1,12 @@
 .collectionfreeformlinkview-linkLine {
     stroke: black;
-    stroke-width: 3;
     transform: translate(10000px,10000px);
+    opacity: 0.5;
     pointer-events: all;
 }
 .collectionfreeformlinkview-linkCircle {
-    stroke: black;
-    stroke-width: 3;
+    stroke: rgb(0,0,0);
+    opacity: 0.5;
     transform: translate(10000px,10000px);
     pointer-events: all;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 3b700b053..63d2f7642 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -47,11 +47,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         return (
             <>
                 <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine"
-                    style={{ strokeWidth: `${l.length * 5}` }}
+                    style={{ strokeWidth: `${l.length / 2}` }}
                     x1={`${x1}`} y1={`${y1}`}
                     x2={`${x2}`} y2={`${y2}`} />
-                <circle key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine"
-                    cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={10} onPointerDown={this.onPointerDown} />
+                <circle key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkCircle"
+                    cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={5} onPointerDown={this.onPointerDown} />
             </>
         );
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index cbfbb1d2c..1d4584cfe 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -84,7 +84,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
         }
         if (view.props.ContainingCollectionView) {
             let collid = view.props.ContainingCollectionView.props.Document[Id];
-            Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).
+            Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(d => d).map(d => d as Doc).
                 filter(child =>
                     child[Id] === collid).map(view =>
                         DocumentManager.Instance.getDocumentViews(view).map(view =>
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 6057aaeba..d8855fe66 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -96,7 +96,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             document.addEventListener("pointermove", this.onPointerMove, true);
             document.addEventListener("pointerup", this.onPointerUp, true);
             document.addEventListener("keydown", this.marqueeCommand, true);
-            e.stopPropagation();
+            // bcz: do we need this?   it kills the context menu on the main collection
+            // e.stopPropagation();
         }
         if (e.altKey) {
             e.preventDefault();
@@ -228,14 +229,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     newCollection.proto!.summaryDoc = summary;
                     selected = [newCollection];
                 }
-                summary.proto!.maximizedDocs = new List<Doc>(selected);
+                summary.proto!.summarizedDocs = new List<Doc>(selected);
                 summary.proto!.isButton = true;
-                selected.map(maximizedDoc => {
-                    let maxx = NumCast(maximizedDoc.x, undefined);
-                    let maxy = NumCast(maximizedDoc.y, undefined);
-                    let maxw = NumCast(maximizedDoc.width, undefined);
-                    let maxh = NumCast(maximizedDoc.height, undefined);
-                    maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0])
+                selected.map(summarizedDoc => {
+                    let maxx = NumCast(summarizedDoc.x, undefined);
+                    let maxy = NumCast(summarizedDoc.y, undefined);
+                    let maxw = NumCast(summarizedDoc.width, undefined);
+                    let maxh = NumCast(summarizedDoc.height, undefined);
+                    summarizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0])
                 });
                 this.props.addLiveTextDocument(summary);
             }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 470c0c2f8..92033ea44 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -6,7 +6,7 @@ import "./DocumentView.scss";
 import React = require("react");
 import { DocComponent } from "../DocComponent";
 import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { FieldValue, Cast, NumCast, BoolCast } from "../../../new_fields/Types";
+import { FieldValue, Cast, NumCast, BoolCast, StrCast } from "../../../new_fields/Types";
 import { OmitKeys, Utils } from "../../../Utils";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Doc, DocListCast, HeightSym } from "../../../new_fields/Doc";
@@ -65,7 +65,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
     panelWidth = () => this.props.PanelWidth();
     panelHeight = () => this.props.PanelHeight();
-    toggleMinimized = () => this.toggleIcon();
+    toggleMinimized = async () => this.toggleIcon(await DocListCast(this.props.Document.maximizedDocs));
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
         .scale(1 / this.contentScaling()).scale(1 / this.zoom)
@@ -126,10 +126,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             2);
     }
     @action
-    public toggleIcon = async (): Promise<void> => {
+    public toggleIcon = async (maximizedDocs: Doc[] | undefined): Promise<void> => {
         SelectionManager.DeselectAll();
         let isMinimized: boolean | undefined;
-        let maximizedDocs = await DocListCast(this.props.Document.maximizedDocs);
         let minimizedDoc: Doc | undefined = this.props.Document;
         if (!maximizedDocs) {
             minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
@@ -177,8 +176,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         let altKey = e.altKey;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            if (BoolCast(this.props.Document.isButton, false) || (e.target as any).id === "isBullet") {
-                let maximizedDocs = await DocListCast(this.props.Document.maximizedDocs);
+            let isBullet = (e.target as any).id === "isBullet";
+            let isIcon = StrCast(this.props.Document.layout).indexOf("IconBox") !== -1;
+            if (BoolCast(this.props.Document.isButton, false) || isBullet) {
+                let maximizedDocs = await DocListCast(isBullet ? this.props.Document.subBulletDocs : isIcon ? this.props.Document.maximizedDocs : this.props.Document.summarizedDocs);
                 if (maximizedDocs) {   // bcz: need a better way to associate behaviors with click events on widget-documents
                     if ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight)) {
                         let dataDocs = await DocListCast(CollectionDockingView.Instance.props.Document.data);
@@ -195,7 +196,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                         }
                     } else {
                         this.props.addDocument && maximizedDocs.forEach(async maxDoc => this.props.addDocument!(await maxDoc, false));
-                        this.toggleIcon();
+                        this.toggleIcon(maximizedDocs);
                     }
                 }
             }
@@ -230,7 +231,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return (
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
                 onPointerDown={this.onPointerDown}
-                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
+                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} onPointerOver={this.onPointerEnter}
                 onClick={this.onClick}
                 style={{
                     outlineColor: "black",
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c3bf36553..25a75904b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -18,7 +18,7 @@ import "./DocumentView.scss";
 import React = require("react");
 import { Opt, Doc, WidthSym, HeightSym, DocListCast } from "../../../new_fields/Doc";
 import { DocComponent } from "../DocComponent";
-import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
 import { FieldValue, StrCast, BoolCast, Cast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
@@ -107,6 +107,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 handlers: { drop: this.drop.bind(this) }
             });
         }
+        // bcz: kind of ugly .. setup a reaction to update the title of a summary document's target (maximizedDocs) whenver the summary doc's title changes
         this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
             async () => {
                 let maxDoc = await DocListCast(this.props.Document.maximizedDocs);
@@ -142,10 +143,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         e.stopPropagation();
     }
 
-    startDragging(x: number, y: number, dropAction: dropActionType) {
+    startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) {
         if (this._mainCont.current) {
+            let allConnected = dragSubBullets ? [this.props.Document, ...Cast(this.props.Document.subBulletDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc)] : [this.props.Document];
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
-            let dragData = new DragManager.DocumentDragData([this.props.Document]);
+            let dragData = new DragManager.DocumentDragData(allConnected);
             const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.xOffset = xoff;
@@ -167,15 +169,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             SelectionManager.SelectDoc(this, e.ctrlKey);
         }
     }
+    _hitIsBullet = false;
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
         this._downY = e.clientY;
         if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) {
             return;
         }
+        this._hitIsBullet = (e.target && (e.target as any).id === "isBullet");
         if (e.shiftKey && e.buttons === 1) {
             if (this.props.isTopMost) {
-                this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined);
+                this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined, this._hitIsBullet);
             } else if (this.props.Document) {
                 CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
             }
@@ -193,7 +197,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 document.removeEventListener("pointermove", this.onPointerMove);
                 document.removeEventListener("pointerup", this.onPointerUp);
                 if (!e.altKey && !this.topMost && (!CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 1) || (CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 2)) {
-                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined);
+                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitIsBullet);
                 }
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
-- 
cgit v1.2.3-70-g09d2


From 2b5e170c1bbca43ab804d7746a816715c490cc37 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 9 May 2019 20:37:59 -0400
Subject: simplified templates.  made bullets always drag sub-bullets.

---
 src/client/views/Templates.tsx          | 15 +++------------
 src/client/views/nodes/DocumentView.tsx |  2 +-
 2 files changed, 4 insertions(+), 13 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 02f9aa510..bbedc95f1 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -38,18 +38,9 @@ export class Template {
 export namespace Templates {
     // export const BasicLayout = new Template("Basic layout", "{layout}");
 
-    export const OuterCaption = new Template("Outer caption", TemplatePosition.OutterBottom,
-        `<div id="screenSpace" style="margin-top: 100%; font-size:14px; background:yellow; width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={"caption"} /></div>`
+    export const Caption = new Template("Caption", TemplatePosition.OutterBottom,
+        `<div id="screenSpace" style="top: 100%; font-size:14px; background:yellow; width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={"caption"} /></div>`
     );
-
-    export const InnerCaption = new Template("Inner caption", TemplatePosition.InnerBottom,
-        `<div><div style="margin:auto; height:calc(100% - 50px); width:100%;">{layout}</div><div style="height:50px; width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={"caption"}/></div></div>`
-    );
-
-    export const SideCaption = new Template("Side caption", TemplatePosition.OutterRight,
-        `<div><div style="margin:auto; height:100%; width:100%;">{layout}</div><div style="height:100%; width:300px; position:absolute; top: 0; right: -300px;"><FormattedTextBox {...props} fieldKey={"caption"}/></div> </div>`
-    );
-
     export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop,
         `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div>
         <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; ">
@@ -72,7 +63,7 @@ export namespace Templates {
         </div>`
     );
 
-    export const TemplateList: Template[] = [Title, TitleOverlay, OuterCaption, InnerCaption, SideCaption, Bullet];
+    export const TemplateList: Template[] = [Title, TitleOverlay, Caption, Bullet];
 
     export function sortTemplates(a: Template, b: Template) {
         if (a.Position < b.Position) { return -1; }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 25a75904b..5aa74c703 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -176,7 +176,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) {
             return;
         }
-        this._hitIsBullet = (e.target && (e.target as any).id === "isBullet");
+        this._hitIsBullet = (e.target && (e.target as any).id === "isBullet") || Cast(this.props.Document.subBulletDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc).length > 0;
         if (e.shiftKey && e.buttons === 1) {
             if (this.props.isTopMost) {
                 this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined, this._hitIsBullet);
-- 
cgit v1.2.3-70-g09d2


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

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

(limited to 'src/client/views/nodes/DocumentView.tsx')

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


From 2767d6b439dd9e90543f955c68e521e03c690af0 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 13 May 2019 02:09:08 -0400
Subject: Added "find aliases" and "see collections that contain me"

---
 src/client/util/SearchUtil.ts                      | 25 +++++++++++++++++
 .../views/collections/ParentDocumentSelector.tsx   | 31 ++++++++++++++++++++--
 src/client/views/nodes/DocumentView.tsx            | 27 ++++++++++++-------
 src/new_fields/Doc.ts                              |  4 +--
 4 files changed, 73 insertions(+), 14 deletions(-)
 create mode 100644 src/client/util/SearchUtil.ts

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
new file mode 100644
index 000000000..4ccff0d1b
--- /dev/null
+++ b/src/client/util/SearchUtil.ts
@@ -0,0 +1,25 @@
+import * as rp from 'request-promise';
+import { DocServer } from '../DocServer';
+import { Doc } from '../../new_fields/Doc';
+import { Id } from '../../new_fields/RefField';
+
+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"), {
+            qs: { query }
+        }));
+        if (!returnDocs) {
+            return ids;
+        }
+        const docMap = await DocServer.GetRefFields(ids);
+        return ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc);
+    }
+
+    export async function GetAliasesOfDocument(doc: Doc): Promise<Doc[]> {
+        const proto = await Doc.GetT(doc, "proto", Doc, true);
+        const protoId = (proto || doc)[Id];
+        return Search(`{!join from=id to=proto_i}id:${protoId}`, true);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 1fdb9d4d9..dd1516da7 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -2,7 +2,34 @@ import * as React from "react";
 import './ParentDocumentSelector.scss';
 import { Doc } from "../../../new_fields/Doc";
 import { observer } from "mobx-react";
-import { observable, action } from "mobx";
+import { observable, action, runInAction } from "mobx";
+import { Id } from "../../../new_fields/RefField";
+import { SearchUtil } from "../../util/SearchUtil";
+import { CollectionDockingView } from "./CollectionDockingView";
+
+@observer
+export class SelectorContextMenu extends React.Component<{ Document: Doc }> {
+    @observable private _docs: Doc[] = [];
+
+    constructor(props: { Document: Doc }) {
+        super(props);
+
+        this.fetchDocuments();
+    }
+
+    async fetchDocuments() {
+        const docs = await SearchUtil.Search(`data_l:${this.props.Document[Id]}`, true);
+        runInAction(() => this._docs = docs);
+    }
+
+    render() {
+        return (
+            <>
+                {this._docs.map(doc => <p><a onClick={() => CollectionDockingView.Instance.AddRightSplit(doc)}>{doc.title}</a></p>)}
+            </>
+        );
+    }
+}
 
 @observer
 export class ParentDocSelector extends React.Component<{ Document: Doc }> {
@@ -23,7 +50,7 @@ export class ParentDocSelector extends React.Component<{ Document: Doc }> {
         if (this.hover) {
             flyout = (
                 <div className="PDS-flyout">
-                    <p>Hello world</p>
+                    <SelectorContextMenu Document={this.props.Document} />
                 </div>
             );
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 90f67db7c..edc2158f0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -26,6 +26,7 @@ import { CurrentUserUtils } from "../../../server/authentication/models/current_
 import { DocServer } from "../../DocServer";
 import { Id } from "../../../new_fields/RefField";
 import { PresentationView } from "../PresentationView";
+import { SearchUtil } from "../../util/SearchUtil";
 
 const linkSchema = createSchema({
     title: "string",
@@ -287,16 +288,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         e.preventDefault();
 
-        ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked });
-        ContextMenu.Instance.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton });
-        ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked });
-        ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) });
-        ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) });
-        ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])) });
-        ContextMenu.Instance.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]) });
-        //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
-        ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document) });
-        ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked });
+        const cm = ContextMenu.Instance;
+        cm.addItem({ description: "Full Screen", event: this.fullScreenClicked });
+        cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton });
+        cm.addItem({ description: "Fields", event: this.fieldsClicked });
+        cm.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) });
+        cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) });
+        cm.addItem({
+            description: "Find aliases", event: async () => {
+                const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
+                CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(aliases, {}));
+            }
+        });
+        cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])) });
+        cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]) });
+        cm.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document) });
+        cm.addItem({ description: "Delete", event: this.deleteClicked });
         if (!this.topMost) {
             // DocumentViews should stop propagation of this event
             e.stopPropagation();
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 46ccb3e90..c08049a39 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -130,8 +130,8 @@ export namespace Doc {
         const self = doc[Self];
         return getField(self, key, ignoreProto);
     }
-    export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): T | null | undefined {
-        return Cast(Get(doc, key, ignoreProto), ctor) as T | null | undefined;
+    export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
+        return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
     }
     export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
         const proto = doc.proto;
-- 
cgit v1.2.3-70-g09d2


From 3be42131bc08024f06e0daec8d09e45bf3f1ddab Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 13 May 2019 16:21:28 -0400
Subject: a bunch of fixes to schemas, marquees, and an experimental feature to
 set a document's data with linking UI

---
 src/client/util/DragManager.ts                             |  6 ++++--
 src/client/views/DocumentDecorations.tsx                   |  2 ++
 src/client/views/collections/CollectionDockingView.tsx     |  7 ++-----
 src/client/views/collections/CollectionSchemaView.tsx      |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx          |  4 ++--
 .../views/collections/collectionFreeForm/MarqueeView.tsx   |  8 +++-----
 src/client/views/nodes/DocumentContentsView.tsx            |  9 +++++++--
 src/client/views/nodes/DocumentView.tsx                    | 13 +++++++++++--
 src/client/views/nodes/ImageBox.tsx                        |  3 ++-
 src/client/views/nodes/KeyValueBox.tsx                     |  6 +++---
 src/client/views/nodes/LinkMenu.tsx                        |  2 +-
 src/client/views/nodes/PDFBox.tsx                          |  4 ++--
 src/new_fields/Doc.ts                                      | 14 +++++++++-----
 13 files changed, 49 insertions(+), 31 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 2da0d5b51..26da34e67 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -105,7 +105,8 @@ export namespace DragManager {
         constructor(
             readonly x: number,
             readonly y: number,
-            readonly data: { [id: string]: any }
+            readonly data: { [id: string]: any },
+            readonly mods: string
         ) { }
     }
 
@@ -334,7 +335,8 @@ export namespace DragManager {
                     detail: {
                         x: e.x,
                         y: e.y,
-                        data: dragData
+                        data: dragData,
+                        mods: e.ctrlKey ? "Control" : ""
                     }
                 })
             );
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4786b4de6..5aa3d804d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -239,6 +239,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             this._removeIcon = snapped;
         }
     }
+    @undoBatch
     @action
     onMinimizeUp = (e: PointerEvent): void => {
         e.stopPropagation();
@@ -270,6 +271,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         runInAction(() => this._minimizedX = this._minimizedY = 0);
     }
 
+    @undoBatch
     @action createIcon = (selected: DocumentView[], layoutString: string): Doc => {
         let doc = selected[0].props.Document;
         let iconDoc = Docs.IconDocument(layoutString);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 0aa067972..a755e0f91 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -5,7 +5,7 @@ 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 } from "../../../new_fields/Doc";
+import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc";
 import { FieldId, Id } from "../../../new_fields/RefField";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
@@ -313,10 +313,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     counter.DashDocId = tab.contentItem.config.props.documentId;
                     tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title],
                         () => {
-                            const lf = Cast(doc.linkedFromDocs, listSpec(Doc), []);
-                            const lt = Cast(doc.linkedToDocs, listSpec(Doc), []);
-                            let count = (lf ? lf.length : 0) + (lt ? lt.length : 0);
-                            counter.innerHTML = count;
+                            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;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 4984e26d1..9ecccc559 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -254,7 +254,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     get previewPanel() {
         // let doc = CompileScript(this.previewScript, { this: selected }, true)();
         const previewDoc = this.previewDocument;
-        return (<div className="collectionSchemaView-previewRegion" style={{ width: `${this.previewRegionWidth}px` }}>
+        return (<div className="collectionSchemaView-previewRegion" style={{ width: `${Math.max(0, this.previewRegionWidth - 1)}px` }}>
             {!previewDoc || !this.previewRegionWidth ? (null) : (
                 <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
                     <DocumentView Document={previewDoc} isTopMost={false} selectOnLoad={false}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dc86f38b6..c36c708db 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -339,7 +339,7 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps &
             isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
     render() {
-        return this.overlayView;
+        return this.props.Document.overlayLayout ? this.overlayView : (null);
     }
 }
 
@@ -350,7 +350,7 @@ class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps
             isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
     render() {
-        return this.backgroundView;
+        return this.props.Document.backgroundLayout ? this.backgroundView : (null);
     }
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 1c29ebaae..24dea200e 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -104,10 +104,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                             continue;
                         }
                         let doc = new Doc();
-                        columns.forEach((col, i) => {
-                            console.log(values[i] + " " + Number(values[i]).toString());
-                            doc[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined);
-                        });
+                        columns.forEach((col, i) => doc[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined));
                         if (groupAttr) {
                             doc["_group"] = groupAttr;
                         }
@@ -207,7 +204,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     @undoBatch
     @action
     marqueeCommand = async (e: KeyboardEvent) => {
-        if (this._commandExecuted) {
+        if (this._commandExecuted || (e as any).propagationIsStopped) {
             return;
         }
         if (e.key === "Backspace" || e.key === "Delete" || e.key === "d") {
@@ -224,6 +221,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         if (e.key === "c" || e.key === "r" || e.key === "s" || e.key === "e" || e.key === "p") {
             this._commandExecuted = true;
             e.stopPropagation();
+            (e as any).propagationIsStopped = true;
             let bounds = this.Bounds;
             let selected = this.marqueeSelect().map(d => {
                 if (e.key === "s") {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f404b7bc6..3ddf8eb00 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -43,9 +43,14 @@ const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
 export class DocumentContentsView extends React.Component<DocumentViewProps & {
     isSelected: () => boolean,
     select: (ctrl: boolean) => void,
-    layoutKey: string
+    layoutKey: string,
 }> {
-    @computed get layout(): string { return Cast(this.props.Document[this.props.layoutKey], "string", this.props.layoutKey === "layout" ? "<p>Error loading layout data</p>" : ""); }
+    @computed get layout(): string {
+        return StrCast(this.props.Document[this.props.layoutKey],
+            this.props.Document.data ?
+                "<FieldView {...props} fieldKey='data' />" :
+                KeyValueBox.LayoutString(this.props.Document.proto ? "proto" : ""));
+    }
 
     CreateBindings(): JsxBindings {
         return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index edc2158f0..b7ad9249a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -243,8 +243,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
             const protoDest = destDoc.proto;
             const protoSrc = sourceDoc.proto;
-            Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
-            de.data.droppedDocuments.push(destDoc);
+            if (de.mods == "Control") {
+                let src = protoSrc ? protoSrc : sourceDoc;
+                let dst = protoDest ? protoDest : destDoc;
+                dst.data = src;
+                dst.nativeWidth = src.nativeWidth;
+                dst.nativeHeight = src.nativeHeight;
+            }
+            else {
+                Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
+                de.data.droppedDocuments.push(destDoc);
+            }
             e.stopPropagation();
         }
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f9659a4b2..05742f8d1 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -42,8 +42,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     onLoad = (target: any) => {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
+        console.log("title: " + this.Document.title);
         if (this._photoIndex === 0) {
-            this.Document.nativeHeight = FieldValue(this.Document.nativeWidth, 0) * h / w;
+            Doc.SetOnPrototype(this.Document, "nativeHeight", FieldValue(this.Document.nativeWidth, 0) * h / w);
             this.Document.height = FieldValue(this.Document.width, 0) * h / w;
         }
     }
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 876a3c173..c9d665ceb 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -18,7 +18,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     @observable private _keyInput: string = "";
     @observable private _valueInput: string = "";
     @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
-
+    get fieldDocToLayout() { return this.props.fieldKey ? FieldValue(Cast(this.props.Document[this.props.fieldKey], Doc)) : this.props.Document }
 
     constructor(props: FieldViewProps) {
         super(props);
@@ -28,7 +28,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     onEnterKey = (e: React.KeyboardEvent): void => {
         if (e.key === 'Enter') {
             if (this._keyInput && this._valueInput) {
-                let doc = FieldValue(Cast(this.props.Document.data, Doc));
+                let doc = this.fieldDocToLayout;
                 if (!doc) {
                     return;
                 }
@@ -60,7 +60,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     }
 
     createTable = () => {
-        let doc = FieldValue(Cast(this.props.Document.data, Doc));
+        let doc = this.fieldDocToLayout;
         if (!doc) {
             return <tr><td>Loading...</td></tr>;
         }
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 7bf13d5f9..5dabfc30d 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -32,7 +32,7 @@ export class LinkMenu extends React.Component<Props> {
     render() {
         //get list of links from document
         let linkFrom = DocListCast(this.props.docView.props.Document.linkedFromDocs);
-        let linkTo = DocListCast(this.props.docView.props.Document.linkedToDoc);
+        let linkTo = DocListCast(this.props.docView.props.Document.linkedToDocs);
         if (this._editingLink === undefined) {
             return (
                 <div id="linkMenu-container">
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index cb27b3f1b..ff8737192 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
 import * as htmlToImage from "html-to-image";
-import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import 'react-image-lightbox/style.css';
 import Measure from "react-measure";
@@ -223,7 +223,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
             document.addEventListener("pointerup", this.onPointerUp);
         }
         if (this.props.isSelected() && e.buttons === 2) {
-            this._alt = true;
+            runInAction(() => this._alt = true);
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointerup", this.onPointerUp);
         }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index a8c9d28e1..4c837fcbd 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -173,11 +173,15 @@ export namespace Doc {
     export function MakeAlias(doc: Doc) {
         const alias = new Doc;
 
-        PromiseValue(Cast(doc.proto, Doc)).then(proto => {
-            if (proto) {
-                alias.proto = proto;
-            }
-        });
+        if (!doc.proto) {
+            alias.proto = doc;
+        } else {
+            PromiseValue(Cast(doc.proto, Doc)).then(proto => {
+                if (proto) {
+                    alias.proto = proto;
+                }
+            });
+        }
 
         return alias;
     }
-- 
cgit v1.2.3-70-g09d2


From 281bfa589bd1ca66823003ca91cc11ce895fe5e2 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 14 May 2019 18:59:32 -0400
Subject: fixes for northstar

---
 src/client/documents/Documents.ts                  |  9 ++-
 src/client/northstar/dash-fields/HistogramField.ts | 11 ++--
 .../dash-nodes/HistogramBinPrimitiveCollection.ts  |  4 +-
 src/client/northstar/dash-nodes/HistogramBox.tsx   | 16 +++---
 .../dash-nodes/HistogramBoxPrimitives.tsx          |  3 -
 src/client/northstar/manager/Gateway.ts            | 12 ++++
 src/client/northstar/model/ModelHelpers.ts         | 10 +++-
 src/client/northstar/model/idea/idea.ts            |  6 ++
 src/client/northstar/operations/BaseOperation.ts   | 17 ------
 .../northstar/operations/HistogramOperation.ts     | 12 ++--
 src/client/views/Main.tsx                          | 54 +-----------------
 .../views/collections/CollectionSchemaView.tsx     | 39 ++++++++++++-
 .../collections/collectionFreeForm/MarqueeView.tsx |  7 ++-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 .../authentication/models/current_user_utils.ts    | 65 ++++++++++++++++++++--
 15 files changed, 162 insertions(+), 105 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 11929455c..ed260d42e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -33,6 +33,7 @@ import { DocServer } from "../DocServer";
 import { StrokeData, InkField } from "../../new_fields/InkField";
 import { dropActionType } from "../util/DragManager";
 import { DateField } from "../../new_fields/DateField";
+import { schema } from "prosemirror-schema-basic";
 
 export interface DocumentOptions {
     x?: number;
@@ -207,16 +208,18 @@ export namespace Docs {
     export function PdfDocument(url: string, options: DocumentOptions = {}) {
         return CreateInstance(pdfProto, new PdfField(new URL(url)), options);
     }
+
     export async function DBDocument(url: string, options: 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));
+            let schemaDocuments = Cast(schemaDoc.data, listSpec(Doc), []);
             if (!schemaDocuments) {
                 return;
             }
+            CurrentUserUtils.AddNorthstarSchema(schema, schemaDoc);
             const docs = schemaDocuments;
             CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
                 DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
@@ -251,8 +254,8 @@ export namespace Docs {
         }
         return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Freeform });
     }
-    export function SchemaDocument(documents: Array<Doc>, options: DocumentOptions) {
-        return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Schema });
+    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 });
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index f01f08487..aabc77bb2 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -9,7 +9,8 @@ import { OmitKeys } from "../../../Utils";
 import { Deserializable } from "../../util/SerializationHelper";
 
 function serialize(field: HistogramField) {
-    return OmitKeys(field.HistoOp, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit;
+    let obj = OmitKeys(field, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit;
+    return obj;
 }
 
 function deserialize(jp: any) {
@@ -31,10 +32,10 @@ function deserialize(jp: any) {
             }
         });
         if (X && Y && V) {
-            return new HistogramField(new HistogramOperation(jp.SchemaName, X, Y, V, jp.Normalization));
+            return new HistogramOperation(jp.SchemaName, X, Y, V, jp.Normalization);
         }
     }
-    return new HistogramField(HistogramOperation.Empty);
+    return HistogramOperation.Empty;
 }
 
 @Deserializable("histogramField")
@@ -50,6 +51,8 @@ export class HistogramField extends ObjectField {
     }
 
     [Copy]() {
-        return new HistogramField(this.HistoOp.Copy());
+        let y = this.HistoOp;
+        let z = this.HistoOp["Copy"];
+        return new HistogramField(HistogramOperation.Duplicate(this.HistoOp));
     }
 }
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
index 6291ec1fc..3e9145a1b 100644
--- a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
+++ b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
@@ -198,8 +198,8 @@ export class HistogramBinPrimitiveCollection {
         var marginParams = new MarginAggregateParameters();
         marginParams.aggregateFunction = axis.AggregateFunction;
         var marginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis, this.histoResult, brush.brushIndex!, marginParams);
-        var marginResult = ModelHelpers.GetAggregateResult(bin, marginAggregateKey) as MarginAggregateResult;
-        return !marginResult ? 0 : marginResult.absolutMargin!;
+        let aggResult = ModelHelpers.GetAggregateResult(bin, marginAggregateKey);
+        return aggResult instanceof MarginAggregateResult && aggResult.absolutMargin ? aggResult.absolutMargin : 0;
     }
 
     private createBinPrimitive(barAxis: number, brush: Brush, marginRect: PIXIRectangle,
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index ed556cf45..eb1ad69b7 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -17,9 +17,8 @@ import "./HistogramBox.scss";
 import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
 import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
 import { StyleConstants } from "../utils/StyleContants";
-import { NumCast, Cast } from "../../../new_fields/Types";
-import { listSpec } from "../../../new_fields/Schema";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Cast } from "../../../new_fields/Types";
+import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/RefField";
 
 
@@ -119,15 +118,16 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                 if (this.HistoOp !== HistogramOperation.Empty) {
                     reaction(() => DocListCast(this.props.Document.linkedFromDocs), (docs) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
                     reaction(() => DocListCast(this.props.Document.brushingDocs).length,
-                        () => {
-                            let brushingDocs = DocListCast(this.props.Document.brushingDocs);
+                        async () => {
+                            let brushingDocs = await DocListCastAsync(this.props.Document.brushingDocs);
                             const proto = this.props.Document.proto;
-                            if (proto) {
-                                this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => {
+                            if (proto && brushingDocs) {
+                                let mapped = brushingDocs.map((brush, i) => {
                                     brush.backgroundColor = StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length];
                                     let brushed = DocListCast(brush.brushingDocs);
                                     return { l: brush, b: brushed[0][Id] === proto[Id] ? brushed[1] : brushed[0] };
-                                }));
+                                });
+                                this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...mapped);
                             }
                         }, { fireImmediately: true });
                     reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
index 721bf6a89..350987695 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -11,9 +11,6 @@ import { StyleConstants } from "../../northstar/utils/StyleContants";
 import { HistogramBinPrimitiveCollection, HistogramBinPrimitive } from "./HistogramBinPrimitiveCollection";
 import { HistogramBox } from "./HistogramBox";
 import "./HistogramBoxPrimitives.scss";
-import { JSXElement } from "babel-types";
-import { Utils } from "../utils/Utils";
-import { all } from "bluebird";
 
 export interface HistogramPrimitivesProps {
     HistoBox: HistogramBox;
diff --git a/src/client/northstar/manager/Gateway.ts b/src/client/northstar/manager/Gateway.ts
index d26f2724f..c541cce6a 100644
--- a/src/client/northstar/manager/Gateway.ts
+++ b/src/client/northstar/manager/Gateway.ts
@@ -23,6 +23,18 @@ export class Gateway {
         }
     }
 
+    public async PostSchema(csvdata: string, schemaname: string): Promise<string> {
+        try {
+            const json = await this.MakePostJsonRequest("postSchema", { csv: csvdata, schema: schemaname });
+            // const cat = Catalog.fromJS(json);
+            // return cat;
+            return json;
+        }
+        catch (error) {
+            throw new Error("can not reach northstar's backend");
+        }
+    }
+
     public async GetSchema(pathname: string, schemaname: string): Promise<Catalog> {
         try {
             const json = await this.MakeGetRequest("schema", undefined, { path: pathname, schema: schemaname });
diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts
index ac807b41f..80bb71224 100644
--- a/src/client/northstar/model/ModelHelpers.ts
+++ b/src/client/northstar/model/ModelHelpers.ts
@@ -31,7 +31,15 @@ export class ModelHelpers {
     }
 
     public static GetAggregateParametersIndex(histogramResult: HistogramResult, aggParameters?: AggregateParameters): number {
-        return ArrayUtil.IndexOfWithEqual(histogramResult.aggregateParameters!, aggParameters);
+        return Array.from(histogramResult.aggregateParameters!).findIndex((value, i, set) => {
+            if (set[i] instanceof CountAggregateParameters && value instanceof CountAggregateParameters)
+                return true;
+            if (set[i] instanceof MarginAggregateParameters && value instanceof MarginAggregateParameters)
+                return true;
+            if (set[i] instanceof SumAggregateParameters && value instanceof SumAggregateParameters)
+                return true;
+            return false;
+        });
     }
 
     public static GetAggregateParameter(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel): AggregateParameters | undefined {
diff --git a/src/client/northstar/model/idea/idea.ts b/src/client/northstar/model/idea/idea.ts
index 9d9d60678..c73a822c7 100644
--- a/src/client/northstar/model/idea/idea.ts
+++ b/src/client/northstar/model/idea/idea.ts
@@ -22,6 +22,9 @@ export abstract class AggregateParameters implements IAggregateParameters {
 
     protected _discriminator: string;
 
+    public Equals(other: Object): boolean {
+        return this == other;
+    }
     constructor(data?: IAggregateParameters) {
         if (data) {
             for (var property in data) {
@@ -204,6 +207,9 @@ export interface IAverageAggregateParameters extends ISingleDimensionAggregatePa
 export abstract class AttributeParameters implements IAttributeParameters {
     visualizationHints?: VisualizationHint[] | undefined;
     rawName?: string | undefined;
+    public Equals(other: Object): boolean {
+        return this == other;
+    }
 
     protected _discriminator: string;
 
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
index c6d5f0a15..0d1361ebf 100644
--- a/src/client/northstar/operations/BaseOperation.ts
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -25,23 +25,6 @@ export abstract class BaseOperation {
 
     @computed
     public get FilterString(): string {
-
-        // let filterModels: FilterModel[] = [];
-        // return FilterModel.GetFilterModelsRecursive(this, new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, true)
-        // if (this.OverridingFilters.length > 0) {
-        //     return "(" + this.OverridingFilters.filter(fm => fm !== null).map(fm => fm.ToPythonString()).join(" || ") + ")";
-        // }
-        // let rdg = MainManager.Instance.MainViewModel.FilterReverseDependencyGraph;
-        // let sliceModel = this.TypedViewModel.IncomingSliceModel;
-        // if (sliceModel !== null && sliceModel.Source !== null && instanceOfIBaseFilterProvider(sliceModel.Source) && rdg.has(sliceModel.Source)) {
-        //     let filterModels = sliceModel.Source.FilterModels.map(f => f);
-        //     return FilterModel.GetFilterModelsRecursive(rdg.get(sliceModel.Source), new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, false);
-        // }
-
-        // if (rdg.has(this.TypedViewModel)) {
-        //     let filterModels = [];
-        //     return FilterModel.GetFilterModelsRecursive(rdg.get(this.TypedViewModel), new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, true)
-        // }
         return "";
     }
 
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index 78b206bdc..74e23ea48 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -30,7 +30,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     @observable public V: AttributeTransformationModel;
     @observable public SchemaName: string;
     @observable public QRange: QuantitativeBinRange | undefined;
-    @computed public get Schema() { return CurrentUserUtils.GetNorthstarSchema(this.SchemaName); }
+    public get Schema() { return CurrentUserUtils.GetNorthstarSchema(this.SchemaName); }
 
     constructor(schemaName: string, x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel, normalized?: number) {
         super();
@@ -41,7 +41,11 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
         this.SchemaName = schemaName;
     }
 
-    Copy(): HistogramOperation {
+    public static Duplicate(op: HistogramOperation) {
+
+        return new HistogramOperation(op.SchemaName, op.X, op.Y, op.V, op.Normalization);
+    }
+    public Copy(): HistogramOperation {
         return new HistogramOperation(this.SchemaName, this.X, this.Y, this.V, this.Normalization);
     }
 
@@ -50,7 +54,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     }
 
 
-    @computed public get FilterModels() {
+    public get FilterModels() {
         return this.BarFilterModels;
     }
     @action
@@ -71,9 +75,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
         return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true);
     }
 
-    @computed
     public get BrushString(): string[] {
-        trace();
         let brushes: string[] = [];
         this.BrushLinks.map(brushLink => {
             let brushHistogram = Cast(brushLink.b.data, HistogramField);
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 66205f8ca..385fc6ead 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -12,13 +12,6 @@ import { CurrentUserUtils } from '../../server/authentication/models/current_use
 import { RouteStore } from '../../server/RouteStore';
 import { emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils';
 import { Docs } from '../documents/Documents';
-import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
-import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
-import { Gateway, NorthstarSettings } from '../northstar/manager/Gateway';
-import { AggregateFunction, Catalog } from '../northstar/model/idea/idea';
-import '../northstar/model/ModelExtensions';
-import { HistogramOperation } from '../northstar/operations/HistogramOperation';
-import '../northstar/utils/Extensions';
 import { SetupDrag, DragManager } from '../util/DragManager';
 import { Transform } from '../util/Transform';
 import { UndoManager } from '../util/UndoManager';
@@ -70,8 +63,6 @@ export class Main extends React.Component {
             }
         }
 
-        CurrentUserUtils.loadCurrentUser();
-
         library.add(faFont);
         library.add(faImage);
         library.add(faFilePdf);
@@ -84,15 +75,8 @@ export class Main extends React.Component {
         library.add(faFilm);
         library.add(faMusic);
         library.add(faTree);
-
         this.initEventListeners();
         this.initAuthenticationRouters();
-
-        // try {
-        //     this.initializeNorthstar();
-        // } catch (e) {
-
-        // }
     }
 
     componentDidMount() { window.onpopstate = this.onHistory; }
@@ -155,7 +139,7 @@ export class Main extends React.Component {
             // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
             setTimeout(() => {
                 this.openWorkspace(mainDoc);
-                let pendingDocument = Docs.SchemaDocument([], { title: "New Mobile Uploads" });
+                let pendingDocument = Docs.SchemaDocument(["title"], [], { title: "New Mobile Uploads" });
                 mainDoc.optionalRightCollection = pendingDocument;
             }, 0);
         }
@@ -229,7 +213,7 @@ export class Main extends React.Component {
 
         let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
         let addColNode = action(() => Docs.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
-        let addSchemaNode = action(() => Docs.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
+        let addSchemaNode = action(() => Docs.SchemaDocument(["title"], [], { width: 200, height: 200, title: "a schema collection" }));
         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" }));
@@ -300,40 +284,6 @@ export class Main extends React.Component {
             </div>
         );
     }
-
-    // --------------- Northstar hooks ------------- /
-    private _northstarSchemas: Doc[] = [];
-
-    @action SetNorthstarCatalog(ctlog: Catalog) {
-        CurrentUserUtils.NorthstarDBCatalog = ctlog;
-        if (ctlog && ctlog.schemas) {
-            ctlog.schemas.map(schema => {
-                let schemaDocuments: Doc[] = [];
-                let attributesToBecomeDocs = CurrentUserUtils.GetAllNorthstarColumnAttributes(schema);
-                Promise.all(attributesToBecomeDocs.reduce((promises, attr) => {
-                    promises.push(DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
-                        if (field instanceof Doc) {
-                            schemaDocuments.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));
-                            schemaDocuments.push(Docs.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }));
-                        }
-                    })));
-                    return promises;
-                }, [] as Promise<void>[])).finally(() =>
-                    this._northstarSchemas.push(Docs.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! })));
-            });
-        }
-    }
-    async initializeNorthstar(): Promise<void> {
-        const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" });
-        NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
-        Gateway.Instance.ClearCatalog().then(async () => this.SetNorthstarCatalog(await Gateway.Instance.GetCatalog()));
-    }
 }
 
 (async () => {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 9ecccc559..f4ad5b357 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -20,11 +20,20 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import "./CollectionSchemaView.scss";
 import { CollectionSubView } from "./CollectionSubView";
 import { Opt, Field, Doc, DocListCastAsync, DocListCast } from "../../../new_fields/Doc";
-import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
 import { List } from "../../../new_fields/List";
 import { Id } from "../../../new_fields/RefField";
 import { isUndefined } from "typescript-collections/dist/lib/util";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { Gateway } from "../../northstar/manager/Gateway";
+import { DocServer } from "../../DocServer";
+import { ColumnAttributeModel } from "../../northstar/core/attribute/AttributeModel";
+import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
+import { AggregateFunction } from "../../northstar/model/idea/idea";
+import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
+import { Docs } from "../../documents/Documents";
+import { ContextMenu } from "../ContextMenu";
 
 
 // bcz: need to add drag and drop of rows and columns.  This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
@@ -207,6 +216,32 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
         }
     }
 
+    onContextMenu = (e: React.MouseEvent): void => {
+        if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+            ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB });
+        }
+    }
+
+    @action
+    makeDB = async () => {
+        let csv: string = this.columns.reduce((val, col) => val + col + ",", "");
+        csv = csv.substr(0, csv.length - 1) + "\n";
+        let self = this;
+        DocListCast(this.props.Document.data).map(doc => {
+            csv += self.columns.reduce((val, col) => val + (doc[col] ? doc[col]!.toString() : "") + ",", "");
+            csv = csv.substr(0, csv.length - 1) + "\n";
+        })
+        csv.substring(0, csv.length - 1);
+        let dbName = StrCast(this.props.Document.title);
+        let res = await Gateway.Instance.PostSchema(csv, dbName);
+        if (self.props.CollectionView.props.addDocument) {
+            let schemaDoc = await Docs.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName });
+            if (schemaDoc) {
+                self.props.CollectionView.props.addDocument(schemaDoc, false); 
+            }
+        }
+    }
+
     @action
     addColumn = () => {
         this.columns.push(this._newKeyName);
@@ -325,7 +360,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
         const children = this.children;
         return (
             <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
-                onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createTarget}>
+                onDrop={(e: React.DragEvent) => this.onDrop(e, {})} onContextMenu={this.onContextMenu} ref={this.createTarget}>
                 <div className="collectionSchemaView-tableContainer" style={{ width: `${this.tableWidth}px` }}>
                     <ReactTable data={children} page={0} pageSize={children.length} showPagination={false}
                         columns={this.columns.map(col => ({
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 24dea200e..be7cddca6 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -17,6 +17,9 @@ import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { ImageField } from "../../../../new_fields/URLField";
 import { Template, Templates } from "../../Templates";
+import { Gateway } from "../../../northstar/manager/Gateway";
+import { DocServer } from "../../../DocServer";
+import { Id } from "../../../../new_fields/RefField";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -111,8 +114,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                         doc.title = i.toString();
                         docList.push(doc);
                     }
-                    let newCol = Docs.SchemaDocument(docList, { x: x, y: y, title: "-dropped table-", width: 300, height: 100 });
-                    newCol.proto!.schemaColumns = new List<string>([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)]);
+                    let newCol = Docs.SchemaDocument([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 });
+
                     this.props.addDocument(newCol, false);
                 }
             })();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b7ad9249a..c97bcbd94 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -306,7 +306,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({
             description: "Find aliases", event: async () => {
                 const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
-                CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(aliases, {}));
+                CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(["title"], aliases, {}));
             }
         });
         cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])) });
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 5f45d7bcc..5b63ac356 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -1,15 +1,21 @@
 import { computed, observable, action, runInAction } from "mobx";
 import * as rp from 'request-promise';
 import { Docs } from "../../../client/documents/Documents";
-import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/northstar/model/idea/idea";
+import { Attribute, AttributeGroup, Catalog, Schema, AggregateFunction } from "../../../client/northstar/model/idea/idea";
 import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
 import { RouteStore } from "../../RouteStore";
 import { DocServer } from "../../../client/DocServer";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, Opt, Field } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView";
 import { CollectionTreeView } from "../../../client/views/collections/CollectionTreeView";
 import { CollectionView } from "../../../client/views/collections/CollectionView";
+import { NorthstarSettings, Gateway } from "../../../client/northstar/manager/Gateway";
+import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
+import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
+import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
+import { Cast, PromiseValue } from "../../../new_fields/Types";
+import { listSpec } from "../../../new_fields/Schema";
 
 export class CurrentUserUtils {
     private static curr_email: string;
@@ -31,13 +37,13 @@ export class CurrentUserUtils {
         doc.title = this.email;
         doc.data = new List<Doc>();
         doc.excludeFromLibrary = true;
-        doc.optionalRightCollection = Docs.SchemaDocument([], { title: "Pending documents" });
+        doc.optionalRightCollection = Docs.SchemaDocument(["title"], [], { title: "Pending documents" });
         // doc.library = Docs.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` });
         // (doc.library as Doc).excludeFromLibrary = true;
         return doc;
     }
 
-    public static loadCurrentUser(): Promise<any> {
+    public static async loadCurrentUser(): Promise<any> {
         let userPromise = rp.get(DocServer.prepend(RouteStore.getCurrUser)).then(response => {
             if (response) {
                 let obj = JSON.parse(response);
@@ -47,7 +53,7 @@ export class CurrentUserUtils {
                 throw new Error("There should be a user! Why does Dash think there isn't one?");
             }
         });
-        let userDocPromise = rp.get(DocServer.prepend(RouteStore.getUserDocumentId)).then(id => {
+        let userDocPromise = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId)).then(id => {
             if (id) {
                 return DocServer.GetRefField(id).then(field =>
                     runInAction(() => this.user_document = field instanceof Doc ? field : this.createUserDocument(id)));
@@ -55,14 +61,63 @@ export class CurrentUserUtils {
                 throw new Error("There should be a user id! Why does Dash think there isn't one?");
             }
         });
+        try {
+            const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" });
+            NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
+            await Gateway.Instance.ClearCatalog();
+            const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
+            let extras = await Promise.all(extraSchemas.map(async sc => await Gateway.Instance.GetSchema("", sc)));
+            let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras);
+            if (catprom) await Promise.all(catprom);
+        } catch (e) {
+
+        }
         return Promise.all([userPromise, userDocPromise]);
     }
 
     /* Northstar catalog ... really just for testing so this should eventually go away */
+    // --------------- Northstar hooks ------------- /
+    static _northstarSchemas: Doc[] = [];
     @observable private static _northstarCatalog?: Catalog;
     @computed public static get NorthstarDBCatalog() { return this._northstarCatalog; }
+
+    @action static SetNorthstarCatalog(ctlog: Catalog, extras: Catalog[]) {
+        CurrentUserUtils.NorthstarDBCatalog = ctlog;
+        if (ctlog && ctlog.schemas) {
+            extras.map(ex => ctlog.schemas!.push(ex));
+            return ctlog.schemas.map(async schema => {
+                let schemaDocuments: Doc[] = [];
+                let attributesToBecomeDocs = CurrentUserUtils.GetAllNorthstarColumnAttributes(schema);
+                await Promise.all(attributesToBecomeDocs.reduce((promises, attr) => {
+                    promises.push(DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
+                        if (field instanceof Doc) {
+                            schemaDocuments.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));
+                            schemaDocuments.push(Docs.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! }));
+            });
+        }
+    }
     public static set NorthstarDBCatalog(ctlog: Catalog | undefined) { this._northstarCatalog = ctlog; }
 
+    public static AddNorthstarSchema(schema: Schema, schemaDoc: Doc) {
+        if (this._northstarCatalog && CurrentUserUtils._northstarSchemas) {
+            this._northstarCatalog.schemas!.push(schema);
+            CurrentUserUtils._northstarSchemas.push(schemaDoc);
+            let schemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
+            schemas.push(schema.displayName!);
+            CurrentUserUtils.UserDocument.DBSchemas = new List<string>(schemas);
+        }
+    }
     public static GetNorthstarSchema(name: string): Schema | undefined {
         return !this._northstarCatalog || !this._northstarCatalog.schemas ? undefined :
             ArrayUtil.FirstOrDefault<Schema>(this._northstarCatalog.schemas, (s: Schema) => s.displayName === name);
-- 
cgit v1.2.3-70-g09d2


From 4ed116991b86e4667fd78eb9ea034e3a9cc75a3e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 15 May 2019 00:42:35 -0400
Subject: small fixes to start allowing trails of links.

---
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 10 +++++-----
 src/client/views/nodes/DocumentView.tsx                   |  4 ++--
 src/client/views/nodes/FormattedTextBox.tsx               |  2 +-
 3 files changed, 8 insertions(+), 8 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 2d4900509..fc1dc2b1e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -204,11 +204,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                         this.toggleIcon(expandedDocs);
                     }
                 }
-                let linkedDocs = [
-                    ...(linkedFromDocs ? linkedFromDocs.map(l => l.linkedFrom as Doc) : []),
-                    ...(linkedToDocs ? linkedToDocs.map(l => l.linkedTo as Doc) : [])];
-                if (linkedDocs) {
-                    DocumentManager.Instance.jumpToDocument(linkedDocs[0]);
+                let linkedFwdDocs = [
+                    linkedToDocs ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
+                    linkedFromDocs ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
+                if (linkedFwdDocs) {
+                    DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0]);
                 }
             }
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c97bcbd94..b88d18046 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -221,8 +221,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
         doc.isButton = !BoolCast(doc.isButton, false);
         if (doc.isButton && !doc.nativeWidth) {
-            doc.nativeWidth = doc[WidthSym]();
-            doc.nativeHeight = doc[HeightSym]();
+            doc.nativeWidth = this.props.Document[WidthSym]();
+            doc.nativeHeight = this.props.Document[HeightSym]();
         }
     }
     fullScreenClicked = (e: React.MouseEvent): void => {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7ebc31b0a..cb09ad42c 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -229,7 +229,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             if (e.target && (e.target as any).href) {
                 let href = (e.target as any).href;
                 if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
-                    let docid = href.replace(DocServer.prepend("/doc/"), "");
+                    let docid = href.replace(DocServer.prepend("/doc/"), "").split("%")[0];
                     DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
                         if (f instanceof Doc) {
                             if (DocumentManager.Instance.getDocumentView(f))
-- 
cgit v1.2.3-70-g09d2


From d79d721baa25947dfbbd17eda173835d9ae93fa6 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 15 May 2019 15:05:57 -0400
Subject: several smaller fixes.

---
 src/client/views/DocumentDecorations.tsx           | 30 +++++++------
 .../views/collections/ParentDocumentSelector.tsx   |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 17 ++-----
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 52 ++++++++++++----------
 src/client/views/nodes/DocumentView.tsx            |  8 ++--
 src/client/views/nodes/FormattedTextBox.tsx        |  2 +-
 src/client/views/nodes/IconBox.scss                |  4 +-
 src/client/views/nodes/IconBox.tsx                 | 27 ++++++++---
 src/new_fields/List.ts                             | 10 +++++
 9 files changed, 87 insertions(+), 65 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 627bedde2..8d85f7e2c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -252,18 +252,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (!this._removeIcon) {
                 if (selectedDocs.length === 1)
                     this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
-                else {
-                    let docViews = SelectionManager.ViewsSortedVertically();
-                    let topDocView = docViews[0];
-                    let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
-                    if (ind !== -1) {
-                        topDocView.templates.splice(ind, 1);
-                        topDocView.props.Document.subBulletDocs = undefined;
-                    } else {
-                        topDocView.addTemplate(Templates.Bullet);
-                        topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document));
+                else
+                    if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD &&
+                        Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
+                        let docViews = SelectionManager.ViewsSortedVertically();
+                        let topDocView = docViews[0];
+                        let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
+                        if (ind !== -1) {
+                            topDocView.templates.splice(ind, 1);
+                            topDocView.props.Document.subBulletDocs = undefined;
+                        } else {
+                            topDocView.addTemplate(Templates.Bullet);
+                            topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document));
+                        }
                     }
-                }
             }
             this._removeIcon = false;
         }
@@ -275,9 +277,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let doc = selected[0].props.Document;
         let iconDoc = Docs.IconDocument(layoutString);
         iconDoc.isButton = true;
-        iconDoc.title = selected.length > 1 ? "ICONset" : "ICON" + StrCast(doc.title);
-        iconDoc.labelField = this._fieldKey;
-        iconDoc[this._fieldKey] = selected.length > 1 ? "collection" : undefined;
+        iconDoc.proto!.title = selected.length > 1 ? "ICONset" : "ICON" + StrCast(doc.title);
+        iconDoc.labelField = selected.length > 1 ? undefined : this._fieldKey;
+        iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined;
         iconDoc.isMinimized = false;
         iconDoc.width = Number(MINIMIZED_ICON_SIZE);
         iconDoc.height = Number(MINIMIZED_ICON_SIZE);
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index dd1516da7..52f7914f3 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -18,7 +18,7 @@ export class SelectorContextMenu extends React.Component<{ Document: Doc }> {
     }
 
     async fetchDocuments() {
-        const docs = await SearchUtil.Search(`data_l:${this.props.Document[Id]}`, true);
+        const docs = await SearchUtil.Search(`data_l:"${this.props.Document[Id]}"`, true);
         runInAction(() => this._docs = docs);
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index be7cddca6..ae4852aa2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -212,6 +212,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         }
         if (e.key === "Backspace" || e.key === "Delete" || e.key === "d") {
             this._commandExecuted = true;
+            e.stopPropagation();
+            (e as any).propagationIsStopped = true;
             this.marqueeSelect().map(d => this.props.removeDocument(d));
             let ink = Cast(this.props.container.props.Document.ink, InkField);
             if (ink) {
@@ -291,20 +293,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 this.props.selectDocuments([newCollection]);
             }
             this.cleanupInteractions(false);
-        } else
-            if (e.key === "s") {
-                // this._commandExecuted = true;
-                // e.stopPropagation();
-                // e.preventDefault();
-                // let bounds = this.Bounds;
-                // let selected = this.marqueeSelect();
-                // SelectionManager.DeselectAll();
-                // let summary = Docs.TextDocument({ x: bounds.left + bounds.width + 25, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
-                // this.props.addLiveTextDocument(summary);
-                // selected.forEach(select => Doc.MakeLink(summary.proto!, select.proto!));
-
-                // this.cleanupInteractions(false);
-            }
+        }
     }
     @action
     marqueeInkSelect(ink: Map<any, any>) {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 925945b17..38ac5cb90 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,19 +1,19 @@
-import { computed, trace, action, reaction, IReactionDisposer } from "mobx";
+import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
+import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { OmitKeys, Utils } from "../../../Utils";
+import { DocumentManager } from "../../util/DocumentManager";
+import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
+import { UndoManager } from "../../util/UndoManager";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { DocComponent } from "../DocComponent";
 import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
 import "./DocumentView.scss";
 import React = require("react");
-import { DocComponent } from "../DocComponent";
-import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { FieldValue, Cast, NumCast, BoolCast, StrCast } from "../../../new_fields/Types";
-import { OmitKeys, Utils } from "../../../Utils";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Doc, DocListCastAsync, DocListCast, } from "../../../new_fields/Doc";
-import { List } from "../../../new_fields/List";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { UndoManager } from "../../util/UndoManager";
-import { DocumentManager } from "../../util/DocumentManager";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 }
@@ -140,7 +140,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             if (!CollectionFreeFormDocumentView._undoBatch) {
                 CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
             }
-            maximizedDocs.forEach(maximizedDoc => {
+            maximizedDocs.map(maximizedDoc => {
                 let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
                 if (!iconAnimating || (Date.now() - iconAnimating[6] > 1000)) {
                     if (isMinimized === undefined) {
@@ -182,12 +182,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 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 expandedDocs = [...(subBulletDocs ? subBulletDocs : []),
-                ...(maximizedDocs ? maximizedDocs : []),
-                ...(summarizedDocs ? summarizedDocs : []),];
-                if (expandedDocs) {   // bcz: need a better way to associate behaviors with click events on widget-documents
+                let linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs, []);
+                let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs, []);
+                let expandedDocs: Doc[] = [];
+                expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
+                expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
+                expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
+                // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []),
+                // ...(maximizedDocs ? maximizedDocs : []),
+                // ...(summarizedDocs ? summarizedDocs : []),];
+                if (expandedDocs.length) {   // bcz: need a better way to associate behaviors with click events on widget-documents
                     if ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight)) {
                         let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
                         if (dataDocs) {
@@ -204,11 +208,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                         this.toggleIcon(expandedDocs);
                     }
                 }
-                let linkedFwdDocs = [
-                    linkedToDocs ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
-                    linkedFromDocs ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
-                if (linkedFwdDocs) {
-                    DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0]);
+                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]];
+                    if (linkedFwdDocs) {
+                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0]);
+                    }
                 }
             }
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b88d18046..27896f02b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -112,8 +112,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
             () => {
                 let maxDoc = DocListCast(this.props.Document.maximizedDocs);
-                if (maxDoc && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
-                    this.props.Document.title = (maxDoc && maxDoc.length === 1 ? maxDoc[0].title + ".icon" : "");
+                if (maxDoc.length === 1 && StrCast(maxDoc[0].title).startsWith("-") && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
+                    this.props.Document.proto!.title = maxDoc[0].title + ".icon";
                 }
                 let sumDoc = Cast(this.props.Document.summaryDoc, Doc);
                 if (sumDoc instanceof Doc) {
@@ -332,8 +332,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     render() {
         var scaling = this.props.ContentScaling();
-        var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%";
-        var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%";
+        var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : (StrCast(this.props.Document.layout).indexOf("IconBox") === -1 ? "100%" : "auto");
+        var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
 
         return (
             <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index cb09ad42c..8e04071c7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -229,7 +229,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             if (e.target && (e.target as any).href) {
                 let href = (e.target as any).href;
                 if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
-                    let docid = href.replace(DocServer.prepend("/doc/"), "").split("%")[0];
+                    let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
                     DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
                         if (f instanceof Doc) {
                             if (DocumentManager.Instance.getDocumentView(f))
diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss
index f6d9860a3..893dc2d36 100644
--- a/src/client/views/nodes/IconBox.scss
+++ b/src/client/views/nodes/IconBox.scss
@@ -4,13 +4,13 @@
     position: inherit;
     left:0;
     top:0;
-    height: 100%;
+    height: auto;
     width: max-content;
     // overflow: hidden;
     pointer-events: all;
     svg {
         width:  $MINIMIZED_ICON_SIZE !important;
-        height: 100%;
+        height: auto;
         background: white;
     }
     .iconBox-label {
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 071930633..b42eb44a5 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./IconBox.scss";
 import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { IconField } from "../../../new_fields/IconField";
 import { ContextMenu } from "../ContextMenu";
 import Measure from "react-measure";
@@ -41,25 +41,40 @@ export class IconBox extends React.Component<FieldViewProps> {
     setLabelField = (e: React.MouseEvent): void => {
         this.props.Document.hideLabel = !BoolCast(this.props.Document.hideLabel);
     }
+    setUseOwnTitleField = (e: React.MouseEvent): void => {
+        this.props.Document.useOwnTitle = !BoolCast(this.props.Document.useTargetTitle);
+    }
 
     specificContextMenu = (e: React.MouseEvent): void => {
         ContextMenu.Instance.addItem({
-            description: BoolCast(this.props.Document.hideLabel) ? "show label" : "hide label",
+            description: BoolCast(this.props.Document.hideLabel) ? "Show label with icon" : "Remove label from icon",
             event: this.setLabelField
         });
+        let maxDocs = DocListCast(this.props.Document.maximizedDocs);
+        if (maxDocs.length === 1 && !BoolCast(this.props.Document.hideLabel)) {
+            ContextMenu.Instance.addItem({
+                description: BoolCast(this.props.Document.useOwnTitle) ? "Use target title for label" : "Use own title label",
+                event: this.setUseOwnTitleField
+            });
+        }
     }
     @observable _panelWidth: number = 0;
     @observable _panelHeight: number = 0;
     render() {
         let labelField = StrCast(this.props.Document.labelField);
         let hideLabel = BoolCast(this.props.Document.hideLabel);
-        let maxDoc = Cast(this.props.Document.maximizedDocs, listSpec(Doc), []);
-        let firstDoc = maxDoc && maxDoc.length > 0 && maxDoc[0] instanceof Doc ? maxDoc[0] as Doc : undefined;
-        let label = !hideLabel && firstDoc && labelField ? firstDoc[labelField] : "";
+        let maxDocs = DocListCast(this.props.Document.maximizedDocs);
+        let firstDoc = maxDocs.length ? maxDocs[0] : undefined;
+        let label = hideLabel ? "" : (firstDoc && labelField && !BoolCast(this.props.Document.useOwnTitle, false) ? firstDoc[labelField] : this.props.Document.title);
         return (
             <div className="iconBox-container" onContextMenu={this.specificContextMenu}>
                 {this.minimizedIcon}
-                <Measure offset onResize={(r) => runInAction(() => { if (r.offset!.width || BoolCast(this.props.Document.hideLabel)) this.props.Document.nativeWidth = this.props.Document.width = (r.offset!.width + Number(MINIMIZED_ICON_SIZE)); })}>
+                <Measure offset onResize={(r) => runInAction(() => {
+                    if (r.offset!.width || BoolCast(this.props.Document.hideLabel)) {
+                        this.props.Document.nativeWidth = (r.offset!.width + Number(MINIMIZED_ICON_SIZE));
+                        if (this.props.Document.height === Number(MINIMIZED_ICON_SIZE)) this.props.Document.width = this.props.Document.nativeWidth;
+                    }
+                })}>
                     {({ measureRef }) =>
                         <span ref={measureRef} className="iconBox-label">{label}</span>
                     }
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index 88a65eba4..70e36f911 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -230,6 +230,16 @@ class ListImpl<T extends Field> extends ObjectField {
         const list = new Proxy<this>(this, {
             set: setter,
             get: listGetter,
+            ownKeys: target => Object.keys(target.__fields),
+            getOwnPropertyDescriptor: (target, prop) => {
+                if (prop in target.__fields) {
+                    return {
+                        configurable: true,//TODO Should configurable be true?
+                        enumerable: true,
+                    };
+                }
+                return Reflect.getOwnPropertyDescriptor(target, prop);
+            },
             deleteProperty: deleteProperty,
             defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
         });
-- 
cgit v1.2.3-70-g09d2


From 09afa3cc843a69eca59b63018e6f50f8e2bee089 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 15 May 2019 16:09:47 -0400
Subject: small fixes.  stopped view from panning when switching to tab
 dragging.

---
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 3 +--
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx       | 9 ++++++---
 src/client/views/nodes/DocumentView.tsx                         | 9 +++++----
 3 files changed, 12 insertions(+), 9 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ae4852aa2..865bae729 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -257,7 +257,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 width: bounds.width * zoomBasis,
                 height: bounds.height * zoomBasis,
                 ink: inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined,
-                title: "a nested collection",
+                title: e.key === "s" ? "-summary-" : e.key === "r" ? "-replacement-" : e.key === "p" ? "-summary-" : "a nested collection",
             });
 
             this.marqueeInkDelete(inkData);
@@ -283,7 +283,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     let maxy = NumCast(summarizedDoc.y, undefined);
                     let maxw = NumCast(summarizedDoc.width, undefined);
                     let maxh = NumCast(summarizedDoc.height, undefined);
-                    summarizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0])
                 });
                 this.props.addLiveTextDocument(summary);
             }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 38ac5cb90..631bf1ba8 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -170,7 +170,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
         this._downY = e.clientY;
-        // e.stopPropagation();
+        if (e.button === 0 && e.altKey) {
+            e.stopPropagation(); // prevents panning from happening on collection if shift is pressed after a document drag has started
+        } // allow pointer down to go through otherwise so that marquees can be drawn starting over a document 
     }
     onClick = async (e: React.MouseEvent) => {
         e.stopPropagation();
@@ -192,14 +194,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 // ...(maximizedDocs ? maximizedDocs : []),
                 // ...(summarizedDocs ? summarizedDocs : []),];
                 if (expandedDocs.length) {   // bcz: need a better way to associate behaviors with click events on widget-documents
-                    if ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight)) {
+                    let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0].proto!);
+                    if (!hasView && ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight))) {
                         let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
                         if (dataDocs) {
                             SelectionManager.DeselectAll();
                             expandedDocs.forEach(maxDoc => {
                                 maxDoc.isMinimized = false;
                                 if (!CollectionDockingView.Instance.CloseRightSplit(maxDoc)) {
-                                    CollectionDockingView.Instance.AddRightSplit(Doc.MakeCopy(maxDoc));
+                                    CollectionDockingView.Instance.AddRightSplit(maxDoc.proto ? Doc.MakeDelegate(maxDoc.proto) : maxDoc);
                                 }
                             });
                         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 27896f02b..260630216 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -112,12 +112,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
             () => {
                 let maxDoc = DocListCast(this.props.Document.maximizedDocs);
-                if (maxDoc.length === 1 && StrCast(maxDoc[0].title).startsWith("-") && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
-                    this.props.Document.proto!.title = maxDoc[0].title + ".icon";
+                if (maxDoc.length === 1 && StrCast(this.props.Document.title).startsWith("-") && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
+                    this.props.Document.proto!.title = "-" + maxDoc[0].title + ".icon";
                 }
                 let sumDoc = Cast(this.props.Document.summaryDoc, Doc);
-                if (sumDoc instanceof Doc) {
-                    this.props.Document.title = sumDoc.title + ".expanded";
+                if (sumDoc instanceof Doc && StrCast(this.props.Document.title).startsWith("-")) {
+                    this.props.Document.proto!.title = "-" + sumDoc.title + ".expanded";
                 }
             }, { fireImmediately: true });
         DocumentManager.Instance.DocumentViews.push(this);
@@ -186,6 +186,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             e.stopPropagation();
         } else if (this.active) {
+            //e.stopPropagation(); // bcz: doing this will block click events from CollectionFreeFormDocumentView which are needed for iconifying,etc
             document.removeEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
-- 
cgit v1.2.3-70-g09d2


From 25e5aa78f858e35eb0f8e7b49d528bdac6691513 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 16 May 2019 12:28:31 -0400
Subject: fixed MakeAlias to check if doc is prototype.  fixed summary template
 to bottom/right + capture image when zoomed, fixed full screen to copy view
 data on delegate

---
 src/client/views/Templates.tsx                                  | 2 +-
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 2 +-
 src/client/views/nodes/DocumentView.tsx                         | 2 +-
 src/new_fields/Doc.ts                                           | 5 +++--
 4 files changed, 6 insertions(+), 5 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 8d6bfbefd..5d29e68ae 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -74,7 +74,7 @@ export namespace Templates {
     export function ImageOverlay(width: number, height: number, field: string = "thumbnail") {
         return (`<div>
                 <div style="height:100%; width:100%; position:absolute;">{layout}</div>
-                <div style="height:${height}px; top:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;">
+                <div style="height:${height}px; bottom:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;">
                     <ImageBox id="isExpander" {...props} PanelWidth={${width}} fieldKey={"${field}"} />
                 </div>
             </div>`);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 17d0a533e..12edb2c2a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -257,7 +257,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
 
             if (e.key === "s" || e.key === "p") {
 
-                htmlToImage.toPng(this._mainCont.current!, { width: bounds.width, height: bounds.height, quality: 1 }).then((dataUrl) => {
+                htmlToImage.toPng(this._mainCont.current!, { width: bounds.width * zoomBasis, height: bounds.height * zoomBasis, quality: 1 }).then((dataUrl) => {
                     selected.map(d => {
                         this.props.removeDocument(d);
                         d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 260630216..cf8bcbb42 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -227,7 +227,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
     fullScreenClicked = (e: React.MouseEvent): void => {
-        const doc = Doc.MakeDelegate(FieldValue(this.Document.proto));
+        const doc = Doc.MakeCopy(this.props.Document, false);
         if (doc) {
             CollectionDockingView.Instance.OpenFullScreen(doc);
         }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 2ae816da4..de72be3ea 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -140,7 +140,7 @@ export namespace Doc {
         return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
     }
     export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
-        const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") == -1 ? doc.proto : doc;
+        const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc;
 
         if (proto) {
             proto[key] = value;
@@ -178,9 +178,10 @@ export namespace Doc {
     }
 
     export function MakeAlias(doc: Doc) {
+        const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : undefined;
         const alias = new Doc;
 
-        if (!doc.proto) {
+        if (!proto) {
             alias.proto = doc;
         } else {
             PromiseValue(Cast(doc.proto, Doc)).then(proto => {
-- 
cgit v1.2.3-70-g09d2


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

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

(limited to 'src/client/views/nodes/DocumentView.tsx')

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


From 530dc40f7519b8288d91db61103468ab713dfbdd Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 16 May 2019 21:02:15 -0400
Subject: fixed some small layout issues related to isButton and  templates

---
 src/client/views/Templates.tsx                  | 4 ++--
 src/client/views/nodes/DocumentContentsView.tsx | 8 ++++++--
 src/client/views/nodes/DocumentView.tsx         | 3 +++
 src/client/views/nodes/ImageBox.scss            | 6 +++++-
 src/client/views/nodes/ImageBox.tsx             | 3 +--
 src/client/views/nodes/PDFBox.tsx               | 1 -
 6 files changed, 17 insertions(+), 8 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 5d29e68ae..5a99b3d90 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -74,8 +74,8 @@ export namespace Templates {
     export function ImageOverlay(width: number, height: number, field: string = "thumbnail") {
         return (`<div>
                 <div style="height:100%; width:100%; position:absolute;">{layout}</div>
-                <div style="height:${height}px; bottom:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;">
-                    <ImageBox id="isExpander" {...props} PanelWidth={${width}} fieldKey={"${field}"} />
+                <div style="height:auto; width:${width}px; bottom:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;">
+                    <ImageBox id="isExpander" {...props} style="width:100%; height=auto;" PanelWidth={${width}} fieldKey={"${field}"} />
                 </div>
             </div>`);
     }
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f3d76c49b..ace8d4aa5 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -21,7 +21,7 @@ import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import React = require("react");
 import { FieldViewProps } from "./FieldView";
 import { Without, OmitKeys } from "../../../Utils";
-import { Cast, StrCast } from "../../../new_fields/Types";
+import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -76,9 +76,13 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             (this.props.layoutKey === "layout" && StrCast(this.props.Document.layout).indexOf("CollectionView") === -1)) {
             this.templates.forEach(template => {
                 let self = this;
+                // this scales constants in the markup by the scaling applied to the document, but caps the constants to be smaller
+                // than the width/height of the containing document
                 function convertConstantsToNative(match: string, offset: number, x: string) {
                     let px = Number(match.replace("px", ""));
-                    return `${px * self.props.ScreenToLocalTransform().Scale}px`;
+                    return `${Math.min(NumCast(self.props.Document.height, 0),
+                        Math.min(NumCast(self.props.Document.width, 0),
+                            px * self.props.ScreenToLocalTransform().Scale))}px`;
                 }
                 let nativizedTemplate = template.replace(/([0-9]+)px/g, convertConstantsToNative);
                 layout = nativizedTemplate.replace("{layout}", base);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 428dd9b36..40ad55d25 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -224,6 +224,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (doc.isButton && !doc.nativeWidth) {
             doc.nativeWidth = this.props.Document[WidthSym]();
             doc.nativeHeight = this.props.Document[HeightSym]();
+        } else {
+
+            doc.nativeWidth = doc.nativeHeight = undefined;
         }
     }
     fullScreenClicked = (e: React.MouseEvent): void => {
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 2316a050e..f1b73a676 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -25,7 +25,11 @@
 }
 
 .imageBox-cont img {
-    height: 100%;
+    height: auto;
+    width:100%;
+}
+.imageBox-cont-interactive img {
+    height: auto;
     width:100%;
 }
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 3cc60a6c5..a06adfcf0 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -42,8 +42,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     onLoad = (target: any) => {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
-        console.log("title: " + this.Document.title);
-        if (this._photoIndex === 0) {
+        if (this._photoIndex === 0 && (this.props as any).id !== "isExpander") {
             Doc.SetOnPrototype(this.Document, "nativeHeight", FieldValue(this.Document.nativeWidth, 0) * h / w);
             this.Document.height = FieldValue(this.Document.width, 0) * h / w;
         }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f6328fa83..e71ac4924 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -284,7 +284,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     }
     @computed
     get pdfPage() {
-        console.log(this.curPage);
         return <Page height={this.renderHeight} renderTextLayer={false} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />;
     }
     @computed
-- 
cgit v1.2.3-70-g09d2


From 6e65b1e32eb972358d9c8dbd7f8d984d3baefc4b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 16 May 2019 22:56:19 -0400
Subject: fixed links so that you can adjust the pan location of a collection
 when its a link endoint

---
 src/client/util/DocumentManager.ts                        |  5 +++--
 src/client/util/DragManager.ts                            |  2 +-
 src/client/util/TooltipTextMenu.tsx                       |  1 -
 src/client/views/Main.tsx                                 |  2 +-
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx |  3 ++-
 src/client/views/nodes/DocumentView.tsx                   | 11 ++++++-----
 src/client/views/nodes/FormattedTextBox.tsx               |  4 +---
 src/client/views/nodes/LinkBox.tsx                        |  2 +-
 src/client/views/nodes/LinkMenu.tsx                       |  5 ++---
 src/new_fields/Doc.ts                                     | 10 ++++++----
 10 files changed, 23 insertions(+), 22 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 51f5fbe9f..c6962ad8d 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -110,7 +110,8 @@ export class DocumentManager {
     }
 
     @undoBatch
-    public jumpToDocument = async (doc: Doc): Promise<void> => {
+    public jumpToDocument = async (docDelegate: Doc, makeCopy: boolean = true): Promise<void> => {
+        let doc = docDelegate.proto ? docDelegate.proto : docDelegate;
         const page = NumCast(doc.page, undefined);
         const contextDoc = await Cast(doc.annotationOn, Doc);
         if (contextDoc) {
@@ -122,7 +123,7 @@ export class DocumentManager {
             docView.props.focus(docView.props.Document);
         } else {
             if (!contextDoc) {
-                CollectionDockingView.Instance.AddRightSplit(Doc.MakeDelegate(doc));
+                CollectionDockingView.Instance.AddRightSplit(docDelegate ? (makeCopy ? Doc.MakeCopy(docDelegate) : docDelegate) : Doc.MakeDelegate(doc));
             } else {
                 let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
                 if (contextView) {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 05eb5c38a..7f75a95f0 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -336,7 +336,7 @@ export namespace DragManager {
                         x: e.x,
                         y: e.y,
                         data: dragData,
-                        mods: e.ctrlKey ? "Control" : ""
+                        mods: e.altKey ? "AltKey" : ""
                     }
                 })
             );
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 223921428..4d40d09b2 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -186,7 +186,6 @@ export class TooltipTextMenu {
                 let node = this.view.state.selection.$from.nodeAfter;
                 let link = node && node.marks.find(m => m.type.name === "link");
                 if (link) {
-                    console.log("Link to : " + link.attrs.href);
                     let href: string = link.attrs.href;
                     if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
                         let docid = href.replace(DocServer.prepend("/doc/"), "");
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 866da010a..51ad0aa49 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -244,7 +244,7 @@ export class Main extends React.Component {
         let logoutRef = React.createRef<HTMLDivElement>();
 
         return [
-            <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.ctrlKey && DocServer.DeleteDatabase()}>Clear Database</button>,
+            <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.altKey && DocServer.DeleteDatabase()}>Clear Database</button>,
             <div id="toolbar" key="toolbar">
                 <button className="toolbar-button round-button" title="Undo" onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button>
                 <button className="toolbar-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index d4f660b3f..001ce3095 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -205,11 +205,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     }
                 }
                 else if (linkedToDocs.length || linkedFromDocs.length) {
+                    SelectionManager.DeselectAll();
                     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]];
                     if (linkedFwdDocs) {
-                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0]);
+                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], altKey);
                     }
                 }
             }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 40ad55d25..5264773b0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -27,6 +27,7 @@ import { DocServer } from "../../DocServer";
 import { Id } from "../../../new_fields/RefField";
 import { PresentationView } from "../PresentationView";
 import { SearchUtil } from "../../util/SearchUtil";
+import { ObjectField, Copy } from "../../../new_fields/ObjectField";
 
 const linkSchema = createSchema({
     title: "string",
@@ -245,17 +246,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let sourceDoc = de.data.linkSourceDocument;
             let destDoc = this.props.Document;
 
-            const protoDest = destDoc.proto;
-            const protoSrc = sourceDoc.proto;
-            if (de.mods === "Control") {
+            if (de.mods === "AltKey") {
+                const protoDest = destDoc.proto;
+                const protoSrc = sourceDoc.proto;
                 let src = protoSrc ? protoSrc : sourceDoc;
                 let dst = protoDest ? protoDest : destDoc;
-                dst.data = src;
+                dst.data = (src.data! as ObjectField)[Copy]();
                 dst.nativeWidth = src.nativeWidth;
                 dst.nativeHeight = src.nativeHeight;
             }
             else {
-                Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
+                Doc.MakeLink(sourceDoc, destDoc);
                 de.data.droppedDocuments.push(destDoc);
             }
             e.stopPropagation();
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f30022508..bf98fb40b 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -111,9 +111,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             let sourceDoc = de.data.linkSourceDocument;
             let destDoc = this.props.Document;
 
-            const protoDest = destDoc.proto;
-            const protoSrc = sourceDoc.proto;
-            Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
+            Doc.MakeLink(sourceDoc, destDoc);
             de.data.droppedDocuments.push(destDoc);
             e.stopPropagation();
         }
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 611cb66b6..68b692aad 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -31,7 +31,7 @@ export class LinkBox extends React.Component<Props> {
     @undoBatch
     onViewButtonPressed = async (e: React.PointerEvent): Promise<void> => {
         e.stopPropagation();
-        DocumentManager.Instance.jumpToDocument(this.props.pairedDoc);
+        DocumentManager.Instance.jumpToDocument(this.props.pairedDoc, e.altKey);
     }
 
     onEditButtonPressed = (e: React.PointerEvent): void => {
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 11117122d..4cf798249 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -6,8 +6,7 @@ import { LinkEditor } from "./LinkEditor";
 import './LinkMenu.scss';
 import React = require("react");
 import { Doc, DocListCast } from "../../../new_fields/Doc";
-import { Cast, FieldValue } from "../../../new_fields/Types";
-import { listSpec } from "../../../new_fields/Schema";
+import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { Id } from "../../../new_fields/RefField";
 
 interface Props {
@@ -24,7 +23,7 @@ export class LinkMenu extends React.Component<Props> {
         return links.map(link => {
             let doc = FieldValue(Cast(link[key], Doc));
             if (doc) {
-                return <LinkBox key={doc[Id]} linkDoc={link} linkName={Cast(link.title, "string", "")} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
+                return <LinkBox key={doc[Id]} linkDoc={link} linkName={StrCast(link.title)} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
             }
         });
     }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index c898f697b..ad3b880cd 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -216,6 +216,8 @@ export namespace Doc {
     }
 
     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;
@@ -226,15 +228,15 @@ export namespace Doc {
             linkDoc.proto!.linkedTo = target;
             linkDoc.proto!.linkedFrom = source;
 
-            let linkedFrom = Cast(target.linkedFromDocs, listSpec(Doc));
+            let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc));
             if (!linkedFrom) {
-                target.linkedFromDocs = linkedFrom = new List<Doc>();
+                protoTarg.linkedFromDocs = linkedFrom = new List<Doc>();
             }
             linkedFrom.push(linkDoc);
 
-            let linkedTo = Cast(source.linkedToDocs, listSpec(Doc));
+            let linkedTo = Cast(protoSrc.linkedToDocs, listSpec(Doc));
             if (!linkedTo) {
-                source.linkedToDocs = linkedTo = new List<Doc>();
+                protoSrc.linkedToDocs = linkedTo = new List<Doc>();
             }
             linkedTo.push(linkDoc);
             return linkDoc;
-- 
cgit v1.2.3-70-g09d2


From 4e244951b7b18d7973360f423e8de80c42466228 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 17 May 2019 19:50:32 -0400
Subject: added right-click "open Tab" and double-click open tab

---
 src/client/views/Main.tsx                            |  1 +
 .../views/collections/CollectionDockingView.tsx      | 20 ++++++++++++++++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx    |  1 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx   | 19 +++++++++++++++++++
 src/client/views/nodes/DocumentView.tsx              |  2 ++
 src/client/views/nodes/FieldView.tsx                 |  2 ++
 src/client/views/nodes/ImageBox.tsx                  |  4 +++-
 7 files changed, 46 insertions(+), 3 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 51ad0aa49..177047a9a 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -166,6 +166,7 @@ export class Main extends React.Component {
             <DocumentView Document={mainCont}
                 toggleMinimized={emptyFunction}
                 addDocument={undefined}
+                addDocTab={emptyFunction}
                 removeDocument={undefined}
                 ScreenToLocalTransform={Transform.Identity}
                 ContentScaling={returnOne}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index deec64225..28624e020 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -107,7 +107,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     @action
     layoutChanged(removed?: Doc) {
         this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
-        this._goldenLayout.emit('sbcreteChanged');
+        this._goldenLayout.emit('stateChanged');
         this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         if (removed) CollectionDockingView.Instance._removedDocs.push(removed);
         this.stateChanged();
@@ -153,6 +153,17 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
         return newContentItem;
     }
+    @action
+    public AddTab(stack: any, document: Doc) {
+        let docs = Cast(this.props.Document.data, listSpec(Doc));
+        if (docs) {
+            docs.push(document);
+        }
+        let docContentConfig = CollectionDockingView.makeDocumentConfig(document);
+        var newContentItem = stack.layoutManager.createContentItem(docContentConfig, this._goldenLayout);
+        stack.addChild(newContentItem.contentItems[0], undefined);
+        this.layoutChanged();
+    }
 
     setupGoldenLayout() {
         var config = StrCast(this.props.Document.dockingConfig);
@@ -395,9 +406,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
-
+    _stack: any;
     constructor(props: any) {
         super(props);
+        this._stack = (this.props as any).glContainer.parent.parent;
         DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
     }
 
@@ -420,6 +432,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
     get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
 
+    addDocTab = (doc: Doc) => {
+        CollectionDockingView.Instance.AddTab(this._stack, doc);
+    }
     get content() {
         if (!this._document) {
             return (null);
@@ -441,6 +456,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                     parentActive={returnTrue}
                     whenActiveChanged={emptyFunction}
                     focus={emptyFunction}
+                    addDocTab={this.addDocTab}
                     ContainingCollectionView={undefined} />
             </div >);
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6e4c79e62..ad32f70dc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -300,6 +300,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             parentActive: this.props.active,
             whenActiveChanged: this.props.whenActiveChanged,
             bringToFront: this.bringToFront,
+            addDocTab: this.props.addDocTab,
         };
     }
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 00aa71656..a0efc3154 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -159,6 +159,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             }, 500);
         }
     }
+    private _lastTap: number = 0;
     static _undoBatch?: UndoManager.Batch = undefined;
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
@@ -166,6 +167,24 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         if (e.button === 0 && e.altKey) {
             e.stopPropagation(); // prevents panning from happening on collection if shift is pressed after a document drag has started
         } // allow pointer down to go through otherwise so that marquees can be drawn starting over a document 
+        if (Date.now() - this._lastTap < 300) {
+            if (e.buttons === 1) {
+                this._downX = e.clientX;
+                this._downY = e.clientY;
+                document.removeEventListener("pointerup", this.onPointerUp);
+                document.addEventListener("pointerup", this.onPointerUp);
+            }
+        } else {
+            this._lastTap = Date.now();
+        }
+    }
+    onPointerUp = (e: PointerEvent): void => {
+
+        document.removeEventListener("pointerup", this.onPointerUp);
+        if (Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) {
+            this.props.addDocTab(this.props.Document);
+        }
+        e.stopPropagation();
     }
     onClick = async (e: React.MouseEvent) => {
         e.stopPropagation();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5264773b0..9a5d21b49 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -57,6 +57,7 @@ export interface DocumentViewProps {
     whenActiveChanged: (isActive: boolean) => void;
     toggleMinimized: () => void;
     bringToFront: (doc: Doc) => void;
+    addDocTab: (doc: Doc) => void;
 }
 
 const schema = createSchema({
@@ -307,6 +308,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton });
         cm.addItem({ description: "Fields", event: this.fieldsClicked });
         cm.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) });
+        cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document) });
         cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) });
         cm.addItem({
             description: "Find aliases", event: async () => {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 34b6c5e70..b5dfd18eb 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -35,6 +35,7 @@ export interface FieldViewProps {
     isTopMost: boolean;
     selectOnLoad: boolean;
     addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
+    addDocTab: (document: Doc) => boolean;
     removeDocument?: (document: Doc) => boolean;
     moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
@@ -87,6 +88,7 @@ export class FieldView extends React.Component<FieldViewProps> {
             return (
                 <DocumentContentsView Document={field}
                     addDocument={undefined}
+                    addDocTab={this.props.addDocTab}
                     removeDocument={undefined}
                     ScreenToLocalTransform={Transform.Identity}
                     ContentScaling={returnOne}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index a06adfcf0..49c2f1b44 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -166,7 +166,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
         let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx
         return (
-            <div id={id} className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+            <div id={id} className={`imageBox-cont${interactive}`}
+                // onPointerDown={this.onPointerDown}
+                onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
                 <img id={id} src={paths[Math.min(paths.length, this._photoIndex)]}
                     style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }}
                     width={nativeWidth}
-- 
cgit v1.2.3-70-g09d2


From c627be793938188ebcb16d2ca3db93728b609116 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 17 May 2019 22:35:43 -0400
Subject: fixed shift-drag of main collection.

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +-
 src/client/views/nodes/DocumentView.tsx                             | 6 +-----
 2 files changed, 2 insertions(+), 6 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ad32f70dc..b44a2f527 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -116,7 +116,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) ||
                 (e.button === 0 && e.altKey)) && this.props.active())) ||
             (!CollectionFreeFormView.RIGHT_BTN_DRAG &&
-                ((e.button === 0 && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) && this.props.active()))) {
+                ((e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) && this.props.active()))) {
             document.removeEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9a5d21b49..b7d8eda9c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -181,11 +181,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
         if (e.shiftKey && e.buttons === 1) {
-            if (this.props.isTopMost) {
-                this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined, this._hitExpander);
-            } else if (this.props.Document) {
-                CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
-            }
+            CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
             e.stopPropagation();
         } else if (this.active) {
             //e.stopPropagation(); // bcz: doing this will block click events from CollectionFreeFormDocumentView which are needed for iconifying,etc
-- 
cgit v1.2.3-70-g09d2


From 718269e2fa50e20227bfbb32b1a1b6ec8f78184d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 18 May 2019 09:42:43 -0400
Subject: fixed font error message.  Improved opening summaries as either inTab
 or inPlace and switching with the alt-key

---
 src/client/views/ContextMenu.tsx                   |  3 ++-
 src/client/views/DocumentDecorations.tsx           |  2 +-
 .../views/collections/CollectionDockingView.tsx    |  7 +++--
 .../collections/collectionFreeForm/MarqueeView.tsx |  4 +--
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 30 ++++++++++++----------
 src/client/views/nodes/DocumentView.tsx            |  4 +--
 src/client/views/nodes/FieldView.tsx               |  2 +-
 7 files changed, 30 insertions(+), 22 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 9ccbb7149..9143c012e 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -5,9 +5,10 @@ import { observer } from "mobx-react"
 import "./ContextMenu.scss"
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faSearch } from '@fortawesome/free-solid-svg-icons';
+import { faSearch, faCircle } from '@fortawesome/free-solid-svg-icons';
 
 library.add(faSearch);
+library.add(faCircle);
 
 @observer
 export class ContextMenu extends React.Component {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 7083b1003..d440ed898 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -279,7 +279,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         iconDoc.proto!.title = selected.length > 1 ? "ICONset" : "ICON" + StrCast(doc.title);
         iconDoc.labelField = selected.length > 1 ? undefined : this._fieldKey;
         iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined;
-        iconDoc.isMinimized = false;
+        iconDoc.proto!.isMinimized = false;
         iconDoc.width = Number(MINIMIZED_ICON_SIZE);
         iconDoc.height = Number(MINIMIZED_ICON_SIZE);
         iconDoc.x = NumCast(doc.x);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 28624e020..74f8ad242 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -432,8 +432,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
     get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
 
-    addDocTab = (doc: Doc) => {
-        CollectionDockingView.Instance.AddTab(this._stack, doc);
+    addDocTab = (doc: Doc, location: string) => {
+        if (location === "onRight")
+            CollectionDockingView.Instance.AddRightSplit(doc);
+        else
+            CollectionDockingView.Instance.AddTab(this._stack, doc);
     }
     get content() {
         if (!this._document) {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c3c4115b8..6301fd27e 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -272,9 +272,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     newCollection.proto!.summaryDoc = summary;
                     selected = [newCollection];
                     newCollection.x = bounds.left + bounds.width;
-                    this.props.addDocument(newCollection, false);
+                    //this.props.addDocument(newCollection, false);
                     summary.proto!.summarizedDocs = new List<Doc>(selected.map(s => s.proto!));
-                    //summary.proto!.maximizeOnRight = true;
+                    summary.proto!.maximizeLocation = "inTab";  // or "inPlace", or "onRight"
                     //summary.proto!.isButton = true;
                     //let scrpt = this.props.getTransform().inverse().transformPoint(bounds.left, bounds.top);
                     // selected.map(summarizedDoc => {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index a0efc3154..833947ac3 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { OmitKeys, Utils } from "../../../Utils";
 import { DocumentManager } from "../../util/DocumentManager";
 import { SelectionManager } from "../../util/SelectionManager";
@@ -139,7 +139,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             }
             maximizedDocs.map(maximizedDoc => {
                 let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
-                if (!iconAnimating || (Date.now() - iconAnimating[6] > 1000)) {
+                if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
                     if (isMinimized === undefined) {
                         isMinimized = BoolCast(maximizedDoc.isMinimized, false);
                     }
@@ -179,10 +179,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         }
     }
     onPointerUp = (e: PointerEvent): void => {
-
         document.removeEventListener("pointerup", this.onPointerUp);
         if (Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) {
-            this.props.addDocTab(this.props.Document);
+            this.props.addDocTab(this.props.Document, "inTab");
         }
         e.stopPropagation();
     }
@@ -193,6 +192,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
             let isExpander = (e.target as any).id === "isExpander";
             if (BoolCast(this.props.Document.isButton, false) || isExpander) {
+                SelectionManager.DeselectAll();
                 let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
                 let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
                 let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
@@ -202,29 +202,33 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
                 expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
                 expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
-                // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []),
-                // ...(maximizedDocs ? maximizedDocs : []),
-                // ...(summarizedDocs ? summarizedDocs : []),];
+                // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),];
                 if (expandedDocs.length) {   // bcz: need a better way to associate behaviors with click events on widget-documents
                     let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView);
-                    if (!hasView && ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight))) {
+                    let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace");
+                    if (!hasView && altKey) {
+                        expandedDocs.forEach(maxDoc => maxDoc.isMinimized = false);
+                        hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView);
+                        maxLocation = this.props.Document.maximizeLocation = (maxLocation === "inPlace" ? "inTab" : "inPlace");
+                        if (!hasView && maxLocation === "inPlace") {
+                            this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(Doc.MakeDelegate(maxDoc), false));
+                            expandedDocs.forEach(maxDoc => maxDoc.isMinimized = true);
+                        }
+                    }
+                    if (!hasView && maxLocation !== "inPlace") {
                         let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
                         if (dataDocs) {
-                            SelectionManager.DeselectAll();
                             expandedDocs.forEach(maxDoc => {
-                                maxDoc.isMinimized = false;
                                 if (!CollectionDockingView.Instance.CloseRightSplit(maxDoc)) {
-                                    CollectionDockingView.Instance.AddRightSplit(Doc.MakeDelegate(maxDoc));
+                                    this.props.addDocTab(Doc.MakeDelegate(maxDoc), maxLocation);
                                 }
                             });
                         }
                     } else {
-                        //if (altKey) this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(maxDoc, false));
                         this.toggleIcon(expandedDocs);
                     }
                 }
                 else if (linkedToDocs.length || linkedFromDocs.length) {
-                    SelectionManager.DeselectAll();
                     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]];
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7167da93b..760e31b49 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -68,7 +68,7 @@ export interface DocumentViewProps {
     whenActiveChanged: (isActive: boolean) => void;
     toggleMinimized: () => void;
     bringToFront: (doc: Doc) => void;
-    addDocTab: (doc: Doc) => void;
+    addDocTab: (doc: Doc, where: string) => void;
 }
 
 const schema = createSchema({
@@ -315,7 +315,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton, icon: "expand-arrows-alt" });
         cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" });
         cm.addItem({ description: "Center", event: () => this.props.focus(this.props.Document), icon: "align-center" });
-        cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document), icon: "expand-arrows-alt" });
+        cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "expand-arrows-alt" });
         cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" });
         cm.addItem({
             description: "Find aliases", event: async () => {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index b5dfd18eb..5c149af99 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -35,7 +35,7 @@ export interface FieldViewProps {
     isTopMost: boolean;
     selectOnLoad: boolean;
     addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
-    addDocTab: (document: Doc) => boolean;
+    addDocTab: (document: Doc, where: string) => boolean;
     removeDocument?: (document: Doc) => boolean;
     moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
-- 
cgit v1.2.3-70-g09d2


From 3f12e4a4b776010e09d12a1adfb1d243675bcd6e Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sun, 19 May 2019 01:15:22 -0400
Subject: Reorganized some stuff to hopefully make circular imports harder

---
 src/client/documents/Documents.ts           | 33 ++++++++++++++++++++++
 src/client/views/nodes/DocumentView.tsx     |  4 +--
 src/client/views/nodes/FormattedTextBox.tsx |  3 +-
 src/debug/Viewer.tsx                        | 44 -----------------------------
 src/new_fields/Doc.ts                       | 31 --------------------
 5 files changed, 37 insertions(+), 78 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ed260d42e..0ebf6ff75 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -34,6 +34,7 @@ import { StrokeData, InkField } from "../../new_fields/InkField";
 import { dropActionType } from "../util/DragManager";
 import { DateField } from "../../new_fields/DateField";
 import { schema } from "prosemirror-schema-basic";
+import { UndoManager } from "../util/UndoManager";
 
 export interface DocumentOptions {
     x?: number;
@@ -64,6 +65,38 @@ export interface DocumentOptions {
 }
 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";
+
+            linkDoc.proto!.linkedTo = target;
+            linkDoc.proto!.linkedFrom = source;
+
+            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");
+    }
+
+
+}
+
 export namespace Docs {
     let textProto: Doc;
     let histoProto: Doc;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 760e31b49..ccf09f999 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -11,7 +11,7 @@ import { BoolCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { emptyFunction, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
-import { Docs } from "../../documents/Documents";
+import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { SearchUtil } from "../../util/SearchUtil";
@@ -264,7 +264,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 dst.nativeHeight = src.nativeHeight;
             }
             else {
-                Doc.MakeLink(sourceDoc, destDoc);
+                DocUtils.MakeLink(sourceDoc, destDoc);
                 de.data.droppedDocuments.push(destDoc);
             }
             e.stopPropagation();
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 98abde89e..d15813f9a 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -28,6 +28,7 @@ import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from "./FieldView";
 import "./FormattedTextBox.scss";
 import React = require("react");
+import { DocUtils } from '../../documents/Documents';
 
 library.add(faEdit);
 library.add(faSmile);
@@ -116,7 +117,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             let sourceDoc = de.data.linkSourceDocument;
             let destDoc = this.props.Document;
 
-            Doc.MakeLink(sourceDoc, destDoc);
+            DocUtils.MakeLink(sourceDoc, destDoc);
             de.data.droppedDocuments.push(destDoc);
             e.stopPropagation();
         }
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index d9b07aac6..720e1640a 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -3,56 +3,12 @@ import "normalize.css";
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { observer } from 'mobx-react';
-import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
-import { RouteStore } from '../server/RouteStore';
-import { emptyFunction } from '../Utils';
-import { Docs } from '../client/documents/Documents';
-import { SetupDrag } from '../client/util/DragManager';
-import { Transform } from '../client/util/Transform';
-import { UndoManager } from '../client/util/UndoManager';
-import { PresentationView } from '../client/views/PresentationView';
-import { CollectionDockingView } from '../client/views/collections/CollectionDockingView';
-import { ContextMenu } from '../client/views/ContextMenu';
-import { DocumentDecorations } from '../client/views/DocumentDecorations';
-import { InkingControl } from '../client/views/InkingControl';
-import { MainOverlayTextBox } from '../client/views/MainOverlayTextBox';
-import { DocumentView } from '../client/views/nodes/DocumentView';
-import { PreviewCursor } from '../client/views/PreviewCursor';
-import { SearchBox } from '../client/views/SearchBox';
-import { SelectionManager } from '../client/util/SelectionManager';
 import { Doc, Field, FieldResult } from '../new_fields/Doc';
-import { Cast } from '../new_fields/Types';
 import { DocServer } from '../client/DocServer';
-import { listSpec } from '../new_fields/Schema';
 import { Id } from '../new_fields/RefField';
-import { HistoryUtil } from '../client/util/History';
 import { List } from '../new_fields/List';
 import { URLField } from '../new_fields/URLField';
 
-CurrentUserUtils;
-RouteStore;
-emptyFunction;
-Docs;
-SetupDrag;
-Transform;
-UndoManager;
-PresentationView;
-CollectionDockingView;
-ContextMenu;
-DocumentDecorations;
-InkingControl;
-MainOverlayTextBox;
-DocumentView;
-PreviewCursor;
-SearchBox;
-SelectionManager;
-Doc;
-Cast;
-DocServer;
-listSpec;
-Id;
-HistoryUtil;
-
 configure({
     enforceActions: "observed"
 });
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 020f764a2..c6fa31a99 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -4,12 +4,9 @@ import { autoObject, SerializationHelper, Deserializable } from "../client/util/
 import { DocServer } from "../client/DocServer";
 import { setter, getter, getField, updateFunction, deleteProperty } from "./util";
 import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast } from "./Types";
-import { UndoManager, undoBatch } from "../client/util/UndoManager";
 import { listSpec } from "./Schema";
-import { List } from "./List";
 import { ObjectField, Parent, OnUpdate } from "./ObjectField";
 import { RefField, FieldId, Id, HandleUpdate } from "./RefField";
-import { Docs } from "../client/documents/Documents";
 
 export function IsField(field: any): field is Field {
     return (typeof field === "string")
@@ -221,34 +218,6 @@ export namespace Doc {
         return copy;
     }
 
-    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";
-
-            linkDoc.proto!.linkedTo = target;
-            linkDoc.proto!.linkedFrom = source;
-
-            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");
-    }
-
     export function MakeDelegate(doc: Doc): Doc;
     export function MakeDelegate(doc: Opt<Doc>): Opt<Doc>;
     export function MakeDelegate(doc: Opt<Doc>): Opt<Doc> {
-- 
cgit v1.2.3-70-g09d2


From 12796ace87581ffeac9d67fa5d7ebff8cecd1a52 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 19 May 2019 11:33:42 -0400
Subject: added opaque background for non-annotation collections.  fixed
 marquee view when there's zoomBasis, fixed double-click opening to
 stopPropagation. got rid of RIGHT_BTN_DRAG stuff.

---
 .../views/collections/CollectionBaseView.tsx       |  4 ++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 11 +++--------
 .../collections/collectionFreeForm/MarqueeView.tsx | 23 ++++++++--------------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  3 ++-
 src/client/views/nodes/DocumentView.tsx            |  5 +----
 5 files changed, 16 insertions(+), 30 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 76270f2e8..54e76fd5e 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -89,7 +89,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         }
         return false;
     }
-    @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
+    @computed get isAnnotationOverlay() { return this.props.fieldKey === "annotations"; }
 
     @action.bound
     addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
@@ -114,7 +114,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             }
             // set the ZoomBasis only if hasn't already been set -- bcz: maybe set/resetting the ZoomBasis should be a parameter to addDocument?
             if (!alreadyAdded && (this.collectionViewType === CollectionViewType.Freeform || this.collectionViewType === CollectionViewType.Invalid)) {
-                let zoom = NumCast(this.props.Document.scale, 1) / this.props.ScreenToLocalTransform().Scale;// ;
+                let zoom = NumCast(this.props.Document.scale, 1);
                 Doc.SetOnPrototype(doc, "zoomBasis", zoom);
             }
         }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b4b811da4..9cb8443f4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -37,7 +37,6 @@ const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema)
 
 @observer
 export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
-    public static RIGHT_BTN_DRAG = false;
     private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
     private _lastX: number = 0;
     private _lastY: number = 0;
@@ -46,8 +45,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
     @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
+    public get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
-    private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
     private panX = () => this.Document.panX || 0;
     private panY = () => this.Document.panY || 0;
     private zoomScaling = () => this.Document.scale || 1;
@@ -66,7 +65,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return true;
     }
     private selectDocuments = (docs: Doc[]) => {
-        SelectionManager.DeselectAll;
+        SelectionManager.DeselectAll();
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>
             SelectionManager.SelectDoc(dv!, true));
     }
@@ -112,11 +111,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        if ((CollectionFreeFormView.RIGHT_BTN_DRAG &&
-            (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) ||
-                (e.button === 0 && e.altKey)) && this.props.active())) ||
-            (!CollectionFreeFormView.RIGHT_BTN_DRAG &&
-                ((e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) && this.props.active()))) {
+        if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
             document.removeEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d102c8af2..4587c2227 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -132,17 +132,18 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         this._downY = this._lastY = e.pageY;
         this._commandExecuted = false;
         PreviewCursor.Visible = false;
-        if ((CollectionFreeFormView.RIGHT_BTN_DRAG && e.button === 0 && !e.altKey && !e.metaKey && this.props.container.props.active()) ||
-            (!CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && this.props.container.props.active())) {
+        if (e.button === 2 || (e.button === 0 && e.altKey)) {
+            if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]);
             document.addEventListener("pointermove", this.onPointerMove, true);
             document.addEventListener("pointerup", this.onPointerUp, true);
             document.addEventListener("keydown", this.marqueeCommand, true);
-            // bcz: do we need this?   it kills the context menu on the main collection
+            if (e.altKey) {
+                e.stopPropagation();
+                e.preventDefault();
+            }
+            // bcz: do we need this?   it kills the context menu on the main collection if !altKey
             // e.stopPropagation();
         }
-        if (e.altKey) {
-            e.preventDefault();
-        }
     }
 
     @action
@@ -248,6 +249,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 panX: 0,
                 panY: 0,
                 borderRounding: e.key === "e" ? -1 : undefined,
+                backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
                 scale: zoomBasis,
                 width: bounds.width * zoomBasis,
                 height: bounds.height * zoomBasis,
@@ -275,15 +277,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     //this.props.addDocument(newCollection, false);
                     summary.proto!.summarizedDocs = new List<Doc>(selected);
                     summary.proto!.maximizeLocation = "inTab";  // or "inPlace", or "onRight"
-                    //summary.proto!.isButton = true;
-                    //let scrpt = this.props.getTransform().inverse().transformPoint(bounds.left, bounds.top);
-                    // selected.map(summarizedDoc => {
-                    //     let maxx = NumCast(summarizedDoc.x, undefined);
-                    //     let maxy = NumCast(summarizedDoc.y, undefined);
-                    //     let maxw = NumCast(summarizedDoc.width, undefined);
-                    //     let maxh = NumCast(summarizedDoc.height, undefined);
-                    //     summarizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0]);
-                    // });
                     this.props.addLiveTextDocument(summary);
                 });
             }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7cb4c1dfe..8851e4b93 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -180,8 +180,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
     onPointerUp = (e: PointerEvent): void => {
         document.removeEventListener("pointerup", this.onPointerUp);
-        if (Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) {
+        if (!(e as any).propagationIsStopped && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) {
             this.props.addDocTab(this.props.Document, "inTab");
+            (e as any).propagationIsStopped = true;
         }
         e.stopPropagation();
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 760e31b49..bb5daaf4e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -187,9 +187,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
         this._downY = e.clientY;
-        if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) {
-            return;
-        }
         this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
         if (e.shiftKey && e.buttons === 1) {
             CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
@@ -207,7 +204,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
                 document.removeEventListener("pointermove", this.onPointerMove);
                 document.removeEventListener("pointerup", this.onPointerUp);
-                if (!e.altKey && !this.topMost && (!CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 1) || (CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 2)) {
+                if (!e.altKey && !this.topMost && e.buttons === 1) {
                     this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander);
                 }
             }
-- 
cgit v1.2.3-70-g09d2


From f53ba030a86bf3fc384ff3c00efcf3c1f0497b9b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 19 May 2019 18:10:53 -0400
Subject: cleaned up context menu icons.

---
 src/client/views/ContextMenu.scss                  |  3 ++-
 src/client/views/ContextMenu.tsx                   |  1 -
 src/client/views/ContextMenuItem.tsx               |  3 +--
 .../views/collections/CollectionDockingView.tsx    |  1 -
 src/client/views/collections/CollectionView.tsx    |  8 ++++---
 src/client/views/nodes/DocumentView.tsx            | 28 +++++++++++++---------
 6 files changed, 25 insertions(+), 19 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 0a14c8ce7..b12ee3faa 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -48,7 +48,7 @@
     border-style: none;
     border-color: $intermediate-color; //  rgb(187, 186, 186);
     border-bottom-style: solid;
-    padding: 10px;
+    padding: 10px 0px 10px 0px;
     white-space: nowrap;
     font-size: 1vw;
 }
@@ -67,4 +67,5 @@
 
 .icon-background {
     background-color: #DDDDDD;
+    margin-right: 2px;
 }
\ No newline at end of file
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 9143c012e..84c17a642 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -90,7 +90,6 @@ export class ContextMenu extends React.Component {
             <div className="contextMenu-cont" style={style} ref={this.ref}>
                 <span>
                     <span className="icon-background">
-                        <FontAwesomeIcon icon="circle" size="lg" />
                         <FontAwesomeIcon icon="search" size="lg" />
                     </span>
                     <input className="contextMenu-item" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange} />
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index ed583942a..b9239dac9 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -37,8 +37,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
             return (
                 <div className="contextMenu-item" onClick={this.props.event}>
                     <span className="icon-background">
-                        <FontAwesomeIcon icon="circle" size="sm" />
-                        {this.props.icon ? <FontAwesomeIcon icon={this.props.icon} size="sm" /> : null}
+                        {this.props.icon ? <FontAwesomeIcon icon={this.props.icon} size="sm" /> : <FontAwesomeIcon icon="circle" size="sm" />}
                     </span>
                     <div className="contextMenu-description"> {this.props.description}</div>
                 </div>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 06b262d79..483209f86 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -444,7 +444,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             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);
-        console.log("Scaling = " + scaling);
         return scaling;
     }
     get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index b9ffc11a2..59c763be8 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faProjectDiagram, faSquare, faTh, faTree } from '@fortawesome/free-solid-svg-icons';
+import { faProjectDiagram, faSquare, faTh, faTree, faSignature, faThList } from '@fortawesome/free-solid-svg-icons';
 import { observer } from "mobx-react";
 import * as React from 'react';
 import { Id } from '../../../new_fields/RefField';
@@ -18,6 +18,8 @@ library.add(faTh);
 library.add(faTree);
 library.add(faSquare);
 library.add(faProjectDiagram);
+library.add(faSignature);
+library.add(faThList);
 
 @observer
 export class CollectionView extends React.Component<FieldViewProps> {
@@ -40,11 +42,11 @@ export class CollectionView extends React.Component<FieldViewProps> {
 
     onContextMenu = (e: React.MouseEvent): void => {
         if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-            ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "project-diagram" });
+            ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "signature" });
             if (CollectionBaseView.InSafeMode()) {
                 ContextMenu.Instance.addItem({ description: "Test Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Invalid), icon: "project-diagram" });
             }
-            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "project-diagram" });
+            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" });
             ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
         }
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 38f3db19f..9e86b411e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash } from '@fortawesome/free-solid-svg-icons';
+import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash, faConciergeBell, faFolder, faMapPin, faLink, faFingerprint, faCrosshairs, faDesktop } from '@fortawesome/free-solid-svg-icons';
 import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
@@ -19,7 +19,6 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
@@ -39,6 +38,13 @@ library.add(faLayerGroup);
 library.add(faAlignCenter);
 library.add(faCaretSquareRight);
 library.add(faSquare);
+library.add(faConciergeBell);
+library.add(faFolder);
+library.add(faMapPin);
+library.add(faLink);
+library.add(faFingerprint);
+library.add(faCrosshairs);
+library.add(faDesktop);
 
 const linkSchema = createSchema({
     title: "string",
@@ -308,21 +314,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         e.preventDefault();
 
         const cm = ContextMenu.Instance;
-        cm.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "expand-arrows-alt" });
-        cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton, icon: "expand-arrows-alt" });
-        cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" });
-        cm.addItem({ description: "Center", event: () => this.props.focus(this.props.Document), icon: "align-center" });
-        cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "expand-arrows-alt" });
+        cm.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "desktop" });
+        cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" });
         cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" });
+        cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" });
+        cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
+        cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton, icon: "concierge-bell" });
         cm.addItem({
             description: "Find aliases", event: async () => {
                 const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
                 CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(["title"], aliases, {}));
-            }, icon: "expand-arrows-alt"
+            }, icon: "search"
         });
-        cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])), icon: "expand-arrows-alt" });
-        cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "expand-arrows-alt" });
-        cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "expand-arrows-alt" });
+        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 ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
         cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         if (!this.topMost) {
             // DocumentViews should stop propagation of this event
-- 
cgit v1.2.3-70-g09d2


From c103acbe8f8df919e87f9a18872a94eb8bc1a001 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 19 May 2019 19:55:26 -0400
Subject: fixed some small things with presentation and icon

---
 src/client/views/DocumentDecorations.tsx | 6 +++---
 src/client/views/MainView.tsx            | 2 +-
 src/client/views/nodes/DocumentView.tsx  | 3 ++-
 src/client/views/nodes/IconBox.tsx       | 1 -
 4 files changed, 6 insertions(+), 6 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 5ec090f05..319e91337 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -276,16 +276,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let doc = selected[0].props.Document;
         let iconDoc = Docs.IconDocument(layoutString);
         iconDoc.isButton = true;
-        iconDoc.proto!.title = selected.length > 1 ? "ICONset" : "ICON" + StrCast(doc.title);
+        iconDoc.proto!.title = selected.length > 1 ? "-multiple-.icon" : StrCast(doc.title) + ".icon";
         iconDoc.labelField = selected.length > 1 ? undefined : this._fieldKey;
-        iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined;
+        //iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined;
         iconDoc.proto!.isMinimized = false;
         iconDoc.width = Number(MINIMIZED_ICON_SIZE);
         iconDoc.height = Number(MINIMIZED_ICON_SIZE);
         iconDoc.x = NumCast(doc.x);
         iconDoc.y = NumCast(doc.y) - 24;
         iconDoc.maximizedDocs = new List<Doc>(selected.map(s => s.props.Document.proto!));
-        doc.minimizedDoc = iconDoc;
+        selected.length === 1 && (doc.minimizedDoc = iconDoc);
         selected[0].props.addDocument && selected[0].props.addDocument(iconDoc, false);
         return iconDoc;
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cdb105e21..400562f12 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -47,7 +47,7 @@ export class MainView extends React.Component {
     private set mainContainer(doc: Opt<Doc>) {
         if (doc) {
             if (!("presentationView" in doc)) {
-                doc.presentationView = new Doc();
+                doc.presentationView = Docs.TreeDocument([], { title: "Presentation" });
             }
             CurrentUserUtils.UserDocument.activeWorkspace = doc;
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9e86b411e..5f218fd1f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -128,7 +128,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         // bcz: kind of ugly .. setup a reaction to update the title of a summary document's target (maximizedDocs) whenver the summary doc's title changes
-        this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
+        this._reactionDisposer = reaction(() => [DocListCast(this.props.Document.maximizedDocs).map(md => md.title),
+        this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
             () => {
                 let maxDoc = DocListCast(this.props.Document.maximizedDocs);
                 if (maxDoc.length === 1 && StrCast(this.props.Document.title).startsWith("-") && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index b42eb44a5..00021bc78 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -12,7 +12,6 @@ import { IconField } from "../../../new_fields/IconField";
 import { ContextMenu } from "../ContextMenu";
 import Measure from "react-measure";
 import { MINIMIZED_ICON_SIZE } from "../../views/globalCssVariables.scss";
-import { listSpec } from "../../../new_fields/Schema";
 
 
 library.add(faCaretUp);
-- 
cgit v1.2.3-70-g09d2


From af2346983eae1145167b70faf96a9aec0ca82427 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 20 May 2019 00:09:47 -0400
Subject: Fixed a bunch of demo bugs Moved Field Symbols to separate file
 Editing is mostly working in debug viewer

---
 deploy/debug/viewer.html                           |   1 +
 src/client/DocServer.ts                            |   3 +-
 src/client/northstar/dash-fields/HistogramField.ts |   7 +-
 src/client/northstar/dash-nodes/HistogramBox.tsx   |   2 +-
 src/client/util/DocumentManager.ts                 |   2 +-
 src/client/util/SearchUtil.ts                      |   2 +-
 src/client/util/TooltipTextMenu.tsx                |   2 +-
 src/client/util/type_decls.d                       | 128 +++++++--------------
 src/client/views/EditableView.tsx                  |  10 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/PresentationView.tsx              |   2 +-
 src/client/views/SearchBox.tsx                     |   2 +-
 .../views/collections/CollectionBaseView.tsx       |   2 +-
 .../views/collections/CollectionDockingView.tsx    |  14 ++-
 src/client/views/collections/CollectionPDFView.tsx |   2 +-
 .../views/collections/CollectionSchemaView.tsx     |   2 +-
 .../views/collections/CollectionTreeView.tsx       |   2 +-
 .../views/collections/CollectionVideoView.tsx      |   2 +-
 src/client/views/collections/CollectionView.tsx    |   2 +-
 .../views/collections/ParentDocumentSelector.tsx   |   2 +-
 .../CollectionFreeFormLinksView.tsx                |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   3 -
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 src/client/views/nodes/KeyValueBox.tsx             |   4 +-
 src/client/views/nodes/KeyValuePair.tsx            |   4 +-
 src/client/views/nodes/LinkMenu.tsx                |   2 +-
 src/debug/Viewer.tsx                               |  53 +++++++--
 src/new_fields/CursorField.ts                      |   7 +-
 src/new_fields/DateField.ts                        |   7 +-
 src/new_fields/Doc.ts                              |  43 ++++---
 src/new_fields/FieldSymbols.ts                     |  10 ++
 src/new_fields/HtmlField.ts                        |   7 +-
 src/new_fields/IconField.ts                        |   7 +-
 src/new_fields/InkField.ts                         |   7 +-
 src/new_fields/List.ts                             |  16 ++-
 src/new_fields/ObjectField.ts                      |   7 +-
 src/new_fields/Proxy.ts                            |   9 +-
 src/new_fields/RefField.ts                         |   5 +-
 src/new_fields/RichTextField.ts                    |   7 +-
 src/new_fields/URLField.ts                         |   9 +-
 src/new_fields/util.ts                             |   7 +-
 42 files changed, 240 insertions(+), 173 deletions(-)
 create mode 100644 src/new_fields/FieldSymbols.ts

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/deploy/debug/viewer.html b/deploy/debug/viewer.html
index 3785a6602..8c265ccb8 100644
--- a/deploy/debug/viewer.html
+++ b/deploy/debug/viewer.html
@@ -3,6 +3,7 @@
 <head>
     <title>Document Debugger</title>
     <link href="https://fonts.googleapis.com/css?family=Fjalla+One|Hind+Siliguri:300" rel="stylesheet">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/3.3.1/typescript.min.js"></script>
 </head>
 
 <body>
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index f1b50d5a0..cbcf751ee 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -3,7 +3,8 @@ import { MessageStore } from "./../server/Message";
 import { Opt } from '../new_fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
-import { RefField, HandleUpdate, Id } from '../new_fields/RefField';
+import { RefField } from '../new_fields/RefField';
+import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
 
 export namespace DocServer {
     const _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index 1ee2189b9..31040a474 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -3,10 +3,11 @@ import { custom, serializable } from "serializr";
 import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
 import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
 import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
-import { ObjectField, Copy } from "../../../new_fields/ObjectField";
+import { ObjectField } from "../../../new_fields/ObjectField";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { OmitKeys } from "../../../Utils";
 import { Deserializable } from "../../util/SerializationHelper";
+import { Copy, ToScriptString } from "../../../new_fields/FieldSymbols";
 
 function serialize(field: HistogramField) {
     let obj = OmitKeys(field, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit;
@@ -55,4 +56,8 @@ export class HistogramField extends ObjectField {
         let z = this.HistoOp.Copy;
         return new HistogramField(HistogramOperation.Duplicate(this.HistoOp));
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index eb1ad69b7..a9646ed31 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -19,7 +19,7 @@ import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
 import { StyleConstants } from "../utils/StyleContants";
 import { Cast } from "../../../new_fields/Types";
 import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 
 
 @observer
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index be448098a..a5e768dcf 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -5,10 +5,10 @@ import { FieldValue, Cast, NumCast, BoolCast } from '../../new_fields/Types';
 import { listSpec } from '../../new_fields/Schema';
 import { undoBatch } from './UndoManager';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { Id } from '../../new_fields/RefField';
 import { CollectionView } from '../views/collections/CollectionView';
 import { CollectionPDFView } from '../views/collections/CollectionPDFView';
 import { CollectionVideoView } from '../views/collections/CollectionVideoView';
+import { Id } from '../../new_fields/FieldSymbols';
 
 
 export class DocumentManager {
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 4ccff0d1b..e8eb70837 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -1,7 +1,7 @@
 import * as rp from 'request-promise';
 import { DocServer } from '../DocServer';
 import { Doc } from '../../new_fields/Doc';
-import { Id } from '../../new_fields/RefField';
+import { Id } from '../../new_fields/FieldSymbols';
 
 export namespace SearchUtil {
     export function Search(query: string, returnDocs: true): Promise<Doc[]>;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 4d40d09b2..a1f80120f 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -22,12 +22,12 @@ import { throwStatement } from "babel-types";
 import { View } from "@react-pdf/renderer";
 import { DragManager } from "./DragManager";
 import { Doc, Opt, Field } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/RefField";
 import { Utils } from "../northstar/utils/Utils";
 import { DocServer } from "../DocServer";
 import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import { DocumentManager } from "./DocumentManager";
+import { Id } from "../../new_fields/FieldSymbols";
 
 const SVG = "http://www.w3.org/2000/svg";
 
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 47c3481b2..51114d0e2 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -119,104 +119,54 @@ interface URL {
     username: string;
     toJSON(): string;
 }
+interface PromiseLike<T> {
+    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
+}
+interface Promise<T> {
+    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
+    catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
+}
 
-declare type FieldId = string;
+declare const Update: unique symbol;
+declare const Self: unique symbol;
+declare const SelfProxy: unique symbol;
+declare const HandleUpdate: unique symbol;
+declare const Id: unique symbol;
+declare const OnUpdate: unique symbol;
+declare const Parent: unique symbol;
+declare const Copy: unique symbol;
+declare const ToScriptString: unique symbol;
 
-declare abstract class Field {
-    Id: FieldId;
-    abstract ToScriptString(): string;
-    abstract TrySetValue(value: any): boolean;
-    abstract GetValue(): any;
-    abstract Copy(): Field;
-}
+declare abstract class RefField {
+    readonly [Id]: FieldId;
+
+    constructor(id?: FieldId);
+    protected [HandleUpdate]?(diff: any): void;
 
-declare abstract class BasicField<T> extends Field {
-    constructor(data: T);
-    Data: T;
-    TrySetValue(value: any): boolean;
-    GetValue(): any;
+    abstract [ToScriptString](): string;
 }
 
-declare class TextField extends BasicField<string>{
-    constructor();
-    constructor(data: string);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class ImageField extends BasicField<URL>{
-    constructor();
-    constructor(data: URL);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class HtmlField extends BasicField<string>{
-    constructor();
-    constructor(data: string);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class NumberField extends BasicField<number>{
-    constructor();
-    constructor(data: number);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class WebField extends BasicField<URL>{
-    constructor();
-    constructor(data: URL);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class ListField<T> extends BasicField<T[]>{
-    constructor();
-    constructor(data: T[]);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class Key extends Field {
-    constructor(name:string);
-    Name: string;
-    TrySetValue(value: any): boolean;
-    GetValue(): any;
-    Copy(): Field;
-    ToScriptString(): string;
-}
-declare type FIELD_WAITING = null;
-declare type Opt<T> = T | undefined;
-declare type FieldValue<T> = Opt<T> | FIELD_WAITING;
-// @ts-ignore
-declare class Document extends Field {
-    TrySetValue(value: any): boolean;
-    GetValue(): any;
-    Copy(): Field;
-    ToScriptString(): string;
+declare abstract class ObjectField {
+    protected [OnUpdate](diff?: any): void;
+    private [Parent]?: RefField | ObjectField;
+    abstract [Copy](): ObjectField;
 
-    Width(): number;
-    Height(): number;
-    Scale(): number;
-    Title: string;
+    abstract [ToScriptString](): string;
+}
+declare type FieldId = string;
 
-    Get(key: Key): FieldValue<Field>;
-    GetAsync(key: Key, callback: (field: Field) => void): boolean;
-    GetOrCreateAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: T) => void): void;
-    GetT<T extends Field>(key: Key, ctor: { new(): T }): FieldValue<T>;
-    GetOrCreate<T extends Field>(key: Key, ctor: { new(): T }): T;
-    GetData<T, U extends Field & { Data: T }>(key: Key, ctor: { new(): U }, defaultVal: T): T;
-    GetHtml(key: Key, defaultVal: string): string;
-    GetNumber(key: Key, defaultVal: number): number;
-    GetText(key: Key, defaultVal: string): string;
-    GetList<T extends Field>(key: Key, defaultVal: T[]): T[];
-    Set(key: Key, field: Field | undefined): void;
-    SetData<T, U extends Field & { Data: T }>(key: Key, value: T, ctor: { new(): U }): void;
-    SetText(key: Key, value: string): void;
-    SetNumber(key: Key, value: number): void;
-    GetPrototype(): FieldValue<Document>;
-    GetAllPrototypes(): Document[];
-    MakeDelegate(): Document;
+declare type Field = number | string | boolean | ObjectField | RefField;
+
+declare type Opt<T> = T | undefined;
+declare class Doc extends RefField {
+    [key: string]: Field | undefined;
+    [ToScriptString](): string;
 }
 
-declare const KeyStore: {
-    [name: string]: Key;
+declare class ListImpl<T extends Field> extends ObjectField {
+    [index: number]: T | (T extends RefField ? Promise<T> : never);
+    [ToScriptString](): string;
+    [Copy](): ObjectField;
 }
 
 // @ts-ignore
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 78143ccda..c946d68e1 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -22,7 +22,7 @@ export interface EditableProps {
      * The contents to render when not editing
      */
     contents: any;
-    height: number;
+    height?: number;
     display?: string;
     oneLine?: boolean;
 }
@@ -53,6 +53,12 @@ export class EditableView extends React.Component<EditableProps> {
         }
     }
 
+    @action
+    onClick = (e: React.MouseEvent) => {
+        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)}
@@ -60,7 +66,7 @@ export class EditableView extends React.Component<EditableProps> {
         } else {
             return (
                 <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`} style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
-                    onClick={action(() => this.editing = true)} >
+                    onClick={this.onClick} >
                     <span>{this.props.contents}</span>
                 </div>
             );
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 400562f12..9edbba997 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -29,7 +29,7 @@ import { FieldResult, Field, Doc, Opt } from '../../new_fields/Doc';
 import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
 import { DocServer } from '../DocServer';
 import { listSpec } from '../../new_fields/Schema';
-import { Id } from '../../new_fields/RefField';
+import { Id } from '../../new_fields/FieldSymbols';
 import { HistoryUtil } from '../util/History';
 import { CollectionBaseView } from './collections/CollectionBaseView';
 
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index 9c37e9000..ce679aa0a 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -7,7 +7,7 @@ import { Utils } from "../../Utils";
 import { Doc, DocListCast, DocListCastAsync } from "../../new_fields/Doc";
 import { listSpec } from "../../new_fields/Schema";
 import { Cast, NumCast, FieldValue, PromiseValue, StrCast } from "../../new_fields/Types";
-import { Id } from "../../new_fields/RefField";
+import { Id } from "../../new_fields/FieldSymbols";
 import { List } from "../../new_fields/List";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 6e64e1af1..8efd8d266 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -16,7 +16,7 @@ import { isString } from 'util';
 import { constant } from 'async';
 import { DocServer } from '../DocServer';
 import { Doc } from '../../new_fields/Doc';
-import { Id } from '../../new_fields/RefField';
+import { Id } from '../../new_fields/FieldSymbols';
 import { DocumentManager } from '../util/DocumentManager';
 import { SetupDrag } from '../util/DragManager';
 import { Docs } from '../documents/Documents';
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 84ffbac36..5686ccfef 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -7,8 +7,8 @@ import { Cast, FieldValue, PromiseValue, NumCast } from '../../../new_fields/Typ
 import { Doc, FieldResult, Opt, DocListCast } from '../../../new_fields/Doc';
 import { listSpec } from '../../../new_fields/Schema';
 import { List } from '../../../new_fields/List';
-import { Id } from '../../../new_fields/RefField';
 import { SelectionManager } from '../../util/SelectionManager';
+import { Id } from '../../../new_fields/FieldSymbols';
 
 export enum CollectionViewType {
     Invalid,
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 483209f86..e904358a9 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -6,7 +6,7 @@ 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 { FieldId, Id } from "../../../new_fields/RefField";
+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";
@@ -21,6 +21,7 @@ import React = require("react");
 import { ParentDocSelector } from './ParentDocumentSelector';
 import { DocumentManager } from '../../util/DocumentManager';
 import { CollectionViewType } from './CollectionBaseView';
+import { Id } from '../../../new_fields/FieldSymbols';
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -75,7 +76,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     @undoBatch
     @action
-    public CloseRightSplit(document: Doc): boolean {
+    public CloseRightSplit = (document: Doc): boolean => {
         let retVal = false;
         if (this._goldenLayout.root.contentItems[0].isRow) {
             retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
@@ -118,7 +119,7 @@ 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, minimize: boolean = false) => {
         let docs = Cast(this.props.Document.data, listSpec(Doc));
         if (docs) {
             docs.push(document);
@@ -155,7 +156,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         return newContentItem;
     }
     @action
-    public AddTab(stack: any, document: Doc) {
+    public AddTab = (stack: any, document: Doc) => {
         let docs = Cast(this.props.Document.data, listSpec(Doc));
         if (docs) {
             docs.push(document);
@@ -412,10 +413,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
-    _stack: any;
+    get _stack(): any {
+        return (this.props as any).glContainer.parent.parent;
+    }
     constructor(props: any) {
         super(props);
-        this._stack = (this.props as any).glContainer.parent.parent;
         DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
     }
 
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index a6614da21..5e51437a4 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -8,7 +8,7 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView";
 import { emptyFunction } from "../../../Utils";
 import { NumCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 
 
 @observer
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index f15da41ff..b25b48339 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -23,7 +23,7 @@ import { Opt, Field, Doc, DocListCastAsync, DocListCast } from "../../../new_fie
 import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
 import { List } from "../../../new_fields/List";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 import { Gateway } from "../../northstar/manager/Gateway";
 import { Docs } from "../../documents/Documents";
 import { ContextMenu } from "../ContextMenu";
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 72fa69cb1..8ad495762 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -11,7 +11,7 @@ import React = require("react");
 import { Document, listSpec } from '../../../new_fields/Schema';
 import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types';
 import { Doc, DocListCast } from '../../../new_fields/Doc';
-import { Id } from '../../../new_fields/RefField';
+import { Id } from '../../../new_fields/FieldSymbols';
 import { ContextMenu } from '../ContextMenu';
 import { undoBatch } from '../../util/UndoManager';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 9ab959f3c..27f23a1a8 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -7,7 +7,7 @@ import "./CollectionVideoView.scss";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import { emptyFunction } from "../../../Utils";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 import { VideoBox } from "../nodes/VideoBox";
 import { NumCast } from "../../../new_fields/Types";
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 59c763be8..bfdef8e8c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -2,7 +2,7 @@ import { library } from '@fortawesome/fontawesome-svg-core';
 import { faProjectDiagram, faSquare, faTh, faTree, faSignature, faThList } from '@fortawesome/free-solid-svg-icons';
 import { observer } from "mobx-react";
 import * as React from 'react';
-import { Id } from '../../../new_fields/RefField';
+import { Id } from '../../../new_fields/FieldSymbols';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../ContextMenu";
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 52f7914f3..4d07c31a7 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -3,7 +3,7 @@ import './ParentDocumentSelector.scss';
 import { Doc } from "../../../new_fields/Doc";
 import { observer } from "mobx-react";
 import { observable, action, runInAction } from "mobx";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 import { SearchUtil } from "../../util/SearchUtil";
 import { CollectionDockingView } from "./CollectionDockingView";
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index d5ce4e1e7..e1ff715d1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -11,7 +11,7 @@ import { Doc, DocListCastAsync, DocListCast } from "../../../../new_fields/Doc";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
 import { listSpec } from "../../../../new_fields/Schema";
 import { List } from "../../../../new_fields/List";
-import { Id } from "../../../../new_fields/RefField";
+import { Id } from "../../../../new_fields/FieldSymbols";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9cb8443f4..ee6f4821f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -22,9 +22,9 @@ import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Sc
 import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc";
 import { FieldValue, Cast, NumCast, BoolCast } from "../../../../new_fields/Types";
 import { pageSchema } from "../../nodes/ImageBox";
-import { Id } from "../../../../new_fields/RefField";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { HistoryUtil } from "../../../util/History";
+import { Id } from "../../../../new_fields/FieldSymbols";
 
 export const panZoomSchema = createSchema({
     panX: "number",
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 4587c2227..2029b91e5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -17,9 +17,6 @@ import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { ImageField } from "../../../../new_fields/URLField";
 import { Template, Templates } from "../../Templates";
-import { Gateway } from "../../../northstar/manager/Gateway";
-import { DocServer } from "../../../DocServer";
-import { Id } from "../../../../new_fields/RefField";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5f218fd1f..87c88f57c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,8 +4,7 @@ import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
-import { Copy, ObjectField } from "../../../new_fields/ObjectField";
-import { Id } from "../../../new_fields/RefField";
+import { ObjectField } from "../../../new_fields/ObjectField";
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
 import { BoolCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
@@ -29,6 +28,7 @@ import { Template } from "./../Templates";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
+import { Id, Copy } from '../../../new_fields/FieldSymbols';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(faTrash);
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 86437a6c1..8cb576786 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -8,7 +8,7 @@ import "./KeyValueBox.scss";
 import { KeyValuePair } from "./KeyValuePair";
 import React = require("react");
 import { NumCast, Cast, FieldValue } from "../../../new_fields/Types";
-import { Doc, IsField } from "../../../new_fields/Doc";
+import { Doc, Field } from "../../../new_fields/Doc";
 
 @observer
 export class KeyValueBox extends React.Component<FieldViewProps> {
@@ -41,7 +41,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                 let res = script.run();
                 if (!res.success) return;
                 const field = res.result;
-                if (IsField(field)) {
+                if (Field.IsField(field)) {
                     realDoc[this._keyInput] = field;
                 }
                 this._keyInput = "";
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 4f7919f50..7a88985c0 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -9,7 +9,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./KeyValueBox.scss";
 import "./KeyValuePair.scss";
 import React = require("react");
-import { Doc, Opt, IsField } from '../../../new_fields/Doc';
+import { Doc, Opt, Field } from '../../../new_fields/Doc';
 import { FieldValue } from '../../../new_fields/Types';
 
 // Represents one row in a key value plane
@@ -75,7 +75,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                             let res = script.run();
                             if (!res.success) return false;
                             const field = res.result;
-                            if (IsField(field)) {
+                            if (Field.IsField(field)) {
                                 props.Document[props.fieldKey] = field;
                                 return true;
                             }
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 4cf798249..3f09d6214 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -7,7 +7,7 @@ import './LinkMenu.scss';
 import React = require("react");
 import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 
 interface Props {
     docView: DocumentView;
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 720e1640a..4314e2132 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -3,11 +3,27 @@ import "normalize.css";
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { observer } from 'mobx-react';
-import { Doc, Field, FieldResult } from '../new_fields/Doc';
+import { Doc, Field, FieldResult, Opt } from '../new_fields/Doc';
 import { DocServer } from '../client/DocServer';
-import { Id } from '../new_fields/RefField';
+import { Id } from '../new_fields/FieldSymbols';
 import { List } from '../new_fields/List';
 import { URLField } from '../new_fields/URLField';
+import { EditableView } from '../client/views/EditableView';
+import { CompileScript } from '../client/util/Scripting';
+
+function applyToDoc(doc: { [index: string]: FieldResult }, key: string, scriptString: string): boolean;
+function applyToDoc(doc: { [index: number]: FieldResult }, key: number, scriptString: string): boolean;
+function applyToDoc(doc: any, key: string | number, scriptString: string): boolean {
+    let script = CompileScript(scriptString, { addReturn: true, params: { this: doc instanceof Doc ? Doc.name : List.name } });
+    if (!script.compiled) {
+        return false;
+    }
+    const res = script.run({ this: doc });
+    if (!res.success) return false;
+    if (!Field.IsField(res.result)) return false;
+    doc[key] = res.result;
+    return true;
+}
 
 configure({
     enforceActions: "observed"
@@ -18,12 +34,18 @@ class ListViewer extends React.Component<{ field: List<Field> }>{
     @observable
     expanded = false;
 
+    @action
+    onClick = (e: React.MouseEvent) => {
+        this.expanded = !this.expanded;
+        e.stopPropagation();
+    }
+
     render() {
         let content;
         if (this.expanded) {
             content = (
                 <div>
-                    {this.props.field.map((field, index) => <DebugViewer field={field} key={index} />)}
+                    {this.props.field.map((field, index) => <DebugViewer field={field} key={index} setValue={value => applyToDoc(this.props.field, index, value)} />)}
                 </div>
             );
         } else {
@@ -31,7 +53,7 @@ class ListViewer extends React.Component<{ field: List<Field> }>{
         }
         return (
             <div>
-                <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
+                <button onClick={this.onClick}>Toggle</button>
                 {content}
             </div >
         );
@@ -42,6 +64,13 @@ class ListViewer extends React.Component<{ field: List<Field> }>{
 class DocumentViewer extends React.Component<{ field: Doc }> {
     @observable
     expanded = false;
+
+    @action
+    onClick = (e: React.MouseEvent) => {
+        this.expanded = !this.expanded;
+        e.stopPropagation();
+    }
+
     render() {
         let content;
         if (this.expanded) {
@@ -50,7 +79,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
                 return (
                     <div key={key}>
                         <b>({key}): </b>
-                        <DebugViewer field={this.props.field[key]}></DebugViewer>
+                        <DebugViewer field={this.props.field[key]} setValue={value => applyToDoc(this.props.field, key, value)}></DebugViewer>
                     </div>
                 );
             });
@@ -67,7 +96,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
         }
         return (
             <div>
-                <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
+                <button onClick={this.onClick}>Toggle</button>
                 {content}
             </div >
         );
@@ -75,7 +104,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
 }
 
 @observer
-class DebugViewer extends React.Component<{ field: FieldResult }> {
+class DebugViewer extends React.Component<{ field: FieldResult, setValue(value: string): boolean }> {
 
     render() {
         let content;
@@ -90,10 +119,14 @@ class DebugViewer extends React.Component<{ field: FieldResult }> {
             content = <p>{field}</p>;
         } else if (field instanceof URLField) {
             content = <p>{field.url.href}</p>;
+        } else if (field instanceof Promise) {
+            return <p>Field loading</p>;
         } else {
-            content = <p>Unrecognized field type</p>;
+            return <p>Unrecognized field type</p>;
         }
-        return content;
+
+        return <EditableView GetValue={() => Field.toScriptString(field)} SetValue={this.props.setValue}
+            contents={content}></EditableView>;
     }
 }
 
@@ -129,7 +162,7 @@ class Viewer extends React.Component {
                     onChange={this.inputOnChange}
                     onKeyDown={this.onKeyPress} />
                 <div>
-                    {this.fields.map((field, index) => <DebugViewer field={field} key={index}></DebugViewer>)}
+                    {this.fields.map((field, index) => <DebugViewer field={field} key={index} setValue={() => false}></DebugViewer>)}
                 </div>
             </>
         );
diff --git a/src/new_fields/CursorField.ts b/src/new_fields/CursorField.ts
index fc144222c..1be1ec3e0 100644
--- a/src/new_fields/CursorField.ts
+++ b/src/new_fields/CursorField.ts
@@ -1,7 +1,8 @@
-import { ObjectField, Copy, OnUpdate } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
 import { observable } from "mobx";
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, createSimpleSchema, object } from "serializr";
+import { OnUpdate, ToScriptString, Copy } from "./FieldSymbols";
 
 export type CursorPosition = {
     x: number,
@@ -52,4 +53,8 @@ export default class CursorField extends ObjectField {
     [Copy]() {
         return new CursorField(this.data);
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
\ No newline at end of file
diff --git a/src/new_fields/DateField.ts b/src/new_fields/DateField.ts
index c0a79f267..fc8abb9d9 100644
--- a/src/new_fields/DateField.ts
+++ b/src/new_fields/DateField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, date } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("date")
 export class DateField extends ObjectField {
@@ -15,4 +16,8 @@ export class DateField extends ObjectField {
     [Copy]() {
         return new DateField(this.date);
     }
+
+    [ToScriptString]() {
+        return `new DateField(new Date(${this.date.toISOString()}))`;
+    }
 }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 02dd34cb4..f4514c33e 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -5,27 +5,33 @@ import { DocServer } from "../client/DocServer";
 import { setter, getter, getField, updateFunction, deleteProperty } from "./util";
 import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast } from "./Types";
 import { listSpec } from "./Schema";
-import { ObjectField, Parent, OnUpdate } from "./ObjectField";
-import { RefField, FieldId, Id, HandleUpdate } from "./RefField";
-
-export function IsField(field: any): field is Field {
-    return (typeof field === "string")
-        || (typeof field === "number")
-        || (typeof field === "boolean")
-        || (field instanceof ObjectField)
-        || (field instanceof RefField);
+import { ObjectField } from "./ObjectField";
+import { RefField, FieldId } from "./RefField";
+import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id } from "./FieldSymbols";
+
+export namespace Field {
+    export function toScriptString(field: Field): string {
+        if (typeof field === "string") {
+            return `"${field}"`;
+        } else if (typeof field === "number" || typeof field === "boolean") {
+            return String(field);
+        } else {
+            return field[ToScriptString]();
+        }
+    }
+    export function IsField(field: any): field is Field {
+        return (typeof field === "string")
+            || (typeof field === "number")
+            || (typeof field === "boolean")
+            || (field instanceof ObjectField)
+            || (field instanceof RefField);
+    }
 }
 export type Field = number | string | boolean | ObjectField | RefField;
 export type Opt<T> = T | undefined;
 export type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>;
 export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
 
-export const Update = Symbol("Update");
-export const Self = Symbol("Self");
-export const SelfProxy = Symbol("SelfProxy");
-export const WidthSym = Symbol("Width");
-export const HeightSym = Symbol("Height");
-
 /**
  * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.  
  * If a default value is given, that will be returned instead of undefined.  
@@ -43,6 +49,9 @@ export function DocListCast(field: FieldResult): Doc[] {
     return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[];
 }
 
+export const WidthSym = Symbol("Width");
+export const HeightSym = Symbol("Height");
+
 @Deserializable("doc").withFields(["id"])
 export class Doc extends RefField {
     constructor(id?: FieldId, forceSave?: boolean) {
@@ -102,6 +111,10 @@ export class Doc extends RefField {
     public [WidthSym] = () => NumCast(this[SelfProxy].width);  // bcz: is this the right way to access width/height?   it didn't work with : this.width
     public [HeightSym] = () => NumCast(this[SelfProxy].height);
 
+    [ToScriptString]() {
+        return "invalid";
+    }
+
     public [HandleUpdate](diff: any) {
         console.log(diff);
         const set = diff.$set;
diff --git a/src/new_fields/FieldSymbols.ts b/src/new_fields/FieldSymbols.ts
new file mode 100644
index 000000000..a436dcf2b
--- /dev/null
+++ b/src/new_fields/FieldSymbols.ts
@@ -0,0 +1,10 @@
+
+export const Update = Symbol("Update");
+export const Self = Symbol("Self");
+export const SelfProxy = Symbol("SelfProxy");
+export const HandleUpdate = Symbol("HandleUpdate");
+export const Id = Symbol("Id");
+export const OnUpdate = Symbol("OnUpdate");
+export const Parent = Symbol("Parent");
+export const Copy = Symbol("Copy");
+export const ToScriptString = Symbol("Copy");
\ No newline at end of file
diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts
index d998746bb..f952acff9 100644
--- a/src/new_fields/HtmlField.ts
+++ b/src/new_fields/HtmlField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, primitive } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("html")
 export class HtmlField extends ObjectField {
@@ -15,4 +16,8 @@ export class HtmlField extends ObjectField {
     [Copy]() {
         return new HtmlField(this.html);
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
diff --git a/src/new_fields/IconField.ts b/src/new_fields/IconField.ts
index 1a928389d..62b2cd254 100644
--- a/src/new_fields/IconField.ts
+++ b/src/new_fields/IconField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, primitive } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("icon")
 export class IconField extends ObjectField {
@@ -15,4 +16,8 @@ export class IconField extends ObjectField {
     [Copy]() {
         return new IconField(this.icon);
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 2d75f8a19..4e3b7abe0 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, custom, createSimpleSchema, list, object, map } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
 import { deepCopy } from "../Utils";
 
 export enum InkTool {
@@ -40,4 +41,8 @@ export class InkField extends ObjectField {
     [Copy]() {
         return new InkField(deepCopy(this.inkData));
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index 70e36f911..f1e4c4721 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -1,11 +1,12 @@
 import { Deserializable, autoObject } from "../client/util/SerializationHelper";
-import { Field, Update, Self, FieldResult, SelfProxy } from "./Doc";
+import { Field } from "./Doc";
 import { setter, getter, deleteProperty, updateFunction } from "./util";
 import { serializable, alias, list } from "serializr";
 import { observable, action } from "mobx";
-import { ObjectField, OnUpdate, Copy, Parent } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
 import { RefField } from "./RefField";
 import { ProxyField } from "./Proxy";
+import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, Copy } from "./FieldSymbols";
 
 const listHandlers: any = {
     /// Mutator methods
@@ -225,7 +226,7 @@ type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
 
 @Deserializable("list")
 class ListImpl<T extends Field> extends ObjectField {
-    constructor(fields: T[] = []) {
+    constructor(fields?: T[]) {
         super();
         const list = new Proxy<this>(this, {
             set: setter,
@@ -244,7 +245,9 @@ class ListImpl<T extends Field> extends ObjectField {
             defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
         });
         this[SelfProxy] = list;
-        (list as any).push(...fields);
+        if (fields) {
+            (list as any).push(...fields);
+        }
         return list;
     }
 
@@ -284,6 +287,11 @@ class ListImpl<T extends Field> extends ObjectField {
 
     private [Self] = this;
     private [SelfProxy]: any;
+
+    [ToScriptString]() {
+        return "invalid";
+        // return `new List([${(this as any).map((field => Field.toScriptString(field))}])`;
+    }
 }
 export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
 export const List: { new <T extends Field>(fields?: T[]): List<T> } = ListImpl as any;
\ No newline at end of file
diff --git a/src/new_fields/ObjectField.ts b/src/new_fields/ObjectField.ts
index 51768c6db..5f4a6f8fb 100644
--- a/src/new_fields/ObjectField.ts
+++ b/src/new_fields/ObjectField.ts
@@ -1,14 +1,13 @@
 import { Doc } from "./Doc";
 import { RefField } from "./RefField";
-
-export const OnUpdate = Symbol("OnUpdate");
-export const Parent = Symbol("Parent");
-export const Copy = Symbol("Copy");
+import { OnUpdate, Parent, Copy, ToScriptString } from "./FieldSymbols";
 
 export abstract class ObjectField {
     protected [OnUpdate](diff?: any) { }
     private [Parent]?: RefField | ObjectField;
     abstract [Copy](): ObjectField;
+
+    abstract [ToScriptString](): string;
 }
 
 export namespace ObjectField {
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
index fd99ae1c0..130ec066e 100644
--- a/src/new_fields/Proxy.ts
+++ b/src/new_fields/Proxy.ts
@@ -3,8 +3,9 @@ import { FieldWaiting } from "./Doc";
 import { primitive, serializable } from "serializr";
 import { observable, action } from "mobx";
 import { DocServer } from "../client/DocServer";
-import { RefField, Id } from "./RefField";
-import { ObjectField, Copy } from "./ObjectField";
+import { RefField } from "./RefField";
+import { ObjectField } from "./ObjectField";
+import { Id, Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("proxy")
 export class ProxyField<T extends RefField> extends ObjectField {
@@ -26,6 +27,10 @@ export class ProxyField<T extends RefField> extends ObjectField {
         return new ProxyField<T>(this.fieldId);
     }
 
+    [ToScriptString]() {
+        return "invalid";
+    }
+
     @serializable(primitive())
     readonly fieldId: string = "";
 
diff --git a/src/new_fields/RefField.ts b/src/new_fields/RefField.ts
index 202c65f21..75ce4287f 100644
--- a/src/new_fields/RefField.ts
+++ b/src/new_fields/RefField.ts
@@ -1,9 +1,8 @@
 import { serializable, primitive, alias } from "serializr";
 import { Utils } from "../Utils";
+import { Id, HandleUpdate, ToScriptString } from "./FieldSymbols";
 
 export type FieldId = string;
-export const HandleUpdate = Symbol("HandleUpdate");
-export const Id = Symbol("Id");
 export abstract class RefField {
     @serializable(alias("id", primitive()))
     private __id: FieldId;
@@ -15,4 +14,6 @@ export abstract class RefField {
     }
 
     protected [HandleUpdate]?(diff: any): void;
+
+    abstract [ToScriptString](): string;
 }
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index eb30e76de..89d077a47 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -1,6 +1,7 @@
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
 import { serializable } from "serializr";
 import { Deserializable } from "../client/util/SerializationHelper";
+import { Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("RichTextField")
 export class RichTextField extends ObjectField {
@@ -15,4 +16,8 @@ export class RichTextField extends ObjectField {
     [Copy]() {
         return new RichTextField(this.Data);
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
\ No newline at end of file
diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts
index d00a95a16..a6f8f1cc5 100644
--- a/src/new_fields/URLField.ts
+++ b/src/new_fields/URLField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, custom } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { ToScriptString, Copy } from "./FieldSymbols";
 
 function url() {
     return custom(
@@ -13,7 +14,7 @@ function url() {
     );
 }
 
-export class URLField extends ObjectField {
+export abstract class URLField extends ObjectField {
     @serializable(url())
     readonly url: URL;
 
@@ -22,6 +23,10 @@ export class URLField extends ObjectField {
         this.url = url;
     }
 
+    [ToScriptString]() {
+        return `new ${this.constructor.name}(new URL(${this.url.href}))`;
+    }
+
     [Copy](): this {
         return new (this.constructor as any)(this.url);
     }
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index d94994a07..65a37a0d1 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -1,11 +1,12 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Update, Doc, Field } from "./Doc";
+import { Doc, Field } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField } from "./Proxy";
 import { FieldValue } from "./Types";
-import { RefField, Id } from "./RefField";
-import { ObjectField, Parent, OnUpdate } from "./ObjectField";
+import { RefField } from "./RefField";
+import { ObjectField } from "./ObjectField";
 import { action } from "mobx";
+import { Parent, OnUpdate, Update, Id } from "./FieldSymbols";
 
 export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
     if (SerializationHelper.IsSerializing()) {
-- 
cgit v1.2.3-70-g09d2


From 30fdae9e869e0f132c76b4fa99b784e75d6c8dae Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 22 May 2019 19:51:49 -0400
Subject: A bunch of fixes/changes

---
 src/client/documents/Documents.ts                  |  2 +
 src/client/util/DocumentManager.ts                 |  6 ++-
 src/client/views/InkingCanvas.scss                 | 42 ++++++++++++--------
 src/client/views/SearchBox.tsx                     | 10 +++--
 .../views/collections/CollectionPDFView.scss       | 39 ++++++++++--------
 src/client/views/collections/CollectionSubView.tsx |  4 ++
 .../views/collections/CollectionVideoView.tsx      | 46 +++++++++++++++++++++-
 .../collections/collectionFreeForm/MarqueeView.tsx |  5 ++-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  9 ++++-
 src/client/views/nodes/DocumentView.tsx            | 12 +++---
 src/client/views/nodes/ImageBox.tsx                |  6 ++-
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/client/views/nodes/PDFBox.tsx                  | 16 +++++++-
 src/client/views/nodes/VideoBox.tsx                |  8 +++-
 14 files changed, 153 insertions(+), 54 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 323534b2b..ae190a989 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -77,7 +77,9 @@ export namespace DocUtils {
             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) {
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index f113ff1d7..65c4b9e4b 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -115,11 +115,11 @@ export class DocumentManager {
     }
 
     @undoBatch
-    public jumpToDocument = async (docDelegate: Doc, forceDockFunc: boolean = false, dockFunc?: (doc: Doc) => void): Promise<void> => {
+    public jumpToDocument = async (docDelegate: Doc, forceDockFunc: boolean = false, dockFunc?: (doc: Doc) => void, linkPage?: number): Promise<void> => {
         let doc = Doc.GetProto(docDelegate);
-        const page = NumCast(doc.page, undefined);
         const contextDoc = await Cast(doc.annotationOn, Doc);
         if (contextDoc) {
+            const page = NumCast(doc.page, linkPage || 0);
             const curPage = NumCast(contextDoc.curPage, page);
             if (page !== curPage) contextDoc.curPage = page;
         }
@@ -127,11 +127,13 @@ export class DocumentManager {
         // using forceDockFunc as a flag for splitting linked to doc to the right...can change later if needed
         if (!forceDockFunc && (docView = DocumentManager.Instance.getDocumentView(doc))) {
             docView.props.Document.libraryBrush = true;
+            if (linkPage !== undefined) docView.props.Document.curPage = linkPage;
             docView.props.focus(docView.props.Document);
         } else {
             if (!contextDoc) {
                 const actualDoc = Doc.MakeAlias(docDelegate);
                 actualDoc.libraryBrush = true;
+                if (linkPage !== undefined) actualDoc.curPage = linkPage;
                 (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc);
             } else {
                 let contextView: DocumentView | null;
diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
index 8bcaf41dd..d95398f17 100644
--- a/src/client/views/InkingCanvas.scss
+++ b/src/client/views/InkingCanvas.scss
@@ -1,40 +1,50 @@
 @import "globalCssVariables";
 
 .inkingCanvas {
-    opacity:0.99;
+    opacity: 0.99;
+
     .jsx-parser {
-        position:absolute;
-        width:100%;
-        height:100%;
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        z-index: -1; // allows annotations to appear on videos when screen is full-size & ... 
     }
 }
-.inkingCanvas-paths-ink, .inkingCanvas-paths-markers, .inkingCanvas-noSelect, .inkingCanvas-canSelect {
+
+.inkingCanvas-paths-ink,
+.inkingCanvas-paths-markers,
+.inkingCanvas-noSelect,
+.inkingCanvas-canSelect {
     position: absolute;
     top: 0;
-    left:0;
+    left: 0;
     width: 8192px;
     height: 8192px;
-    cursor:"crosshair";
+    cursor: "crosshair";
     pointer-events: auto;
-    
+
 }
-.inkingCanvas-canSelect, 
-.inkingCanvas-noSelect    {
-    top:-50000px;
-    left:-50000px;
+
+.inkingCanvas-canSelect,
+.inkingCanvas-noSelect {
+    top: -50000px;
+    left: -50000px;
     width: 100000px;
     height: 100000px;
 }
-.inkingCanvas-noSelect  {
+
+.inkingCanvas-noSelect {
     pointer-events: none;
     cursor: "arrow";
 }
-.inkingCanvas-paths-ink, .inkingCanvas-paths-markers {
+
+.inkingCanvas-paths-ink,
+.inkingCanvas-paths-markers {
     pointer-events: none;
     z-index: 10000; // overlays ink on top of everything 
     cursor: "arrow";
 }
+
 .inkingCanvas-paths-markers {
     mix-blend-mode: multiply;
-}
-
+}
\ No newline at end of file
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 911fc8d9c..fa9462acf 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -21,6 +21,7 @@ import { DocumentManager } from '../util/DocumentManager';
 import { SetupDrag } from '../util/DragManager';
 import { Docs } from '../documents/Documents';
 import { RouteStore } from '../../server/RouteStore';
+import { NumCast } from '../../new_fields/Types';
 
 library.add(faSearch);
 library.add(faObjectGroup);
@@ -143,18 +144,21 @@ export class SearchBox extends React.Component {
         });
         let x = 0;
         let y = 0;
+        let maxy = 300;
         for (const doc of docs) {
             doc.x = x;
             doc.y = y;
             doc.width = 200;
-            doc.height = 200;
+            doc.height = 200 * NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1);
+            maxy = Math.max(doc.height, maxy);
             x += 250;
             if (x > 1000) {
+                maxy = 300;
                 x = 0;
-                y += 250;
+                y += maxy + 25;
             }
         }
-        return Docs.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, title: `Search Docs: "${this.searchString}"` });
+        return Docs.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/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss
index f6fb79582..50201bae8 100644
--- a/src/client/views/collections/CollectionPDFView.scss
+++ b/src/client/views/collections/CollectionPDFView.scss
@@ -1,49 +1,56 @@
 .collectionPdfView-buttonTray {
-    top : 15px;
-    left : 20px;
-    position: relative; 
+    top: 15px;
+    left: 20px;
+    position: relative;
     transform-origin: left top;
     position: absolute;
 }
+
 .collectionPdfView-thumb {
-    width:25px;
-    height:25px;
+    width: 25px;
+    height: 25px;
     transform-origin: left top;
     position: absolute;
     background: darkgray;
 }
+
 .collectionPdfView-slider {
-    width:25px;
-    height:25px;
+    width: 25px;
+    height: 25px;
     transform-origin: left top;
     position: absolute;
     background: lightgray;
 }
-.collectionPdfView-cont{
+
+.collectionPdfView-cont {
     width: 100%;
     height: 100%;
-    position: absolute; 
+    position: absolute;
     top: 0;
-    left:0;
+    left: 0;
+    z-index: -1;
 }
+
 .collectionPdfView-cont-dragging {
     span {
         user-select: none;
     }
 }
+
 .collectionPdfView-backward {
-    color : white;
+    color: white;
     font-size: 24px;
-    top :0px;
-    left : 0px;
+    top: 0px;
+    left: 0px;
     position: absolute;
     background-color: rgba(50, 50, 50, 0.2);
 }
+
 .collectionPdfView-forward {
-    color : white;
+    color: white;
     font-size: 24px;
-    top :0px;
-    left : 45px;
+    top: 0px;
+    left: 45px;
     position: absolute;
     background-color: rgba(50, 50, 50, 0.2);
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 7800b35df..4a21e4465 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -153,6 +153,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         @undoBatch
         @action
         protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
+            if (e.ctrlKey) {
+                e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl
+                return;
+            }
             let html = e.dataTransfer.getData("text/html");
             let text = e.dataTransfer.getData("text/plain");
 
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 27f23a1a8..7853544d5 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -1,4 +1,5 @@
 import { action, observable, trace } from "mobx";
+import * as htmlToImage from "html-to-image";
 import { observer } from "mobx-react";
 import { ContextMenu } from "../ContextMenu";
 import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
@@ -6,10 +7,14 @@ import React = require("react");
 import "./CollectionVideoView.scss";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
-import { emptyFunction } from "../../../Utils";
+import { emptyFunction, Utils } from "../../../Utils";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { VideoBox } from "../nodes/VideoBox";
-import { NumCast } from "../../../new_fields/Types";
+import { NumCast, Cast, StrCast } from "../../../new_fields/Types";
+import { VideoField } from "../../../new_fields/URLField";
+import { SearchBox } from "../SearchBox";
+import { DocServer } from "../../DocServer";
+import { Docs, DocUtils } from "../../documents/Documents";
 
 
 @observer
@@ -67,6 +72,43 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
     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
         }
+
+        let field = Cast(this.props.Document[this.props.fieldKey], VideoField);
+        if (field) {
+            let url = field.url.href;
+            ContextMenu.Instance.addItem({
+                description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt"
+            });
+        }
+        let width = NumCast(this.props.Document.width);
+        let height = NumCast(this.props.Document.height);
+        ContextMenu.Instance.addItem({
+            description: "Take Snapshot", event: async () => {
+                var canvas = document.createElement('canvas');
+                canvas.width = 640;
+                canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth);
+                var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
+                ctx && ctx.drawImage(this._videoBox!.player!, 0, 0, canvas.width, canvas.height);
+
+                //convert to desired file format
+                var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
+                // if you want to preview the captured image,
+
+                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 imageSummary = Docs.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-"
+                        });
+                        this.props.addDocument && this.props.addDocument(imageSummary, false);
+                        DocUtils.MakeLink(imageSummary, this.props.Document);
+                    }
+                });
+            },
+            icon: "expand-arrows-alt"
+        });
     }
 
     setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; };
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d72046800..973ebfbdd 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -296,7 +296,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     SearchBox.convertDataUri(dataUrl, "icon" + summary[Id] + "_image").then((returnedFilename) => {
                         if (returnedFilename) {
                             let url = DocServer.prepend(returnedFilename);
-                            let imageSummary = Docs.ImageDocument(url, { x: bounds.left, y: bounds.top + 100 / zoomBasis, width: 150, title: "-summary image-" });
+                            let imageSummary = Docs.ImageDocument(url, {
+                                x: bounds.left, y: bounds.top + 100 / zoomBasis,
+                                width: 150, height: bounds.height / bounds.width * 150, title: "-summary image-"
+                            });
                             summary.imageSummary = imageSummary;
                             this.props.addDocument(imageSummary, false);
                         }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index aaaa6a9c5..66d91f9c2 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -195,6 +195,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             this.props.Document.libraryBrush = false;
         }
         let altKey = e.altKey;
+        let ctrlKey = e.ctrlKey;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
             let isExpander = (e.target as any).id === "isExpander";
@@ -242,9 +243,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     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]];
-                    if (linkedFwdDocs) {
+
+                    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], altKey, document => this.props.addDocTab(document, maxLocation));
+                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0]);
                     }
                 }
             }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 87c88f57c..01c4d82fb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -234,12 +234,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     makeButton = (e: React.MouseEvent): void => {
         let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
         doc.isButton = !BoolCast(doc.isButton, false);
-        if (doc.isButton && !doc.nativeWidth) {
-            doc.nativeWidth = this.props.Document[WidthSym]();
-            doc.nativeHeight = this.props.Document[HeightSym]();
-        } else {
+        if (StrCast(doc.layout).indexOf("Formatted") !== -1) { // only need to freeze the dimensions of text boxes since they don't have a native width and height naturally
+            if (doc.isButton && !doc.nativeWidth) {
+                doc.nativeWidth = this.props.Document[WidthSym]();
+                doc.nativeHeight = this.props.Document[HeightSym]();
+            } else {
 
-            doc.nativeWidth = doc.nativeHeight = undefined;
+                doc.nativeWidth = doc.nativeHeight = undefined;
+            }
         }
     }
     fullScreenClicked = (e: React.MouseEvent): void => {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 71bda6fea..cc0dc8220 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -47,7 +47,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     onLoad = (target: any) => {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
-        if (this._photoIndex === 0 && (this.props as any).id !== "isExpander" && (!this.Document.nativeHeight || !this.Document.nativeWidth)) {
+        if (this._photoIndex === 0 && (this.props as any).id !== "isExpander" && (!this.Document.nativeWidth || !this.Document.nativeHeight || Math.abs(this.Document.nativeWidth / this.Document.nativeHeight - w / h) > 0.05)) {
             Doc.SetOnPrototype(this.Document, "nativeHeight", FieldValue(this.Document.nativeWidth, 0) * h / w);
             this.Document.height = FieldValue(this.Document.width, 0) * h / w;
         }
@@ -165,8 +165,10 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     }
 
     choosePath(url: URL) {
-        if (url.protocol === "data" || url.href.indexOf(window.location.origin) === -1)
+        const lower = url.href.toLowerCase();
+        if (url.protocol === "data" || url.href.indexOf(window.location.origin) === -1 || !(lower.endsWith(".png") || lower.endsWith(".jpg") || lower.endsWith(".jpeg"))) {
             return url.href;
+        }
         let ext = path.extname(url.href);
         return url.href.replace(ext, this._curSuffix + ext);
     }
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 8cb576786..849f17aa4 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -78,7 +78,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
 
         let rows: JSX.Element[] = [];
         let i = 0;
-        for (let key in ids) {
+        for (let key of Object.keys(ids).sort()) {
             rows.push(<KeyValuePair doc={realDoc} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} key={key} keyName={key} />);
         }
         return rows;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 5b8b15ed8..733bc920f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -23,6 +23,7 @@ import { pageSchema } from "./ImageBox";
 import "./PDFBox.scss";
 var path = require('path');
 import React = require("react");
+import { ContextMenu } from "../ContextMenu";
 
 /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
  * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, 
@@ -357,7 +358,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     @computed
     get imageProxyRenderer() {
         let thumbField = this.props.Document.thumbnail;
-        if (thumbField && this._renderAsSvg) {
+        if (thumbField && this._renderAsSvg && NumCast(this.props.Document.thumbnailPage, 0) === this.Document.curPage) {
 
             // let transform = this.props.ScreenToLocalTransform().inverse();
             let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
@@ -380,10 +381,21 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     }
     @action onKeyDown = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = true);
     @action onKeyUp = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = false);
+    onContextMenu = (e: React.MouseEvent): void => {
+        let field = Cast(this.Document[this.props.fieldKey], PdfField);
+        if (field) {
+            let url = field.url.href;
+            ContextMenu.Instance.addItem({
+                description: "Copy path", event: () => {
+                    Utils.CopyText(url);
+                }, icon: "expand-arrows-alt"
+            });
+        }
+    }
     render() {
         let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
         return (
-            <div className={classname} tabIndex={0} ref={this._mainDiv} onPointerDown={this.onPointerDown} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} >
+            <div className={classname} tabIndex={0} ref={this._mainDiv} onPointerDown={this.onPointerDown} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} onContextMenu={this.onContextMenu} >
                 {this.pdfRenderer}
             </div >
         );
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 6ae55d151..ab57b4b04 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -88,10 +88,14 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         if (vref) {
             vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
             if (this._reactionDisposer) this._reactionDisposer();
-            this._reactionDisposer = reaction(() => this.props.Document.curPage, () =>
-                vref.currentTime = NumCast(this.props.Document.curPage, 0), { fireImmediately: true });
+            this._reactionDisposer = reaction(() => this.props.Document.curPage, () => {
+                if (!this.Playing) {
+                    vref.currentTime = NumCast(this.props.Document.curPage, 0);
+                }
+            }, { fireImmediately: true });
         }
     }
+
     videoContent(path: string) {
         let style = "videoBox-cont" + (this._fullScreen ? "-fullScreen" : "");
         return <video className={`${style}`} ref={this.setVideoRef} onPointerDown={this.onPointerDown}>
-- 
cgit v1.2.3-70-g09d2


From 435f0c8ef035995001dde92f8e7a04fe35a3a41d Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 28 May 2019 20:00:50 -0400
Subject: Added default values for Document Schemas

---
 src/client/views/nodes/DocumentView.tsx | 13 +++++++------
 src/debug/Repl.tsx                      | 11 ++++++++---
 src/new_fields/Schema.ts                | 18 +++++++++++++++---
 src/new_fields/Types.ts                 | 12 +++++++++---
 4 files changed, 39 insertions(+), 15 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 01c4d82fb..2fb794925 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
-import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { createSchema, makeInterface, defaultSpec } from "../../../new_fields/Schema";
 import { BoolCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { emptyFunction, Utils } from "../../../Utils";
@@ -60,15 +60,15 @@ const LinkDoc = makeInterface(linkSchema);
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     Document: Doc;
-    addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean;
-    removeDocument?: (doc: Document) => boolean;
-    moveDocument?: (doc: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+    addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
+    removeDocument?: (doc: Doc) => boolean;
+    moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
     isTopMost: boolean;
     ContentScaling: () => number;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    focus: (doc: Document) => void;
+    focus: (doc: Doc) => void;
     selectOnLoad: boolean;
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
@@ -81,7 +81,8 @@ const schema = createSchema({
     layout: "string",
     nativeWidth: "number",
     nativeHeight: "number",
-    backgroundColor: "string"
+    backgroundColor: "string",
+    test: defaultSpec("number", 5)
 });
 
 export const positionSchema = createSchema({
diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx
index 01acb0e76..c2db3bdcb 100644
--- a/src/debug/Repl.tsx
+++ b/src/debug/Repl.tsx
@@ -3,6 +3,7 @@ import * as ReactDOM from 'react-dom';
 import { observer } from 'mobx-react';
 import { observable, computed } from 'mobx';
 import { CompileScript } from '../client/util/Scripting';
+import { makeInterface } from '../new_fields/Schema';
 
 @observer
 class Repl extends React.Component {
@@ -15,12 +16,16 @@ class Repl extends React.Component {
     }
 
     onKeyDown = (e: React.KeyboardEvent) => {
-        if (e.ctrlKey && e.key === "Enter") {
-            const script = CompileScript(this.text, { addReturn: true, typecheck: false });
+        if (!e.ctrlKey && e.key === "Enter") {
+            e.preventDefault();
+            const script = CompileScript(this.text, {
+                addReturn: true, typecheck: false,
+                params: { makeInterface: "any" }
+            });
             if (!script.compiled) {
                 this.executedCommands.push({ command: this.text, result: "Compile Error" });
             } else {
-                const result = script.run();
+                const result = script.run({ makeInterface });
                 if (result.success) {
                     this.executedCommands.push({ command: this.text, result: result.result });
                 } else {
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
index b821baec9..250f3c975 100644
--- a/src/new_fields/Schema.ts
+++ b/src/new_fields/Schema.ts
@@ -1,4 +1,4 @@
-import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType } from "./Types";
+import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from "./Types";
 import { Doc, Field } from "./Doc";
 
 type AllToInterface<T extends Interface[]> = {
@@ -10,7 +10,7 @@ export const emptySchema = createSchema({});
 export const Document = makeInterface(emptySchema);
 export type Document = makeInterface<[typeof emptySchema]>;
 
-export type makeInterface<T extends Interface[]> = Partial<AllToInterface<T>> & Doc & { proto: Doc | undefined };
+export type makeInterface<T extends Interface[]> = AllToInterface<T> & Doc & { proto: Doc | undefined };
 // export function makeInterface<T extends Interface[], U extends Doc>(schemas: T): (doc: U) => All<T, U>;
 // export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U>; 
 export function makeInterface<T extends Interface[]>(...schemas: T): (doc?: Doc) => makeInterface<T> {
@@ -24,7 +24,12 @@ export function makeInterface<T extends Interface[]>(...schemas: T): (doc?: Doc)
         get(target: any, prop, receiver) {
             const field = receiver.doc[prop];
             if (prop in schema) {
-                return Cast(field, (schema as any)[prop]);
+                const desc = (schema as any)[prop];
+                if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) {
+                    return Cast(field, desc.type, desc.defaultVal);
+                } else {
+                    return Cast(field, (schema as any)[prop]);
+                }
             }
             return field;
         },
@@ -79,4 +84,11 @@ export function createSchema<T extends Interface>(schema: T): T & { proto: ToCon
 
 export function listSpec<U extends ToConstructor<Field>>(type: U): ListSpec<ToType<U>> {
     return { List: type as any };//TODO Types
+}
+
+export function defaultSpec<T extends ToConstructor<Field>>(type: T, defaultVal: ToType<T>): DefaultFieldConstructor<ToType<T>> {
+    return {
+        type: type as any,
+        defaultVal
+    };
 }
\ No newline at end of file
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index 4b4c58eb8..c04dd5e6d 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -2,12 +2,13 @@ import { Field, Opt, FieldResult, Doc } from "./Doc";
 import { List } from "./List";
 import { RefField } from "./RefField";
 
-export type ToType<T extends ToConstructor<Field> | ListSpec<Field>> =
+export type ToType<T extends ToConstructor<Field> | ListSpec<Field> | DefaultFieldConstructor<Field>> =
     T extends "string" ? string :
     T extends "number" ? number :
     T extends "boolean" ? boolean :
     T extends ListSpec<infer U> ? List<U> :
     // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+    T extends DefaultFieldConstructor<infer _U> ? never :
     T extends { new(...args: any[]): List<Field> } ? never :
     T extends { new(...args: any[]): infer R } ? R : never;
 
@@ -19,12 +20,17 @@ export type ToConstructor<T extends Field> =
     new (...args: any[]) => T;
 
 export type ToInterface<T extends Interface> = {
-    [P in Exclude<keyof T, "proto">]: FieldResult<ToType<T[P]>>;
+    [P in Exclude<keyof T, "proto">]: T[P] extends DefaultFieldConstructor<infer F> ? Exclude<FieldResult<F>, undefined> : FieldResult<ToType<T[P]>>;
 };
 
 // type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
 export type ListSpec<T extends Field> = { List: ToConstructor<T> };
 
+export type DefaultFieldConstructor<T extends Field> = {
+    type: ToConstructor<T>,
+    defaultVal: T
+};
+
 // type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
 
 export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
@@ -34,7 +40,7 @@ export type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
 
 //TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
 export interface Interface {
-    [key: string]: ToConstructor<Field> | ListSpec<Field>;
+    [key: string]: ToConstructor<Field> | ListSpec<Field> | DefaultFieldConstructor<Field>;
     // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
 }
 
-- 
cgit v1.2.3-70-g09d2


From 732cc8c3aec072525535d246b1177181bbd3f7da Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 29 May 2019 12:06:44 -0400
Subject: improved stacking view and captions templates.

---
 src/client/documents/Documents.ts                  |   6 +-
 src/client/views/MainOverlayTextBox.tsx            |   7 +-
 src/client/views/Templates.tsx                     |   4 +-
 .../views/collections/CollectionStackingView.tsx   | 125 ++++++++++++---------
 src/client/views/collections/CollectionSubView.tsx |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   6 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   1 +
 src/client/views/nodes/DocumentView.tsx            |   5 +
 src/client/views/nodes/FormattedTextBox.tsx        |  21 +++-
 9 files changed, 107 insertions(+), 70 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4bc0df31f..9bf62196f 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -26,7 +26,7 @@ import { OmitKeys } from "../../Utils";
 import { ImageField, VideoField, AudioField, PdfField, WebField } from "../../new_fields/URLField";
 import { HtmlField } from "../../new_fields/HtmlField";
 import { List } from "../../new_fields/List";
-import { Cast } from "../../new_fields/Types";
+import { Cast, NumCast } from "../../new_fields/Types";
 import { IconField } from "../../new_fields/IconField";
 import { listSpec } from "../../new_fields/Schema";
 import { DocServer } from "../DocServer";
@@ -221,10 +221,12 @@ export namespace Docs {
         let inst = CreateInstance(imageProto, new ImageField(new URL(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!) * size.height / 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;
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 9be408049..6e8e6f8ce 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -17,6 +17,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     @observable _textXf: () => Transform = () => Transform.Identity();
     private _textFieldKey: string = "data";
     private _textColor: string | null = null;
+    private _textHideOnLeave?: boolean;
     private _textTargetDiv: HTMLDivElement | undefined;
     private _textProxyDiv: React.RefObject<HTMLDivElement>;
 
@@ -39,9 +40,9 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         this._textFieldKey = textFieldKey!;
         this._textXf = tx ? tx : () => Transform.Identity();
         this._textTargetDiv = div;
+        this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave;
         if (div) {
-            if (div.parentElement && div.parentElement instanceof HTMLDivElement && div.parentElement.id === "screenSpace") this._textXf = () => Transform.Identity();
-            this._textColor = div.style.color;
+            this._textColor = (getComputedStyle(div) as any).color;
             div.style.color = "transparent";
         }
     }
@@ -88,7 +89,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
             return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${textRect.top}px) scale(${1 / s},${1 / s})`, width: "auto", height: "auto" }} >
                 <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
                     style={{ width: `${textRect.width * s}px`, height: `${textRect.height * s}px` }}>
-                    <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
+                    <FormattedTextBox fieldKey={this._textFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
                         selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
                         ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={emptyFunction} />
                 </div>
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 303f3f3b8..0cd367bcb 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -42,8 +42,8 @@ export namespace Templates {
     export const Caption = new Template("Caption", TemplatePosition.OutterBottom,
         `<div>
             <div style="height:100%; width:100%;position:absolute;">{layout}</div>
-            <div id="screenSpace" style="top: 100%; font-size:14px; background:yellow; width:100%; position:absolute">
-                <FormattedTextBox {...props} fieldKey={"caption"} />
+            <div style="bottom: 0; font-size:14px; width:100%; position:absolute">
+                <FormattedTextBox {...props} fieldKey={"caption"} hideOnLeave={"true"} />
             </div>
         </div>` );
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index a29648d5b..425eecebb 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,9 +4,9 @@ import { CollectionSubView, CollectionViewProps, SubCollectionViewProps } from "
 import { Doc, WidthSym, HeightSym, DocListCast } from "../../../new_fields/Doc";
 import { DocumentView } from "../nodes/DocumentView";
 import { Transform } from "../../util/Transform";
-import { emptyFunction, returnOne } from "../../../Utils";
+import { emptyFunction, returnOne, Utils } from "../../../Utils";
 import "./CollectionStackingView.scss";
-import { action, reaction } from "mobx";
+import { action, reaction, trace, computed } from "mobx";
 import { StrCast, NumCast } from "../../../new_fields/Types";
 import { Id } from "../../../new_fields/FieldSymbols";
 
@@ -14,20 +14,13 @@ import { Id } from "../../../new_fields/FieldSymbols";
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
-    getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform();
+
+    get gridGap() { return 10; }
+    get gridSize() { return 20; }
+    get itemWidth() { return NumCast(this.props.Document.itemWidth, 250); }
 
     constructor(props: SubCollectionViewProps) {
         super(props);
-        // reaction(() => [this.props.PanelHeight() + this.props.PanelWidth(),
-        // (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document[this.props.ContainingCollectionView.props.fieldKey])], () => {
-        //         if (this.props.ContainingCollectionView) {
-        //             let allItems = DocListCast(this.props.ContainingCollectionView.props.Document[this.props.ContainingCollectionView.props.fieldKey]);
-        //             for (let x = 0; x < allItems.length; x++) {
-        //                 resizeGridItem(allItems[x]);
-        //             }
-        //         }
-        //     }
-        // );
     }
 
     @action
@@ -36,57 +29,79 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         addDocument(doc);
         return true;
     }
+    getDocTransform(doc: Doc, dref: HTMLDivElement) {
+        let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
+        let outerXf = Utils.GetScreenTransform(this.masonryGridRef!);
+        let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+        return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.itemWidth);
+    }
+    masonryGridRef: HTMLDivElement | null = null;
+    createRef = (ele: HTMLDivElement | null) => {
+        this.masonryGridRef = ele;
+        this.createDropTarget(ele!);
+    }
+    @computed
+    get children() {
+        return this.childDocs.map(d => {
+            let colSpan = Math.ceil((this.itemWidth + this.gridGap) / (this.gridSize + this.gridGap));
+            let rowSpan = Math.ceil((this.itemWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap));
+            let dref = React.createRef<HTMLDivElement>();
+            let dxf = () => this.getDocTransform(d, dref.current!);
+            return (<div className="colletionStackingView-masonryDoc"
+                key={d[Id]}
+                ref={dref}
+                style={{
+                    transformOrigin: "top left",
+                    gridRowEnd: `span ${rowSpan}`,
+                    gridColumnEnd: `span ${colSpan}`,
+                    transform: `scale(${this.itemWidth / NumCast(d.nativeWidth, 1)}, ${this.itemWidth / NumCast(d.nativeWidth, 1)})`
+                }} >
+                <DocumentView key={d[Id]} Document={d}
+                    addDocument={this.props.addDocument}
+                    removeDocument={this.props.removeDocument}
+                    moveDocument={this.moveDocument}
+                    ContainingCollectionView={this.props.CollectionView}
+                    isTopMost={false}
+                    ScreenToLocalTransform={dxf}
+                    focus={emptyFunction}
+                    ContentScaling={returnOne}
+                    PanelWidth={d[WidthSym]}
+                    PanelHeight={d[HeightSym]}
+                    selectOnLoad={false}
+                    parentActive={this.props.active}
+                    addDocTab={this.props.addDocTab}
+                    bringToFront={emptyFunction}
+                    toggleMinimized={emptyFunction}
+                    whenActiveChanged={this.props.whenActiveChanged} />
+            </div>);
+        })
+    }
+    onClick = (e: React.MouseEvent) => {
+        if (this.props.active()) {
+            let rect = (this.masonryGridRef!.firstChild! as HTMLElement).getBoundingClientRect();
+            if (e.clientX < rect.left || e.clientX > rect.right || e.clientY > rect.bottom) this.props.select(false);
+            e.stopPropagation();
+        }
+    }
     render() {
-        const docs = this.childDocs;
-        let gridGap = 10;
-        let gridSize = 20;
-        let itemWidth = NumCast(this.props.Document.itemWidth, 250);
         let leftMargin = 20;
         let topMargin = 20;
-        let itemCols = Math.ceil(itemWidth / (gridSize + gridGap));
-        let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (gridSize + gridGap)));
+        let itemCols = Math.ceil(this.itemWidth / (this.gridSize + this.gridGap));
+        let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (this.gridSize + this.gridGap)));
         return (
-            <div className="collectionStackingView" ref={this.createDropTarget} onWheel={(e: React.WheelEvent) => e.stopPropagation()}>
+            <div className="collectionStackingView" ref={this.createRef} onClick={this.onClick} onWheel={(e: React.WheelEvent) => e.stopPropagation()}>
                 <div className="collectionStackingview-masonryGrid"
                     style={{
                         padding: `${topMargin}px 0px 0px ${leftMargin}px`,
-                        width: `${cells * itemCols * (gridSize + gridGap) + leftMargin}`,
-                        margin: "auto", position: "relative",
-                        gridGap: gridGap,
-                        gridTemplateColumns: `repeat(auto-fill, minmax(${gridSize}px,1fr))`,
-                        gridAutoRows: `${gridSize}px`
+                        width: `${cells * itemCols * (this.gridSize + this.gridGap) + leftMargin}`,
+                        height: "auto",
+                        marginLeft: "auto", marginRight: "auto", position: "relative",
+                        gridGap: this.gridGap,
+                        gridTemplateColumns: `repeat(auto-fill, minmax(${this.gridSize}px,1fr))`,
+                        gridAutoRows: `${this.gridSize}px`
                     }}
                 >
-                    {docs.map(d => {
-                        let colSpan = Math.ceil((itemWidth + gridGap) / (gridSize + gridGap));
-                        let rowSpan = Math.ceil((itemWidth / d[WidthSym]() * d[HeightSym]() + gridGap) / (gridSize + gridGap));
-                        return (<div className="mycontent" id={StrCast(d.title, "")}
-                            key={d[Id]}
-                            style={{
-                                transformOrigin: "top left",
-                                gridRowEnd: `span  ${rowSpan}`,
-                                gridColumnEnd: `span  ${colSpan}`,
-                                transform: `scale(${itemWidth / NumCast(d.nativeWidth, 1)}, ${itemWidth / NumCast(d.nativeWidth, 1)})`
-                            }} >
-                            <DocumentView Document={d}
-                                addDocument={this.props.addDocument}
-                                removeDocument={this.props.removeDocument}
-                                moveDocument={this.moveDocument}
-                                ContainingCollectionView={this.props.CollectionView}
-                                isTopMost={false}
-                                ScreenToLocalTransform={this.getPreviewTransform}
-                                focus={emptyFunction}
-                                ContentScaling={returnOne}
-                                PanelWidth={d[WidthSym]}
-                                PanelHeight={d[HeightSym]}
-                                selectOnLoad={false}
-                                parentActive={this.props.active}
-                                addDocTab={this.props.addDocTab}
-                                bringToFront={emptyFunction}
-                                toggleMinimized={emptyFunction}
-                                whenActiveChanged={this.props.active} />
-                        </div>);
-                    })}
+                    {this.children}
                 </div>
             </div>
         );
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index c4e72b23a..fe9e12640 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -212,7 +212,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     }).then(async (res: Response) => {
                         (await res.json()).map(action((file: any) => {
                             let path = window.location.origin + file;
-                            let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName });
+                            let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName });
 
                             docPromise.then(doc => doc && this.props.addDocument(doc));
                         }));
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 66d91f9c2..04e0a91f8 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -256,9 +256,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         }
     }
 
-    onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; };
-    onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
-
     borderRounding = () => {
         let br = NumCast(this.props.Document.borderRounding);
         return br >= 0 ? br :
@@ -283,8 +280,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
         return (
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
-                onPointerDown={this.onPointerDown}
-                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} onPointerOver={this.onPointerEnter}
+                onPointerDown={this.onPointerDown} onPointerOver={this.onPointerEnter}
                 onClick={this.onClick}
                 style={{
                     outlineColor: "maroon",
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 52a9582d2..02396c3af 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -44,6 +44,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     isSelected: () => boolean,
     select: (ctrl: boolean) => void,
     layoutKey: string,
+    hideOnLeave?: boolean
 }> {
     @computed get layout(): string {
         const layout = Cast(this.props.Document[this.props.layoutKey], "string");
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 01c4d82fb..ed5862660 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -343,6 +343,9 @@ 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; };
+
     isSelected = () => SelectionManager.IsSelected(this);
     select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed);
 
@@ -366,6 +369,8 @@ 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}
             </div>
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 5afef221c..5b74c2e7e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -50,8 +50,9 @@ library.add(faSmile);
 //  this will edit the document and assign the new value to that field.
 //]
 
-export interface FormattedTextBoxOverlay {
+export interface FormattedTextBoxProps {
     isOverlay?: boolean;
+    hideOnLeave?: boolean;
 }
 
 const richTextSchema = createSchema({
@@ -62,7 +63,7 @@ type RichTextDocument = makeInterface<[typeof richTextSchema]>;
 const RichTextDocument = makeInterface(richTextSchema);
 
 @observer
-export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxOverlay), RichTextDocument>(RichTextDocument) {
+export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
     public static LayoutString(fieldStr: string = "data") {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
@@ -357,6 +358,17 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             this._undoTyping = UndoManager.StartBatch("undoTyping");
         }
     }
+
+    @observable
+    _entered = false;
+    @action
+    onPointerEnter = (e: React.PointerEvent) => {
+        this._entered = true;
+    }
+    @action
+    onPointerLeave = (e: React.PointerEvent) => {
+        this._entered = false;
+    }
     render() {
         let style = this.props.isOverlay ? "scroll" : "hidden";
         let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : "";
@@ -364,6 +376,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         return (
             <div className={`formattedTextBox-cont-${style}`} ref={this._ref}
                 style={{
+                    background: this.props.hideOnLeave ? "rgba(0,0,0,0.4)" : undefined,
+                    opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || this.props.Document.libraryBrush ? 1 : 0.1) : 1,
+                    color: this.props.hideOnLeave ? "white" : "initial",
                     pointerEvents: interactive ? "all" : "none",
                 }}
                 // onKeyDown={this.onKeyPress}
@@ -377,6 +392,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 onContextMenu={this.specificContextMenu}
                 // tfs: do we need this event handler
                 onWheel={this.onPointerWheel}
+                onPointerEnter={this.onPointerEnter}
+                onPointerLeave={this.onPointerLeave}
             >
                 <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} ref={this._proseRef} />
             </div>
-- 
cgit v1.2.3-70-g09d2


From 9dbf61fccc96f7c4d6bd63e25a7208b82df28705 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 29 May 2019 18:21:03 -0400
Subject: stacking view focus fixes.  linkview fixes.-

---
 src/client/documents/Documents.ts                      |  3 ++-
 .../views/collections/CollectionStackingView.scss      |  2 +-
 .../views/collections/CollectionStackingView.tsx       | 10 ++++++++--
 src/client/views/collections/CollectionView.tsx        | 13 +++++++++++++
 .../collectionFreeForm/CollectionFreeFormLinksView.tsx | 18 +++++++++++-------
 .../collections/collectionFreeForm/MarqueeView.tsx     |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx     |  9 +--------
 src/client/views/nodes/DocumentView.tsx                |  6 ++++++
 src/client/views/nodes/FormattedTextBox.tsx            |  1 -
 9 files changed, 43 insertions(+), 21 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9bf62196f..fffada459 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -36,6 +36,7 @@ import { DateField } from "../../new_fields/DateField";
 import { UndoManager } from "../util/UndoManager";
 import { RouteStore } from "../../server/RouteStore";
 var requestImageSize = require('request-image-size');
+var path = require('path');
 
 export interface DocumentOptions {
     x?: number;
@@ -218,7 +219,7 @@ export namespace Docs {
     }
 
     export function ImageDocument(url: string, options: DocumentOptions = {}) {
-        let inst = CreateInstance(imageProto, new ImageField(new URL(url)), options);
+        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;
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 6043a813d..0e4a8bb7e 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -8,7 +8,7 @@
     width: 100%;
     height: 100%;
     position: absolute;
-    overflow-y: scroll;
+    overflow-y: auto;
     min-width: 250px;
     border-width: 0;
     box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 425eecebb..6d44aa37d 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -47,14 +47,20 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             let rowSpan = Math.ceil((this.itemWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap));
             let dref = React.createRef<HTMLDivElement>();
             let dxf = () => this.getDocTransform(d, dref.current!);
+            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="colletionStackingView-masonryDoc"
                 key={d[Id]}
                 ref={dref}
                 style={{
+                    width: NumCast(d.nativeWidth, d[WidthSym]()),
+                    height: NumCast(d.nativeHeight, d[HeightSym]()),
                     transformOrigin: "top left",
                     gridRowEnd: `span ${rowSpan}`,
                     gridColumnEnd: `span ${colSpan}`,
-                    transform: `scale(${this.itemWidth / NumCast(d.nativeWidth, 1)}, ${this.itemWidth / NumCast(d.nativeWidth, 1)})`
+                    transform: `scale(${this.itemWidth / NumCast(d.nativeWidth, d[WidthSym]())}, ${this.itemWidth / NumCast(d.nativeWidth, d[WidthSym]())})`
                 }} >
                 <DocumentView key={d[Id]} Document={d}
                     addDocument={this.props.addDocument}
@@ -63,7 +69,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     ContainingCollectionView={this.props.CollectionView}
                     isTopMost={false}
                     ScreenToLocalTransform={dxf}
-                    focus={emptyFunction}
+                    focus={childFocus}
                     ContentScaling={returnOne}
                     PanelWidth={d[WidthSym]}
                     PanelHeight={d[HeightSym]}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 39c8af4ab..a1e7c7c69 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -13,6 +13,8 @@ import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionTreeView } from "./CollectionTreeView";
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionStackingView } from './CollectionStackingView';
+import { NumCast } from '../../../new_fields/Types';
+import { WidthSym, HeightSym } from '../../../new_fields/Doc';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
@@ -42,6 +44,16 @@ export class CollectionView extends React.Component<FieldViewProps> {
 
     get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; } // bcz: ? Why do we need to compare Id's?
 
+    freezeNativeDimensions = (e: React.MouseEvent): void => {
+        if (NumCast(this.props.Document.nativeWidth)) {
+            this.props.Document.proto!.nativeWidth = undefined;
+            this.props.Document.proto!.nativeHeight = undefined;
+
+        } else {
+            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
+            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
+        }
+    }
     onContextMenu = (e: React.MouseEvent): void => {
         if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "signature" });
@@ -51,6 +63,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
             ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" });
             ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
             ContextMenu.Instance.addItem({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
+            ContextMenu.Instance.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" });
         }
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 85556fb90..6207dc8af 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -95,22 +95,26 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
 
     @computed
     get uniqueConnections() {
+        console.log("-----");
         let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
             let srcViews = this.documentAnchors(connection.a);
             let targetViews = this.documentAnchors(connection.b);
             let possiblePairs: { a: Doc, b: Doc, }[] = [];
             srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document })));
-            possiblePairs.map(possiblePair =>
-                drawnPairs.reduce((found, drawnPair) => {
-                    let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b);
+            possiblePairs.map(possiblePair => {
+                if (!drawnPairs.reduce((found, drawnPair) => {
+                    let match1 = (Doc.AreProtosEqual(possiblePair.a, drawnPair.a) && Doc.AreProtosEqual(possiblePair.b, drawnPair.b));
+                    let match2 = (Doc.AreProtosEqual(possiblePair.a, drawnPair.b) && Doc.AreProtosEqual(possiblePair.b, drawnPair.a));
+                    let match = match1 || match2;
                     if (match && !drawnPair.l.reduce((found, link) => found || link[Id] === connection.l[Id], false)) {
                         drawnPair.l.push(connection.l);
                     }
                     return match || found;
-                }, false)
-                ||
-                drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })
-            );
+                }, false)) {
+                    console.log("A" + possiblePair.a[Id] + " B" + possiblePair.b[Id] + " L" + connection.l[Id]);
+                    drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })
+                }
+            });
             return drawnPairs;
         }, [] as { a: Doc, b: Doc, l: Doc[] }[]);
         return connections.map(c => {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 64d5301c9..14dbe1899 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -87,7 +87,7 @@ 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, { width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer
+                    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
                 } else {
                     this.pasteTable(ns, x, y);
                 }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 04e0a91f8..5f4d9e9ec 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -280,16 +280,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
         return (
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
-                onPointerDown={this.onPointerDown} onPointerOver={this.onPointerEnter}
+                onPointerDown={this.onPointerDown}
                 onClick={this.onClick}
                 style={{
-                    outlineColor: "maroon",
-                    outlineStyle: "dashed",
-                    outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ||
-                        BoolCast(this.props.Document.protoBrush, false) ?
-                        `${1 * this.getTransform().Scale}px`
-                        // "2px"
-                        : "0px",
                     opacity: zoomFade,
                     borderRadius: `${this.borderRounding()}px`,
                     transformOrigin: "left top",
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ed5862660..98f4f050a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -362,6 +362,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
                 style={{
+                    outlineColor: "maroon",
+                    outlineStyle: "dashed",
+                    outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ||
+                        BoolCast(this.props.Document.protoBrush, false) ?
+                        `${1 * this.props.ScreenToLocalTransform().Scale}px`
+                        : "0px",
                     borderRadius: "inherit",
                     background: this.Document.backgroundColor || "",
                     width: nativeWidth,
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 5b74c2e7e..da584c811 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -275,7 +275,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-    //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
     freezeNativeDimensions = (e: React.MouseEvent): void => {
         if (NumCast(this.props.Document.nativeWidth)) {
             this.props.Document.proto!.nativeWidth = undefined;
-- 
cgit v1.2.3-70-g09d2


From 83edfcd06b659839f161121728de02aca91d4af8 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 30 May 2019 00:20:35 -0400
Subject: added image rotation.  added double click on stacking views.  need to
 cleanup/fix DocumentView click events

---
 .../views/collections/CollectionStackingView.scss   |  2 +-
 .../views/collections/CollectionStackingView.tsx    | 15 +++++++++++----
 .../views/nodes/CollectionFreeFormDocumentView.tsx  | 21 +--------------------
 src/client/views/nodes/DocumentView.tsx             | 12 +++++++++++-
 src/client/views/nodes/ImageBox.tsx                 | 19 +++++++++++++++++--
 5 files changed, 41 insertions(+), 28 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 0e4a8bb7e..4d240342c 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -31,7 +31,7 @@
         align-items: center;
     }
 
-    .collectionStackingview-masonryGrid {
+    .collectionStackingView-masonryGrid {
         display:grid;
         width:100%;
         height:100%;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6d44aa37d..943e8dd5f 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -83,10 +83,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         })
     }
     onClick = (e: React.MouseEvent) => {
+        let hitBackground = (e.target as any).className === "collectionStackingView-masonryGrid" ||
+            (e.target as any).className === "collectionStackingView";
         if (this.props.active()) {
-            let rect = (this.masonryGridRef!.firstChild! as HTMLElement).getBoundingClientRect();
-            if (e.clientX < rect.left || e.clientX > rect.right || e.clientY > rect.bottom) this.props.select(false);
-            e.stopPropagation();
+            if (!hitBackground)
+                e.stopPropagation();
+        }
+        if (hitBackground) {
+            if (!this.props.active() && this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.active()) {
+                e.stopPropagation();
+            }
+            this.props.select(false);
         }
     }
     render() {
@@ -96,7 +103,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (this.gridSize + this.gridGap)));
         return (
             <div className="collectionStackingView" ref={this.createRef} onClick={this.onClick} onWheel={(e: React.WheelEvent) => e.stopPropagation()}>
-                <div className="collectionStackingview-masonryGrid"
+                <div className="collectionStackingView-masonryGrid"
                     style={{
                         padding: `${topMargin}px 0px 0px ${leftMargin}px`,
                         width: `${cells * itemCols * (this.gridSize + this.gridGap) + leftMargin}`,
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 5f4d9e9ec..5d11e051d 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -33,7 +33,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     private _downX: number = 0;
     private _downY: number = 0;
     private _doubleTap = false;
-    private _lastTap: number = 0;
     _bringToFrontDisposer?: IReactionDisposer;
 
     @computed get transform() {
@@ -169,31 +168,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         if (e.button === 0 && e.altKey) {
             e.stopPropagation(); // prevents panning from happening on collection if shift is pressed after a document drag has started
         } // allow pointer down to go through otherwise so that marquees can be drawn starting over a document 
-        if (Date.now() - this._lastTap < 300) {
-            if (e.buttons === 1) {
-                document.removeEventListener("pointerup", this.onPointerUp);
-                document.addEventListener("pointerup", this.onPointerUp);
-            }
-        } else {
-            this._lastTap = Date.now();
-        }
+
         if (e.button === 0) {
             e.preventDefault();  // prevents Firefox from dragging images (we want to do that ourself)
         }
     }
-    onPointerUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointerup", this.onPointerUp);
-        if (Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) {
-            this._doubleTap = true;
-        }
-    }
     onClick = async (e: React.MouseEvent) => {
         e.stopPropagation();
-        if (this._doubleTap) {
-            this.props.addDocTab(this.props.Document, "inTab");
-            SelectionManager.DeselectAll();
-            this.props.Document.libraryBrush = false;
-        }
         let altKey = e.altKey;
         let ctrlKey = e.ctrlKey;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 98f4f050a..36c14fbf2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -183,13 +183,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
+    _doubleTap = false;
     onClick = (e: React.MouseEvent): void => {
-        if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
+        if (this._doubleTap && !this.props.isTopMost) {
+            this.props.addDocTab(this.props.Document, "inTab");
+            SelectionManager.DeselectAll();
+            this.props.Document.libraryBrush = false;
+            e.stopPropagation();
+        }
+        else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
                 Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             SelectionManager.SelectDoc(this, e.ctrlKey);
         }
     }
+    private _lastTap: number = 0;
     _hitExpander = false;
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
@@ -222,6 +230,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     onPointerUp = (e: PointerEvent): void => {
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
+        this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
+        this._lastTap = Date.now();
     }
 
     deleteClicked = (): void => {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d70068295..8ea6c5436 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -12,11 +12,11 @@ import React = require("react");
 import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';
 import { DocComponent } from '../DocComponent';
 import { positionSchema } from './DocumentView';
-import { FieldValue, Cast, StrCast, PromiseValue } from '../../../new_fields/Types';
+import { FieldValue, Cast, StrCast, PromiseValue, NumCast } from '../../../new_fields/Types';
 import { ImageField } from '../../../new_fields/URLField';
 import { List } from '../../../new_fields/List';
 import { InkingControl } from '../InkingControl';
-import { Doc } from '../../../new_fields/Doc';
+import { Doc, WidthSym, HeightSym } from '../../../new_fields/Doc';
 import { faImage } from '@fortawesome/free-solid-svg-icons';
 import { library } from '@fortawesome/fontawesome-svg-core';
 var path = require('path');
@@ -136,6 +136,17 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                     Utils.CopyText(url);
                 }, icon: "expand-arrows-alt"
             });
+            ContextMenu.Instance.addItem({
+                description: "Rotate", event: action(() => {
+                    this.props.Document.rotation = (NumCast(this.props.Document.rotation) + 90) % 360;
+                    let nw = this.props.Document.nativeWidth;
+                    this.props.Document.nativeWidth = this.props.Document.nativeHeight;
+                    this.props.Document.nativeHeight = nw;
+                    let w = this.props.Document.width;
+                    this.props.Document.width = this.props.Document.height;
+                    this.props.Document.height = w;
+                }), icon: "expand-arrows-alt"
+            });
         }
     }
 
@@ -199,6 +210,9 @@ 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 shift = (rotation % 180) ? (this.props.Document[HeightSym]() - this.props.Document[WidthSym]() / aspect) / 2 : 0;
         return (
             <div id={id} className={`imageBox-cont${interactive}`}
                 onPointerDown={this.onPointerDown}
@@ -206,6 +220,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                 <img id={id}
                     key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
                     src={paths[Math.min(paths.length, this._photoIndex)]}
+                    style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }}
                     // style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }}
                     width={nativeWidth}
                     ref={this._imgRef}
-- 
cgit v1.2.3-70-g09d2


From 37e16bb59a38c000ba80312a0661e1f54c93f3c6 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 30 May 2019 10:51:00 -0400
Subject: cleaned up event handling for documentviews.   fixed context menu
 closing.

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/views/ContextMenu.tsx                   |   7 +-
 src/client/views/ContextMenuItem.tsx               |  34 +++--
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/MainView.tsx                      |   1 -
 .../views/collections/CollectionBaseView.tsx       |   1 +
 .../views/collections/CollectionDockingView.tsx    |   1 -
 .../views/collections/CollectionSchemaView.tsx     |   1 -
 .../views/collections/CollectionStackingView.tsx   |  28 +----
 .../views/collections/CollectionTreeView.tsx       |  22 ++--
 .../CollectionFreeFormLinksView.tsx                |   1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 -
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 119 ------------------
 src/client/views/nodes/DocumentView.tsx            | 140 ++++++++++++++++-----
 src/client/views/nodes/FieldView.tsx               |   1 -
 15 files changed, 153 insertions(+), 208 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index fffada459..ab61b915c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -295,7 +295,7 @@ export namespace Docs {
         return CreateInstance(webProto, new HtmlField(html), options);
     }
     export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
-        return CreateInstance(kvpProto, document, options);
+        return CreateInstance(kvpProto, document, { title: document.title + ".kvp", ...options });
     }
     export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, makePrototype: boolean = true) {
         if (!makePrototype) {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 542d259d6..da374455e 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -81,6 +81,11 @@ export class ContextMenu extends React.Component {
         return false;
     }
 
+    @action
+    closeMenu = () => {
+        this.clearItems();
+    }
+
     render() {
         let style = this._yRelativeToTop ? { left: this._pageX, top: this._pageY, display: this._display } :
             { left: this._pageX, bottom: this._pageY, display: this._display };
@@ -95,7 +100,7 @@ export class ContextMenu extends React.Component {
                     <input className="contextMenu-item contextMenu-description" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange} />
                 </span>
                 {this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1).
-                    map(prop => <ContextMenuItem {...prop} key={prop.description} />)}
+                    map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.closeMenu} />)}
             </div>
         );
     }
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index b9239dac9..63347e076 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -8,11 +8,13 @@ export interface OriginalMenuProps {
     description: string;
     event: (e: React.MouseEvent<HTMLDivElement>) => void;
     icon?: IconProp; //maybe should be optional (icon?)
+    closeMenu?: () => void;
 }
 
 export interface SubmenuProps {
     description: string;
     subitems: ContextMenuProps[];
+    closeMenu?: () => void;
 }
 
 export interface ContextMenuItemProps {
@@ -32,30 +34,36 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
         }
     }
 
+    handleEvent = (e: React.MouseEvent<HTMLDivElement>) => {
+        if ("event" in this.props) {
+            this.props.event(e);
+            this.props.closeMenu && this.props.closeMenu();
+        }
+    }
+
     render() {
         if ("event" in this.props) {
             return (
-                <div className="contextMenu-item" onClick={this.props.event}>
+                <div className="contextMenu-item" onClick={this.handleEvent}>
                     <span className="icon-background">
                         {this.props.icon ? <FontAwesomeIcon icon={this.props.icon} size="sm" /> : <FontAwesomeIcon icon="circle" size="sm" />}
                     </span>
-                    <div className="contextMenu-description"> {this.props.description}</div>
+                    <div className="contextMenu-description">
+                        {this.props.description}
+                    </div>
                 </div>
             );
         }
         else {
-            let submenu = null;
-            if (this.overItem) {
-                submenu = (
-                    <div className="subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
-                        {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} />)}
-                    </div>
-                );
-            }
+            let submenu = !this.overItem ? (null) :
+                <div className="subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
+                    {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
+                </div>;
             return (
-                <div className="contextMenu-item" onMouseEnter={action(() => { this.overItem = true; })}
-                    onMouseLeave={action(() => this.overItem = false)}>
-                    <div className="contextMenu-description"> {this.props.description}</div>
+                <div className="contextMenu-item" onMouseEnter={action(() => { this.overItem = true; })} onMouseLeave={action(() => this.overItem = false)}>
+                    <div className="contextMenu-description">
+                        {this.props.description}
+                    </div>
                     {submenu}
                 </div>
             );
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 319e91337..e06fd6119 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -251,7 +251,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             }
             if (!this._removeIcon) {
                 if (selectedDocs.length === 1) {
-                    this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
+                    this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].toggleMinimized());
                 } else if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD &&
                     Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
                     let docViews = SelectionManager.ViewsSortedVertically();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 438607157..74dafadc4 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -184,7 +184,6 @@ export class MainView extends React.Component {
         let mainCont = this.mainContainer;
         let content = !mainCont ? (null) :
             <DocumentView Document={mainCont}
-                toggleMinimized={emptyFunction}
                 addDocument={undefined}
                 addDocTab={emptyFunction}
                 removeDocument={undefined}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 2bccde241..e89b8be0f 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -128,6 +128,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     @action.bound
     removeDocument(doc: Doc): boolean {
+        SelectionManager.DeselectAll();
         const props = this.props;
         //TODO This won't create the field if it doesn't already exist
         const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 180a8be46..b6076b5f7 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -466,7 +466,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             <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}
-                    toggleMinimized={emptyFunction}
                     bringToFront={emptyFunction}
                     addDocument={undefined}
                     removeDocument={undefined}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 1af13765b..b17d5b865 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -412,7 +412,6 @@ class CollectionSchemaPreview extends React.Component<CollectionSchemaPreviewPro
             {!this.props.Document || !this.props.width ? (null) : (
                 <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.centeringOffset}px, 0px)` }}>
                     <DocumentView Document={this.props.Document} isTopMost={false} selectOnLoad={false}
-                        toggleMinimized={emptyFunction}
                         addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
                         ScreenToLocalTransform={this.getTransform}
                         ContentScaling={this.contentScaling}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 943e8dd5f..bf246d4ec 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -14,15 +14,11 @@ import { Id } from "../../../new_fields/FieldSymbols";
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
-
+    _masonryGridRef: HTMLDivElement | null = null;
     get gridGap() { return 10; }
     get gridSize() { return 20; }
     get itemWidth() { return NumCast(this.props.Document.itemWidth, 250); }
 
-    constructor(props: SubCollectionViewProps) {
-        super(props);
-    }
-
     @action
     moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => {
         this.props.removeDocument(doc);
@@ -31,13 +27,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     getDocTransform(doc: Doc, dref: HTMLDivElement) {
         let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
-        let outerXf = Utils.GetScreenTransform(this.masonryGridRef!);
+        let outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
         let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
         return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.itemWidth);
     }
-    masonryGridRef: HTMLDivElement | null = null;
     createRef = (ele: HTMLDivElement | null) => {
-        this.masonryGridRef = ele;
+        this._masonryGridRef = ele;
         this.createDropTarget(ele!);
     }
     @computed
@@ -77,32 +72,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     parentActive={this.props.active}
                     addDocTab={this.props.addDocTab}
                     bringToFront={emptyFunction}
-                    toggleMinimized={emptyFunction}
                     whenActiveChanged={this.props.whenActiveChanged} />
             </div>);
         })
     }
-    onClick = (e: React.MouseEvent) => {
-        let hitBackground = (e.target as any).className === "collectionStackingView-masonryGrid" ||
-            (e.target as any).className === "collectionStackingView";
-        if (this.props.active()) {
-            if (!hitBackground)
-                e.stopPropagation();
-        }
-        if (hitBackground) {
-            if (!this.props.active() && this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.active()) {
-                e.stopPropagation();
-            }
-            this.props.select(false);
-        }
-    }
     render() {
         let leftMargin = 20;
         let topMargin = 20;
         let itemCols = Math.ceil(this.itemWidth / (this.gridSize + this.gridGap));
         let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (this.gridSize + this.gridGap)));
         return (
-            <div className="collectionStackingView" ref={this.createRef} onClick={this.onClick} onWheel={(e: React.WheelEvent) => e.stopPropagation()}>
+            <div className="collectionStackingView" ref={this.createRef} onWheel={(e: React.WheelEvent) => e.stopPropagation()}>
                 <div className="collectionStackingView-masonryGrid"
                     style={{
                         padding: `${topMargin}px 0px 0px ${leftMargin}px`,
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8ad495762..85aab96d3 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -26,6 +26,7 @@ export interface TreeViewProps {
     deleteDoc: (doc: Doc) => void;
     moveDocument: DragManager.MoveFunction;
     dropAction: "alias" | "copy" | undefined;
+    addDocTab: (doc: Doc, where: string) => void;
 }
 
 export enum BulletType {
@@ -140,19 +141,12 @@ class TreeView extends React.Component<TreeViewProps> {
     onWorkspaceContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.props.document)) });
-            ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.document) });
-            ContextMenu.Instance.addItem({
-                description: "Open Fields", event: () => CollectionDockingView.Instance.AddRightSplit(Docs.KVPDocument(this.props.document,
-                    { title: this.props.document.title + ".kvp", width: 300, height: 300 }))
-            });
+            ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight") });
+            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight") });
             if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
                 ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
             }
-            ContextMenu.Instance.addItem({
-                description: "Delete", event: undoBatch(() => {
-                    this.props.deleteDoc(this.props.document);
-                })
-            });
+            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
             ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
             e.stopPropagation();
         }
@@ -180,7 +174,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         {(key === "data") ? (null) :
                             <span className="collectionTreeView-keyHeader" style={{ display: "block", marginTop: "7px" }} key={key}>{key}</span>}
                         <div style={{ display: "block", marginTop: `${spacing}px` }}>
-                            {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction)}
+                            {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction, this.props.addDocTab)}
                         </div>
                     </ul >);
                 } else {
@@ -197,9 +191,9 @@ class TreeView extends React.Component<TreeViewProps> {
             </li>
         </div>;
     }
-    public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
+    public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => void) {
         return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).map(child =>
-            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
+            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} addDocTab={addDocTab} />);
     }
 }
 
@@ -225,7 +219,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         if (!this.childDocs) {
             return (null);
         }
-        let childElements = TreeView.GetChildElements(this.childDocs, false, this.remove, this.props.moveDocument, dropAction);
+        let childElements = TreeView.GetChildElements(this.childDocs, false, this.remove, this.props.moveDocument, dropAction, this.props.addDocTab);
 
         return (
             <div id="body" className="collectionTreeView-dropTarget"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 6207dc8af..a43c5f241 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -95,7 +95,6 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
 
     @computed
     get uniqueConnections() {
-        console.log("-----");
         let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
             let srcViews = this.documentAnchors(connection.a);
             let targetViews = this.documentAnchors(connection.b);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d1c4fb72d..1a1614ac7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -279,7 +279,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     getDocumentViewProps(document: Doc): DocumentViewProps {
         return {
             Document: document,
-            toggleMinimized: emptyFunction,
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 5d11e051d..411e8c059 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -30,9 +30,6 @@ const FreeformDocument = makeInterface(schema, positionSchema);
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) {
     private _mainCont = React.createRef<HTMLDivElement>();
-    private _downX: number = 0;
-    private _downY: number = 0;
-    private _doubleTap = false;
     _bringToFrontDisposer?: IReactionDisposer;
 
     @computed get transform() {
@@ -62,7 +59,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
     panelWidth = () => this.props.PanelWidth();
     panelHeight = () => this.props.PanelHeight();
-    toggleMinimized = async () => this.toggleIcon(await DocListCastAsync(this.props.Document.maximizedDocs));
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
         .scale(1 / this.contentScaling()).scale(1 / this.zoom)
@@ -70,7 +66,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @computed
     get docView() {
         return <DocumentView {...OmitKeys(this.props, ['zoomFade']).omit}
-            toggleMinimized={this.toggleMinimized}
             ContentScaling={this.contentScaling}
             ScreenToLocalTransform={this.getTransform}
             PanelWidth={this.panelWidth}
@@ -124,118 +119,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         },
             2);
     }
-    @action
-    public toggleIcon = async (maximizedDocs: Doc[] | undefined): Promise<void> => {
-        SelectionManager.DeselectAll();
-        let isMinimized: boolean | undefined;
-        let minimizedDoc: Doc | undefined = this.props.Document;
-        if (!maximizedDocs) {
-            minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
-            if (minimizedDoc) maximizedDocs = await DocListCastAsync(minimizedDoc.maximizedDocs);
-        }
-        if (minimizedDoc && maximizedDocs) {
-            let minimizedTarget = minimizedDoc;
-            if (!CollectionFreeFormDocumentView._undoBatch) {
-                CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
-            }
-            maximizedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
-                let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
-                if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
-                    if (isMinimized === undefined) {
-                        isMinimized = BoolCast(maximizedDoc.isMinimized, false);
-                    }
-                    let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) / 2;
-                    let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) / 2;
-                    if (minx !== undefined && miny !== undefined) {
-                        let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny);
-                        maximizedDoc.willMaximize = isMinimized;
-                        maximizedDoc.isMinimized = false;
-                        maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
-                    }
-                }
-            });
-            setTimeout(() => {
-                CollectionFreeFormDocumentView._undoBatch && CollectionFreeFormDocumentView._undoBatch.end();
-                CollectionFreeFormDocumentView._undoBatch = undefined;
-            }, 500);
-        }
-    }
-    static _undoBatch?: UndoManager.Batch = undefined;
-    onPointerDown = (e: React.PointerEvent): void => {
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        this._doubleTap = false;
-        if (e.button === 0 && e.altKey) {
-            e.stopPropagation(); // prevents panning from happening on collection if shift is pressed after a document drag has started
-        } // allow pointer down to go through otherwise so that marquees can be drawn starting over a document 
-
-        if (e.button === 0) {
-            e.preventDefault();  // prevents Firefox from dragging images (we want to do that ourself)
-        }
-    }
-    onClick = async (e: React.MouseEvent) => {
-        e.stopPropagation();
-        let altKey = e.altKey;
-        let ctrlKey = e.ctrlKey;
-        if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
-            Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            let isExpander = (e.target as any).id === "isExpander";
-            if (BoolCast(this.props.Document.isButton, false) || isExpander) {
-                SelectionManager.DeselectAll();
-                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 expandedDocs: Doc[] = [];
-                expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
-                expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
-                expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
-                // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),];
-                if (expandedDocs.length) {   // bcz: need a better way to associate behaviors with click events on widget-documents
-                    let expandedProtoDocs = expandedDocs.map(doc => Doc.GetProto(doc));
-                    let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace");
-                    let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target);
-                    if (altKey) {
-                        maxLocation = this.props.Document.maximizeLocation = (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace");
-                        if (!maxLocation || maxLocation === "inPlace") {
-                            let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView);
-                            let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized, false), false);
-                            expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false);
-                            let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView);
-                            if (!hasView) {
-                                this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(getDispDoc(maxDoc), false));
-                            }
-                            expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized);
-                        }
-                    }
-                    if (maxLocation && maxLocation !== "inPlace") {
-                        let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
-                        if (dataDocs) {
-                            expandedDocs.forEach(maxDoc =>
-                                (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) &&
-                                    this.props.addDocTab(getDispDoc(maxDoc), maxLocation)));
-                        }
-                    } else {
-                        this.toggleIcon(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]);
-                    }
-                }
-            }
-        }
-    }
 
     borderRounding = () => {
         let br = NumCast(this.props.Document.borderRounding);
@@ -261,8 +144,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
         return (
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
-                onPointerDown={this.onPointerDown}
-                onClick={this.onClick}
                 style={{
                     opacity: zoomFade,
                     borderRadius: `${this.borderRounding()}px`,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 36c14fbf2..a16a52ac6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -2,11 +2,11 @@ import { library } from '@fortawesome/fontawesome-svg-core';
 import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash, faConciergeBell, faFolder, faMapPin, faLink, faFingerprint, faCrosshairs, faDesktop } from '@fortawesome/free-solid-svg-icons';
 import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, StrCast, NumCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { emptyFunction, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
@@ -16,7 +16,7 @@ import { DragManager, dropActionType } from "../../util/DragManager";
 import { SearchUtil } from "../../util/SearchUtil";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
@@ -72,9 +72,9 @@ export interface DocumentViewProps {
     selectOnLoad: boolean;
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
-    toggleMinimized: () => void;
     bringToFront: (doc: Doc) => void;
     addDocTab: (doc: Doc, where: string) => void;
+    whenClicked?: () => void;
 }
 
 const schema = createSchema({
@@ -103,6 +103,9 @@ const Document = makeInterface(schema);
 export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
     private _downX: number = 0;
     private _downY: number = 0;
+    private _lastTap: number = 0;
+    private _doubleTap = false;
+    private _hitExpander = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
 
@@ -182,23 +185,112 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
     }
+    toggleMinimized = async () => {
+        let minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
+        if (minimizedDoc) {
+            let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(
+                NumCast(minimizedDoc.x) - NumCast(this.Document.x), NumCast(minimizedDoc.y) - NumCast(this.Document.y));
+            this.collapseToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs));
+        }
+    }
 
-    _doubleTap = false;
-    onClick = (e: React.MouseEvent): void => {
+    static _undoBatch?: UndoManager.Batch = undefined;
+    @action
+    public collapseToPoint = async (scrpt: number[], expandedDocs: Doc[] | undefined): Promise<void> => {
+        SelectionManager.DeselectAll();
+        if (expandedDocs) {
+            if (!DocumentView._undoBatch) {
+                DocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
+            }
+            let isMinimized: boolean | undefined;
+            expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
+                let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
+                if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
+                    if (isMinimized === undefined) {
+                        isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+                    }
+                    maximizedDoc.willMaximize = isMinimized;
+                    maximizedDoc.isMinimized = false;
+                    maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
+                }
+            });
+            setTimeout(() => {
+                DocumentView._undoBatch && DocumentView._undoBatch.end();
+                DocumentView._undoBatch = undefined;
+            }, 500);
+        }
+    }
+    onClick = async (e: React.MouseEvent) => {
+        e.stopPropagation();
+        let altKey = e.altKey;
+        let ctrlKey = e.ctrlKey;
         if (this._doubleTap && !this.props.isTopMost) {
             this.props.addDocTab(this.props.Document, "inTab");
             SelectionManager.DeselectAll();
             this.props.Document.libraryBrush = false;
-            e.stopPropagation();
         }
         else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
                 Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             SelectionManager.SelectDoc(this, e.ctrlKey);
+            let isExpander = (e.target as any).id === "isExpander";
+            if (BoolCast(this.props.Document.isButton, false) || isExpander) {
+                SelectionManager.DeselectAll();
+                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 expandedDocs: Doc[] = [];
+                expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
+                expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
+                expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
+                // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),];
+                if (expandedDocs.length) {   // bcz: need a better way to associate behaviors with click events on widget-documents
+                    let expandedProtoDocs = expandedDocs.map(doc => Doc.GetProto(doc));
+                    let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace");
+                    let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target);
+                    if (altKey) {
+                        maxLocation = this.props.Document.maximizeLocation = (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace");
+                        if (!maxLocation || maxLocation === "inPlace") {
+                            let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView);
+                            let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized, false), false);
+                            expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false);
+                            let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView);
+                            if (!hasView) {
+                                this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(getDispDoc(maxDoc), false));
+                            }
+                            expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized);
+                        }
+                    }
+                    if (maxLocation && maxLocation !== "inPlace") {
+                        let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
+                        if (dataDocs) {
+                            expandedDocs.forEach(maxDoc =>
+                                (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) &&
+                                    this.props.addDocTab(getDispDoc(maxDoc), maxLocation)));
+                        }
+                    } else {
+                        let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2);
+                        this.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]);
+                    }
+                }
+            }
         }
     }
-    private _lastTap: number = 0;
-    _hitExpander = false;
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
         this._downY = e.clientY;
@@ -206,8 +298,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (e.shiftKey && e.buttons === 1) {
             CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
             e.stopPropagation();
-        } else if (this.active) {
-            //e.stopPropagation(); // bcz: doing this will block click events from CollectionFreeFormDocumentView which are needed for iconifying,etc
+        } 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);
@@ -215,7 +307,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
     onPointerMove = (e: PointerEvent): void => {
-        if (!e.cancelBubble) {
+        if (!e.cancelBubble && this.active) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
                 document.removeEventListener("pointermove", this.onPointerMove);
                 document.removeEventListener("pointerup", this.onPointerUp);
@@ -234,32 +326,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._lastTap = Date.now();
     }
 
-    deleteClicked = (): void => {
-        this.props.removeDocument && this.props.removeDocument(this.props.Document);
-    }
-    fieldsClicked = (e: React.MouseEvent): void => {
-        let kvp = Docs.KVPDocument(this.props.Document, { title: this.props.Document.title + ".kvp", width: 300, height: 300 });
-        CollectionDockingView.Instance.AddRightSplit(kvp);
-    }
-    makeButton = (e: React.MouseEvent): void => {
-        let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+    deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); }
+    fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.Document, { width: 300, height: 300 }), "onRight") };
+    makeBtnClicked = (): void => {
+        let doc = Doc.GetProto(this.props.Document);
         doc.isButton = !BoolCast(doc.isButton, false);
         if (StrCast(doc.layout).indexOf("Formatted") !== -1) { // only need to freeze the dimensions of text boxes since they don't have a native width and height naturally
             if (doc.isButton && !doc.nativeWidth) {
                 doc.nativeWidth = this.props.Document[WidthSym]();
                 doc.nativeHeight = this.props.Document[HeightSym]();
             } else {
-
                 doc.nativeWidth = doc.nativeHeight = undefined;
             }
         }
     }
-    fullScreenClicked = (e: React.MouseEvent): void => {
-        const doc = Doc.MakeCopy(this.props.Document, false);
-        if (doc) {
-            CollectionDockingView.Instance.OpenFullScreen(doc);
-        }
-        ContextMenu.Instance.clearItems();
+    fullScreenClicked = (): void => {
+        CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false));
         SelectionManager.DeselectAll();
     }
 
@@ -332,7 +414,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" });
         cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" });
         cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
-        cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton, icon: "concierge-bell" });
+        cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" });
         cm.addItem({
             description: "Find aliases", event: async () => {
                 const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index d3d765eed..7b642b299 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -102,7 +102,6 @@ export class FieldView extends React.Component<FieldViewProps> {
                     layoutKey={"layout"}
                     ContainingCollectionView={this.props.ContainingCollectionView}
                     parentActive={this.props.active}
-                    toggleMinimized={emptyFunction}
                     whenActiveChanged={this.props.whenActiveChanged}
                     bringToFront={emptyFunction} />
             );
-- 
cgit v1.2.3-70-g09d2


From 890a40ef8cc2efa7beea5722301b59ede5141238 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 30 May 2019 13:47:53 -0400
Subject: fixed context menus and tree view jitter.

---
 src/client/util/DragManager.ts                     |  4 +--
 src/client/util/TooltipTextMenu.tsx                |  2 +-
 src/client/views/ContextMenu.scss                  |  2 +-
 src/client/views/ContextMenuItem.tsx               |  2 +-
 src/client/views/MainView.tsx                      |  6 ++--
 .../views/collections/CollectionDockingView.tsx    |  6 +++-
 .../views/collections/CollectionTreeView.scss      |  6 +++-
 .../views/collections/CollectionTreeView.tsx       | 33 +++++++++++++---------
 src/client/views/collections/CollectionView.tsx    | 30 +++++++-------------
 src/client/views/nodes/DocumentView.tsx            | 31 ++++++++++++++------
 src/client/views/nodes/FormattedTextBox.tsx        | 25 +---------------
 src/client/views/nodes/ImageBox.tsx                | 11 ++++----
 .../authentication/models/current_user_utils.ts    | 20 ++++++-------
 13 files changed, 85 insertions(+), 93 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 7f75a95f0..8f0cce095 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -26,7 +26,7 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
         // if (this.props.isSelected() || this.props.isTopMost) {
         if (e.button === 0) {
             e.stopPropagation();
-            if (e.shiftKey) {
+            if (e.shiftKey && CollectionDockingView.Instance) {
                 CollectionDockingView.Instance.StartOtherDrag([await docFunc()], e);
             } else {
                 document.addEventListener("pointermove", onRowMove);
@@ -264,7 +264,7 @@ export namespace DragManager {
             if (dragData instanceof DocumentDragData) {
                 dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
             }
-            if (e.shiftKey) {
+            if (e.shiftKey && CollectionDockingView.Instance) {
                 AbortDrag();
                 CollectionDockingView.Instance.StartOtherDrag(docs, {
                     pageX: e.pageX,
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index a1f80120f..5dd10f1bf 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 CollectionDockingView.Instance.AddRightSplit(f);
+                                else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f);
                             }
                         }));
                     }
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 61ae69179..74acba615 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -22,7 +22,7 @@
     color: $light-color;
 }
 
-.subMenu-cont {
+.contextMenu-subMenu-cont {
     position: absolute;
     display: flex;
     z-index: 1000;
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 63347e076..fcda0db89 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -56,7 +56,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
         }
         else {
             let submenu = !this.overItem ? (null) :
-                <div className="subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
+                <div className="contextMenu-subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
                     {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
                 </div>;
             return (
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 74dafadc4..a093ffdec 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -104,7 +104,9 @@ export class MainView extends React.Component {
         }, false); // drag event handler
         // click interactions for the context menu
         document.addEventListener("pointerdown", action(function (e: PointerEvent) {
-            if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
+
+            const targets = document.elementsFromPoint(e.x, e.y);
+            if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) {
                 ContextMenu.Instance.clearItems();
             }
         }), true);
@@ -163,7 +165,7 @@ export class MainView extends React.Component {
     }
 
     openNotifsCol = () => {
-        if (this._notifsCol) {
+        if (this._notifsCol && CollectionDockingView.Instance) {
             CollectionDockingView.Instance.AddRightSplit(this._notifsCol);
         }
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index b6076b5f7..dcc1bd95d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -22,6 +22,7 @@ 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';
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -415,7 +416,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
     get _stack(): any {
-        return (this.props as any).glContainer.parent.parent;
+        let parent = (this.props as any).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);
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 2f0329fc4..bb3be0a73 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -48,7 +48,11 @@
         .treeViewItem-openRight {
             display: inline-block;
             // display: inline;
-            transform: translate(0px, -3px); //TODO Fix this
+            svg {
+                display:block;
+                padding:0px;
+                margin: 0px;
+            }
         }
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 85aab96d3..2814c0502 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -9,7 +9,7 @@ import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import React = require("react");
 import { Document, listSpec } from '../../../new_fields/Schema';
-import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types';
+import { Cast, StrCast, BoolCast, FieldValue, NumCast } from '../../../new_fields/Types';
 import { Doc, DocListCast } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { ContextMenu } from '../ContextMenu';
@@ -19,6 +19,7 @@ import { CollectionDockingView } from './CollectionDockingView';
 import { DocumentManager } from '../../util/DocumentManager';
 import { Docs } from '../../documents/Documents';
 import { MainView } from '../MainView';
+import { CollectionViewType } from './CollectionBaseView';
 
 
 export interface TreeViewProps {
@@ -54,7 +55,7 @@ class TreeView extends React.Component<TreeViewProps> {
         if (this.props.document.dockingConfig) {
             MainView.Instance.openWorkspace(this.props.document);
         } else {
-            CollectionDockingView.Instance.AddRightSplit(this.props.document);
+            this.props.addDocTab(this.props.document, "openRight");
         }
     }
 
@@ -122,11 +123,11 @@ class TreeView extends React.Component<TreeViewProps> {
                     return true;
                 }}
             />);
-        let dataDocs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []);
+        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}>
                 <FontAwesomeIcon icon="angle-right" size="lg" />
-                <FontAwesomeIcon icon="angle-right" size="lg" />
+                {/* <FontAwesomeIcon icon="angle-right" size="lg" /> */}
             </div>);
         return (
             <div className="docContainer" ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
@@ -139,14 +140,19 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     onWorkspaceContextMenu = (e: React.MouseEvent): void => {
-        if (!e.isPropagationStopped() && this.props.document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+        if (!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 Right", event: () => this.props.addDocTab(this.props.document, "onRight") });
-            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight") });
-            if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
-                ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
+            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.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" });
+                if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
+                    ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
+                }
+                ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
+            } else {
+                ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
             }
-            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
             ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
             e.stopPropagation();
         }
@@ -207,11 +213,10 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         }
     }
     onContextMenu = (e: React.MouseEvent): void => {
-        if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+        // 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
             ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => MainView.Instance.createNewWorkspace()) });
-        }
-        if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) {
-            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.remove(this.props.Document)) });
+            ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)) });
         }
     }
     render() {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a1e7c7c69..68eefab4c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,20 +1,19 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faProjectDiagram, faSquare, faTh, faTree, faSignature, faThList } from '@fortawesome/free-solid-svg-icons';
+import { faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
 import { observer } from "mobx-react";
 import * as React from 'react';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from '../ContextMenuItem';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from './CollectionBaseView';
 import { CollectionDockingView } from "./CollectionDockingView";
-import { CollectionSchemaView } from "./CollectionSchemaView";
-import { CollectionTreeView } from "./CollectionTreeView";
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
-import { NumCast } from '../../../new_fields/Types';
-import { WidthSym, HeightSym } from '../../../new_fields/Doc';
+import { CollectionTreeView } from "./CollectionTreeView";
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
@@ -44,26 +43,17 @@ export class CollectionView extends React.Component<FieldViewProps> {
 
     get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; } // bcz: ? Why do we need to compare Id's?
 
-    freezeNativeDimensions = (e: React.MouseEvent): void => {
-        if (NumCast(this.props.Document.nativeWidth)) {
-            this.props.Document.proto!.nativeWidth = undefined;
-            this.props.Document.proto!.nativeHeight = undefined;
-
-        } else {
-            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
-            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
-        }
-    }
     onContextMenu = (e: React.MouseEvent): void => {
         if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-            ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "signature" });
+            let subItems: ContextMenuProps[] = [];
+            subItems.push({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "signature" });
             if (CollectionBaseView.InSafeMode()) {
                 ContextMenu.Instance.addItem({ description: "Test Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Invalid), icon: "project-diagram" });
             }
-            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" });
-            ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
-            ContextMenu.Instance.addItem({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
-            ContextMenu.Instance.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" });
+            subItems.push({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" });
+            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 });
         }
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a16a52ac6..69691b6f3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -29,6 +29,7 @@ import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
 import { Id, Copy } from '../../../new_fields/FieldSymbols';
+import { ContextMenuProps } from '../ContextMenuItem';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(faTrash);
@@ -263,7 +264,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                             expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized);
                         }
                     }
-                    if (maxLocation && maxLocation !== "inPlace") {
+                    if (maxLocation && maxLocation !== "inPlace" && CollectionDockingView.Instance) {
                         let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
                         if (dataDocs) {
                             expandedDocs.forEach(maxDoc =>
@@ -295,7 +296,7 @@ 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) {
+        if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) {
             CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
             e.stopPropagation();
         } else {
@@ -341,7 +342,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
     fullScreenClicked = (): void => {
-        CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false));
+        CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false));
         SelectionManager.DeselectAll();
     }
 
@@ -398,6 +399,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.templates = this.templates;
     }
 
+    freezeNativeDimensions = (e: React.MouseEvent): void => {
+        if (NumCast(this.props.Document.nativeWidth)) {
+            this.props.Document.proto!.nativeWidth = undefined;
+            this.props.Document.proto!.nativeHeight = undefined;
+
+        } else {
+            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
+            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
+        }
+    }
+
     @action
     onContextMenu = (e: React.MouseEvent): void => {
         e.stopPropagation();
@@ -409,16 +421,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         e.preventDefault();
 
         const cm = ContextMenu.Instance;
-        cm.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "desktop" });
-        cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" });
-        cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" });
-        cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" });
+        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 Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "onRight"), icon: "caret-square-right" });
+        subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
+        cm.addItem({ description: "Open...", subitems: subitems });
+        cm.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" });
         cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
         cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" });
         cm.addItem({
             description: "Find aliases", event: async () => {
                 const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
-                CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(["title"], aliases, {}));
+                this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), "onRight");
             }, icon: "search"
         });
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document), icon: "crosshairs" });
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index da584c811..5d93edaac 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -240,7 +240,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                             if (DocumentManager.Instance.getDocumentView(f)) {
                                 DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
                             } else {
-                                CollectionDockingView.Instance.AddRightSplit(f);
+                                this.props.addDocTab && this.props.addDocTab(f, "onRight");
                             }
                         }
                     }));
@@ -274,29 +274,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         }
     }
-
-    freezeNativeDimensions = (e: React.MouseEvent): void => {
-        if (NumCast(this.props.Document.nativeWidth)) {
-            this.props.Document.proto!.nativeWidth = undefined;
-            this.props.Document.proto!.nativeHeight = undefined;
-
-        } else {
-            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
-            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
-        }
-    }
-    specificContextMenu = (e: React.MouseEvent): void => {
-        if (!this._gotDown) {
-            e.preventDefault();
-            return;
-        }
-        ContextMenu.Instance.addItem({
-            description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze",
-            event: this.freezeNativeDimensions,
-            icon: "edit"
-        });
-    }
-
     onPointerWheel = (e: React.WheelEvent): void => {
         if (this.props.isSelected()) {
             e.stopPropagation();
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 8ea6c5436..4c2b73b70 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -19,6 +19,7 @@ import { InkingControl } from '../InkingControl';
 import { Doc, WidthSym, HeightSym } from '../../../new_fields/Doc';
 import { faImage } from '@fortawesome/free-solid-svg-icons';
 import { library } from '@fortawesome/fontawesome-svg-core';
+import { ContextMenuItemProps, ContextMenuProps } from '../ContextMenuItem';
 var path = require('path');
 
 
@@ -131,12 +132,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         let field = Cast(this.Document[this.props.fieldKey], ImageField);
         if (field) {
             let url = field.url.href;
-            ContextMenu.Instance.addItem({
-                description: "Copy path", event: () => {
-                    Utils.CopyText(url);
-                }, icon: "expand-arrows-alt"
-            });
-            ContextMenu.Instance.addItem({
+            let subitems: ContextMenuProps[] = [];
+            subitems.push({ description: "Copy path", event: () => Utils.CopyText(url), icon: "expand-arrows-alt" });
+            subitems.push({
                 description: "Rotate", event: action(() => {
                     this.props.Document.rotation = (NumCast(this.props.Document.rotation) + 90) % 360;
                     let nw = this.props.Document.nativeWidth;
@@ -147,6 +145,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                     this.props.Document.height = w;
                 }), icon: "expand-arrows-alt"
             });
+            ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: subitems });
         }
     }
 
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index add347a88..e5b7a025b 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -1,21 +1,17 @@
-import { computed, observable, action, runInAction } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
 import * as rp from 'request-promise';
+import { DocServer } from "../../../client/DocServer";
 import { Docs } from "../../../client/documents/Documents";
-import { Attribute, AttributeGroup, Catalog, Schema, AggregateFunction } from "../../../client/northstar/model/idea/idea";
+import { Gateway, NorthstarSettings } from "../../../client/northstar/manager/Gateway";
+import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/northstar/model/idea/idea";
 import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
-import { RouteStore } from "../../RouteStore";
-import { DocServer } from "../../../client/DocServer";
-import { Doc, Opt, Field } from "../../../new_fields/Doc";
-import { List } from "../../../new_fields/List";
 import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView";
-import { CollectionTreeView } from "../../../client/views/collections/CollectionTreeView";
 import { CollectionView } from "../../../client/views/collections/CollectionView";
-import { NorthstarSettings, Gateway } from "../../../client/northstar/manager/Gateway";
-import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
-import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
-import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
-import { Cast, PromiseValue } from "../../../new_fields/Types";
+import { Doc } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
+import { Cast } from "../../../new_fields/Types";
+import { RouteStore } from "../../RouteStore";
 
 export class CurrentUserUtils {
     private static curr_email: string;
-- 
cgit v1.2.3-70-g09d2


From e83c2a88303ae6d994a0ac2e84214947cac4d96d Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 31 May 2019 00:44:30 -0400
Subject: Added Open Tab Alias and Open Right Alias to context menu Cleaned up
 MakeAlias

---
 src/client/util/TooltipTextMenu.tsx     |  1 +
 src/client/views/nodes/DocumentView.tsx |  2 ++
 src/new_fields/Doc.ts                   | 13 ++-----------
 3 files changed, 5 insertions(+), 11 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 5dd10f1bf..f517f757a 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -198,6 +198,7 @@ export class TooltipTextMenu {
                             }
                         }));
                     }
+                    // TODO This should have an else to handle external links
                     e.stopPropagation();
                     e.preventDefault();
                 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c7c142c3b..4905f0ddc 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -424,7 +424,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         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 Fields", event: this.fieldsClicked, icon: "layer-group" });
         cm.addItem({ description: "Open...", subitems: subitems });
         cm.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" });
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 0c74b8f65..7f7263cf1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -213,19 +213,10 @@ export namespace Doc {
 
 
     export function MakeAlias(doc: Doc) {
-        const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : undefined;
         const alias = new Doc;
-
-        if (!proto) {
-            alias.proto = doc;
-        } else {
-            PromiseValue(Cast(doc.proto, Doc)).then(proto => {
-                if (proto) {
-                    alias.proto = proto;
-                }
-            });
+        if (!GetT(doc, "isPrototype", "boolean", true)) {
+            alias.proto = doc.proto;
         }
-
         return alias;
     }
 
-- 
cgit v1.2.3-70-g09d2


From d1b0f462ced168f3a9ac5d007ba1219e98a3b6cf Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 31 May 2019 18:18:05 -0400
Subject: stacking views and more..

---
 src/client/views/MainOverlayTextBox.tsx            | 11 +++++++--
 .../views/collections/CollectionBaseView.tsx       |  2 +-
 .../views/collections/CollectionStackingView.scss  |  2 +-
 .../views/collections/CollectionStackingView.tsx   | 28 +++++++++++++++-------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 13 ++++------
 6 files changed, 36 insertions(+), 22 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 6e8e6f8ce..24327b995 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -7,6 +7,8 @@ import { Transform } from '../util/Transform';
 import "normalize.css";
 import "./MainOverlayTextBox.scss";
 import { FormattedTextBox } from './nodes/FormattedTextBox';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { Doc } from '../../new_fields/Doc';
 
 interface MainOverlayTextBoxProps {
 }
@@ -82,6 +84,11 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         document.removeEventListener('pointerup', this.textBoxUp);
     }
 
+    addDocTab = (doc: Doc, location: string) => {
+        if (true) { // location === "onRight") { need to figure out stack to add "inTab"
+            CollectionDockingView.Instance.AddRightSplit(doc);
+        }
+    }
     render() {
         if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) {
             let textRect = this._textTargetDiv.getBoundingClientRect();
@@ -91,10 +98,10 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                     style={{ width: `${textRect.width * s}px`, height: `${textRect.height * s}px` }}>
                     <FormattedTextBox fieldKey={this._textFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
                         selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
-                        ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={emptyFunction} />
+                        ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} />
                 </div>
             </ div>;
         }
-        else return (null);
+        else return (null); Z
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index e89b8be0f..734669893 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -120,7 +120,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             // set the ZoomBasis only if hasn't already been set -- bcz: maybe set/resetting the ZoomBasis should be a parameter to addDocument?
             if (!alreadyAdded && (this.collectionViewType === CollectionViewType.Freeform || this.collectionViewType === CollectionViewType.Invalid)) {
                 let zoom = NumCast(this.props.Document.scale, 1);
-                Doc.SetOnPrototype(doc, "zoomBasis", zoom);
+                // Doc.GetProto(doc).zoomBasis = zoom;
             }
         }
         return true;
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 1bb0b2674..1ab335cc3 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -6,7 +6,7 @@
     display: flex;
     flex-direction: column;
     width: 100%;
-    height: 100%;
+    height: auto;
     position: absolute;
     overflow-y: auto;
     border-width: 0;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index bf246d4ec..a616b5ac7 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,24 +1,36 @@
 import React = require("react");
+import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
-import { CollectionSubView, CollectionViewProps, SubCollectionViewProps } from "./CollectionSubView";
-import { Doc, WidthSym, HeightSym, DocListCast } from "../../../new_fields/Doc";
-import { DocumentView } from "../nodes/DocumentView";
-import { Transform } from "../../util/Transform";
+import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { NumCast } from "../../../new_fields/Types";
 import { emptyFunction, returnOne, Utils } from "../../../Utils";
+import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionStackingView.scss";
-import { action, reaction, trace, computed } from "mobx";
-import { StrCast, NumCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/FieldSymbols";
+import { CollectionSubView } from "./CollectionSubView";
 
 
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
     _masonryGridRef: HTMLDivElement | null = null;
+    _heightDisposer?: IReactionDisposer;
     get gridGap() { return 10; }
     get gridSize() { return 20; }
     get itemWidth() { return NumCast(this.props.Document.itemWidth, 250); }
 
+    componentDidMount() {
+        this._heightDisposer = reaction(() => [this.childDocs.map(d => d[HeightSym]()), this.props.PanelWidth()],
+            () => {
+                let hgt = (this.props.PanelWidth() > 500) ? this.props.Document[HeightSym]() :
+                    this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => height + d[HeightSym]() + this.gridGap, this.gridGap + 20 /* top margin */);
+                this.props.Document.height = hgt;
+            }, { fireImmediately: true });
+    }
+    componentWillUnmount() {
+        if (this._heightDisposer) this._heightDisposer();
+    }
+
     @action
     moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => {
         this.props.removeDocument(doc);
@@ -37,7 +49,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     @computed
     get children() {
-        return this.childDocs.map(d => {
+        return this.childDocs.filter(d => !d.isMinimized).map(d => {
             let colSpan = Math.ceil((this.itemWidth + this.gridGap) / (this.gridSize + this.gridGap));
             let rowSpan = Math.ceil((this.itemWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap));
             let dref = React.createRef<HTMLDivElement>();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 69691b6f3..f3a596558 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -328,7 +328,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.Document, { width: 300, height: 300 }), "onRight") };
+    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);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 5d93edaac..3b61c68dd 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -230,20 +230,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 this._toolTipTextMenu.tooltip.style.opacity = "0";
             }
         }
+        let ctrlKey = e.ctrlKey;
         if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey) {
             if (e.target && (e.target as any).href) {
                 let href = (e.target as any).href;
                 if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
                     let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
-                    DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
-                        if (f instanceof Doc) {
-                            if (DocumentManager.Instance.getDocumentView(f)) {
-                                DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
-                            } else {
-                                this.props.addDocTab && this.props.addDocTab(f, "onRight");
-                            }
-                        }
-                    }));
+                    DocServer.GetRefField(docid).then(f => {
+                        (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab"))
+                    });
                 }
                 e.stopPropagation();
                 e.preventDefault();
-- 
cgit v1.2.3-70-g09d2


From 448996fbc234b82a27da952f85587a27cabe8881 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 1 Jun 2019 15:21:19 -0400
Subject: fixed stacking views to work with minimized documents

---
 .../views/collections/CollectionStackingView.tsx   | 30 +++++++++++++---
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 42 +++++++++++++++++-----
 src/client/views/nodes/DocumentView.tsx            | 30 ++--------------
 3 files changed, 62 insertions(+), 40 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index a616b5ac7..d06413154 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -3,11 +3,14 @@ import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
-import { NumCast } from "../../../new_fields/Types";
+import { NumCast, Cast, BoolCast } from "../../../new_fields/Types";
 import { emptyFunction, returnOne, Utils } from "../../../Utils";
 import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionStackingView.scss";
 import { CollectionSubView } from "./CollectionSubView";
+import { undoBatch } from "../../util/UndoManager";
+import { SelectionManager } from "../../util/SelectionManager";
+import { List } from "../../../new_fields/List";
 
 
 
@@ -20,10 +23,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     get itemWidth() { return NumCast(this.props.Document.itemWidth, 250); }
 
     componentDidMount() {
-        this._heightDisposer = reaction(() => [this.childDocs.map(d => d[HeightSym]()), this.props.PanelWidth()],
+        this._heightDisposer = reaction(() => [this.childDocs.map(d => [d[HeightSym](), d.isMinimized]), this.props.PanelWidth()],
             () => {
                 let hgt = (this.props.PanelWidth() > 500) ? this.props.Document[HeightSym]() :
-                    this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => height + d[HeightSym]() + this.gridGap, this.gridGap + 20 /* top margin */);
+                    this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => {
+                        let rowSpan = Math.ceil((this.itemWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap));
+                        return height + rowSpan * (this.gridSize + this.gridGap);
+                    }, 10);
                 this.props.Document.height = hgt;
             }, { fireImmediately: true });
     }
@@ -47,6 +53,20 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         this._masonryGridRef = ele;
         this.createDropTarget(ele!);
     }
+    @undoBatch
+    @action
+    public collapseToPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => {
+        SelectionManager.DeselectAll();
+        if (expandedDocs) {
+            let isMinimized: boolean | undefined;
+            expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
+                if (isMinimized === undefined) {
+                    isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+                }
+                maximizedDoc.isMinimized = !isMinimized;
+            });
+        }
+    }
     @computed
     get children() {
         return this.childDocs.filter(d => !d.isMinimized).map(d => {
@@ -84,7 +104,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     parentActive={this.props.active}
                     addDocTab={this.props.addDocTab}
                     bringToFront={emptyFunction}
-                    whenActiveChanged={this.props.whenActiveChanged} />
+                    whenActiveChanged={this.props.whenActiveChanged}
+                    collapseToPoint={this.collapseToPoint}
+                />
             </div>);
         })
     }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 411e8c059..499b83c0f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,19 +1,17 @@
-import { action, computed, IReactionDisposer, reaction, trace } from "mobx";
+import { computed, IReactionDisposer, reaction, action } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
+import { Doc } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
-import { OmitKeys, Utils } from "../../../Utils";
-import { DocumentManager } from "../../util/DocumentManager";
-import { SelectionManager } from "../../util/SelectionManager";
+import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { OmitKeys } from "../../../Utils";
 import { Transform } from "../../util/Transform";
-import { UndoManager } from "../../util/UndoManager";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { DocComponent } from "../DocComponent";
 import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
 import "./DocumentView.scss";
 import React = require("react");
+import { UndoManager } from "../../util/UndoManager";
+import { SelectionManager } from "../../util/SelectionManager";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 }
@@ -70,6 +68,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             ScreenToLocalTransform={this.getTransform}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
+            collapseToPoint={this.collapseToPoint}
         />;
     }
 
@@ -88,6 +87,33 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         if (this._bringToFrontDisposer) this._bringToFrontDisposer();
     }
 
+    static _undoBatch?: UndoManager.Batch = undefined;
+    @action
+    public collapseToPoint = async (scrpt: number[], expandedDocs: Doc[] | undefined): Promise<void> => {
+        SelectionManager.DeselectAll();
+        if (expandedDocs) {
+            if (!CollectionFreeFormDocumentView._undoBatch) {
+                CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
+            }
+            let isMinimized: boolean | undefined;
+            expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
+                let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
+                if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
+                    if (isMinimized === undefined) {
+                        isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+                    }
+                    maximizedDoc.willMaximize = isMinimized;
+                    maximizedDoc.isMinimized = false;
+                    maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
+                }
+            });
+            setTimeout(() => {
+                CollectionFreeFormDocumentView._undoBatch && CollectionFreeFormDocumentView._undoBatch.end();
+                CollectionFreeFormDocumentView._undoBatch = undefined;
+            }, 500);
+        }
+    }
+
     animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, maximizing: boolean) {
 
         setTimeout(() => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index be40efbfa..80c1292ae 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -75,7 +75,7 @@ export interface DocumentViewProps {
     whenActiveChanged: (isActive: boolean) => void;
     bringToFront: (doc: Doc) => void;
     addDocTab: (doc: Doc, where: string) => void;
-    whenClicked?: () => void;
+    collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
 }
 
 const schema = createSchema({
@@ -195,32 +195,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    static _undoBatch?: UndoManager.Batch = undefined;
-    @action
-    public collapseToPoint = async (scrpt: number[], expandedDocs: Doc[] | undefined): Promise<void> => {
-        SelectionManager.DeselectAll();
-        if (expandedDocs) {
-            if (!DocumentView._undoBatch) {
-                DocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
-            }
-            let isMinimized: boolean | undefined;
-            expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
-                let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
-                if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
-                    if (isMinimized === undefined) {
-                        isMinimized = BoolCast(maximizedDoc.isMinimized, false);
-                    }
-                    maximizedDoc.willMaximize = isMinimized;
-                    maximizedDoc.isMinimized = false;
-                    maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
-                }
-            });
-            setTimeout(() => {
-                DocumentView._undoBatch && DocumentView._undoBatch.end();
-                DocumentView._undoBatch = undefined;
-            }, 500);
-        }
-    }
     onClick = async (e: React.MouseEvent) => {
         e.stopPropagation();
         let altKey = e.altKey;
@@ -273,7 +247,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         }
                     } else {
                         let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2);
-                        this.collapseToPoint(scrpt, expandedProtoDocs);
+                        this.props.collapseToPoint && this.props.collapseToPoint(scrpt, expandedProtoDocs);
                     }
                 }
                 else if (linkedToDocs.length || linkedFromDocs.length) {
-- 
cgit v1.2.3-70-g09d2


From f4e6d81f4be7e26ec8866ea5a6e9d2433b89a79c Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 2 Jun 2019 22:22:05 -0400
Subject: fixes to stacking view and freezing collections

---
 .../views/collections/CollectionStackingView.tsx    | 21 +++++++++++----------
 .../collections/collectionFreeForm/MarqueeView.tsx  | 12 ++++++++----
 src/client/views/nodes/DocumentView.tsx             | 21 +++++++++++++++------
 3 files changed, 34 insertions(+), 20 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 25cc55be9..dcc7417ad 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -22,14 +22,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     get columnWidth() { return this.singleColumn ? this.props.PanelWidth() - 2 * this.gridSize - this.gridGap : NumCast(this.props.Document.columnWidth, 250); }
 
     componentDidMount() {
-        this._heightDisposer = reaction(() => [this.childDocs.map(d => [d[HeightSym](), d.isMinimized]), this.props.PanelHeight(), this.props.PanelWidth()],
+        this._heightDisposer = reaction(() => [this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized]), this.columnWidth, this.props.PanelHeight()],
             () => {
                 if (this.singleColumn) {
                     this.props.Document.height = this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => {
-                        let hgt = d[HeightSym]();
+                        let hgt = d[HeightSym]() / NumCast(d.zoomBasis, 1);
                         let nw = NumCast(d.nativeWidth);
                         let nh = NumCast(d.nativeHeight);
-                        if (nw && nh) hgt = nh / nw * Math.min(this.columnWidth, d[WidthSym]());
+                        if (nw && nh) hgt = nh / nw * Math.min(this.columnWidth, d[WidthSym]() / NumCast(d.zoomBasis, 1));
                         let rowSpan = Math.ceil((hgt + this.gridGap) / (this.gridSize + this.gridGap));
                         return height + rowSpan * (this.gridSize + this.gridGap);
                     }, 10);
@@ -76,19 +76,20 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return this.childDocs.filter(d => !d.isMinimized).map(d => {
             let dref = React.createRef<HTMLDivElement>();
             let script = undefined;
-            let colWidth = () => this.columnWidth;
+            let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym]() / NumCast(d.zoomBasis, 1), this.columnWidth) : this.columnWidth;
+            let margin = colWidth() < this.columnWidth ? "auto" : undefined;
             let rowHeight = () => {
-                let hgt = d[HeightSym]();
+                let hgt = d[HeightSym]() / NumCast(d.zoomBasis, 1);
                 let nw = NumCast(d.nativeWidth);
                 let nh = NumCast(d.nativeHeight);
-                if (nw && nh) hgt = nh / nw * Math.min(this.columnWidth, d[WidthSym]());
+                if (nw && nh) hgt = nh / nw * colWidth();
                 return hgt;
             }
             let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]());
             return <div className="colletionStackingView-masonryDoc"
                 key={d[Id]}
                 ref={dref}
-                style={{ width: colWidth(), height: rowHeight() }} >
+                style={{ width: colWidth(), height: rowHeight(), marginLeft: margin, marginRight: margin }} >
                 <CollectionSchemaPreview
                     Document={d}
                     width={colWidth}
@@ -111,13 +112,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return this.childDocs.filter(d => !d.isMinimized).map(d => {
             let dref = React.createRef<HTMLDivElement>();
             let dxf = () => this.getDocTransform(d, dref.current!);
-            let colSpan = Math.ceil((this.columnWidth + this.gridGap) / (this.gridSize + this.gridGap));
+            let colSpan = Math.ceil(Math.min(d[WidthSym](), this.columnWidth + this.gridGap) / (this.gridSize + this.gridGap));
             let rowSpan = Math.ceil((this.columnWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap));
             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="colletionStackingView-masonryDoc"
+            return (<div className="collectionStackingView-masonryDoc"
                 key={d[Id]}
                 ref={dref}
                 style={{
@@ -163,7 +164,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                         width: `${cells * itemCols * (this.gridSize + this.gridGap) + leftMargin}`,
                         height: "100%",
                         overflow: "hidden",
-                        marginLeft: "auto", marginRight: "auto", position: "relative",
+                        marginRight: "auto", position: "relative",
                         gridGap: this.gridGap,
                         gridTemplateColumns: this.singleColumn ? undefined : `repeat(auto-fill, minmax(${this.gridSize}px,1fr))`,
                         gridAutoRows: this.singleColumn ? undefined : `${this.gridSize}px`
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 14dbe1899..a19f40978 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -20,6 +20,8 @@ import { Template, Templates } from "../../Templates";
 import { SearchBox } from "../../SearchBox";
 import { DocServer } from "../../../DocServer";
 import { Id } from "../../../../new_fields/FieldSymbols";
+import { CollectionView } from "../CollectionView";
+import { CollectionViewType } from "../CollectionBaseView";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -284,10 +286,12 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 newCollection.proto!.summaryDoc = summary;
                 selected = [newCollection];
                 newCollection.x = bounds.left + bounds.width;
-                summary.proto!.summarizedDocs = new List<Doc>(selected);
-                summary.proto!.maximizeLocation = "inTab";  // or "inPlace", or "onRight"
-
-                this.props.addLiveTextDocument(summary);
+                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-" });
+                container.viewType = CollectionViewType.Stacking;
+                this.props.addLiveTextDocument(container);
                 // });
             } else if (e.key === "S") {
                 await htmlToImage.toPng(this._mainCont.current!, { width: bounds.width * zoomBasis, height: bounds.height * zoomBasis, quality: 0.2 }).then((dataUrl) => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 80c1292ae..b9e000ec8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -375,12 +375,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     freezeNativeDimensions = (e: React.MouseEvent): void => {
         if (NumCast(this.props.Document.nativeWidth)) {
-            this.props.Document.proto!.nativeWidth = undefined;
-            this.props.Document.proto!.nativeHeight = undefined;
-
-        } else {
-            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
-            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
+            let proto = Doc.GetProto(this.props.Document);
+            let nw = proto.nativeWidth;
+            let nh = proto.nativeHeight;
+            proto.nativeWidth = proto.nativeHeight = undefined;
+            this.props.Document.width = this.props.Document.frozenWidth;
+            this.props.Document.height = this.props.Document.frozenHeight;
+        }
+        else {
+            let scale = this.props.ScreenToLocalTransform().Scale * NumCast(this.props.Document.zoomBasis, 1);
+            let scr = this.screenRect();
+            let proto = Doc.GetProto(this.props.Document);
+            this.props.Document.frozenWidth = this.props.Document.width;
+            this.props.Document.frozenHeight = this.props.Document.height;
+            this.props.Document.height = proto.nativeHeight = scr.height * scale;
+            this.props.Document.width = proto.nativeWidth = scr.width * scale;
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 6f49d067b58caf6297f7ae7687cf05b627c27a1d Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 4 Jun 2019 09:48:18 -0400
Subject: icon and stacking view fixes.

---
 src/client/util/DragManager.ts                          |  3 +--
 src/client/views/collections/CollectionStackingView.tsx | 10 +++++-----
 src/client/views/nodes/DocumentView.tsx                 |  4 ++--
 src/client/views/nodes/IconBox.scss                     |  1 +
 4 files changed, 9 insertions(+), 9 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 8f0cce095..1e84a0db0 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -279,8 +279,7 @@ export namespace DragManager {
             lastX = e.pageX;
             lastY = e.pageY;
             dragElements.map((dragElement, i) => (dragElement.style.transform =
-                `translate(${(xs[i] += moveX)}px, ${(ys[i] += moveY)}px) 
-                scale(${scaleXs[i]}, ${scaleYs[i]})`)
+                `translate(${(xs[i] += moveX)}px, ${(ys[i] += moveY)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`)
             );
         };
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index acd585268..da7ea50c6 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -26,13 +26,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             () => {
                 if (this.singleColumn) {
                     this.props.Document.height = this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => {
-                        let hgt = d[HeightSym]() / NumCast(d.zoomBasis, 1);
-                        let wid = d[WidthSym]() / NumCast(d.zoomBasis, 1);
+                        let hgt = d[HeightSym]();
+                        let wid = d[WidthSym]();
                         let nw = NumCast(d.nativeWidth);
                         let nh = NumCast(d.nativeHeight);
                         if (nw && nh) hgt = nh / nw * Math.min(this.columnWidth, wid);
                         return height + hgt + 2 * this.gridGap;
-                    }, this.gridGap * 2) * NumCast(this.props.Document.zoomBasis, 1);
+                    }, this.gridGap * 2);
                 }
             }, { fireImmediately: true });
     }
@@ -76,10 +76,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return this.childDocs.filter(d => !d.isMinimized).map((d, i) => {
             let dref = React.createRef<HTMLDivElement>();
             let script = undefined;
-            let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym]() / NumCast(d.zoomBasis, 1), this.columnWidth) : this.columnWidth;
+            let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth;
             let margin = colWidth() < this.columnWidth ? "auto" : undefined;
             let rowHeight = () => {
-                let hgt = d[HeightSym]() / NumCast(d.zoomBasis, 1);
+                let hgt = d[HeightSym]();
                 let nw = NumCast(d.nativeWidth);
                 let nh = NumCast(d.nativeHeight);
                 if (nw && nh) hgt = nh / nw * colWidth();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b9e000ec8..7750b9173 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -191,7 +191,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (minimizedDoc) {
             let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(
                 NumCast(minimizedDoc.x) - NumCast(this.Document.x), NumCast(minimizedDoc.y) - NumCast(this.Document.y));
-            this.collapseToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs));
+            this.props.collapseToPoint && this.props.collapseToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs));
         }
     }
 
@@ -447,7 +447,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     render() {
         var scaling = this.props.ContentScaling();
-        var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : (StrCast(this.props.Document.layout).indexOf("IconBox") === -1 ? "100%" : "auto");
+        var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
         var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
 
         return (
diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss
index 893dc2d36..488681027 100644
--- a/src/client/views/nodes/IconBox.scss
+++ b/src/client/views/nodes/IconBox.scss
@@ -10,6 +10,7 @@
     pointer-events: all;
     svg {
         width:  $MINIMIZED_ICON_SIZE !important;
+        height:  $MINIMIZED_ICON_SIZE !important;
         height: auto;
         background: white;
     }
-- 
cgit v1.2.3-70-g09d2

