From 04fd1451fb33bba806f9fe406d51c13d8d4dd541 Mon Sep 17 00:00:00 2001
From: Monika Hedman <monika_hedman@brown.edu>
Date: Tue, 16 Apr 2019 17:17:41 -0400
Subject: checking other search

---
 src/client/views/Main.tsx | 3 +++
 1 file changed, 3 insertions(+)

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 0469211fa..7d47c9932 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -262,6 +262,9 @@ export class Main extends React.Component {
             </div >,
             <div className="main-buttonDiv" key="workspaces" style={{ top: '34px', left: '2px', position: 'absolute' }} ref={workspacesRef}>
                 <button onClick={toggleWorkspaces}>Workspaces</button></div>,
+
+            <div className="main-searchDiv" key="search" style={{ top: '34px', right: '100px', position: 'absolute' }} ref={workspacesRef}> hello </div>,
+
             <div className="main-buttonDiv" key="logout" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}>
                 <button onClick={() => request.get(ServerUtils.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
         ];
-- 
cgit v1.2.3-70-g09d2


From 2181bc0f19b04a5c1a3e6e20206fc04a086995d2 Mon Sep 17 00:00:00 2001
From: Monika Hedman <monika_hedman@brown.edu>
Date: Tue, 16 Apr 2019 17:28:56 -0400
Subject: attempting graphics

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

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 7d47c9932..92c7fa7dd 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -263,7 +263,7 @@ export class Main extends React.Component {
             <div className="main-buttonDiv" key="workspaces" style={{ top: '34px', left: '2px', position: 'absolute' }} ref={workspacesRef}>
                 <button onClick={toggleWorkspaces}>Workspaces</button></div>,
 
-            <div className="main-searchDiv" key="search" style={{ top: '34px', right: '100px', position: 'absolute' }} ref={workspacesRef}> hello </div>,
+            <div className="main-searchDiv" key="search" style={{ top: '34px', right: '100px', position: 'absolute' }} ref={workspacesRef}> <SearchBox/> </div>,
 
             <div className="main-buttonDiv" key="logout" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}>
                 <button onClick={() => request.get(ServerUtils.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
-- 
cgit v1.2.3-70-g09d2


From 39bb878a32821d2e14110f4158471890234f4769 Mon Sep 17 00:00:00 2001
From: Monika Hedman <monika_hedman@brown.edu>
Date: Mon, 29 Apr 2019 19:52:35 -0400
Subject: search UI

---
 src/client/views/Main.scss      |  4 --
 src/client/views/Main.tsx       |  5 ++-
 src/client/views/SearchBox.scss | 64 +++++++++++++++++++++++++++++
 src/client/views/SearchBox.tsx  | 89 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 156 insertions(+), 6 deletions(-)
 create mode 100644 src/client/views/SearchBox.scss
 create mode 100644 src/client/views/SearchBox.tsx

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 26284974a..bca75da47 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -81,10 +81,6 @@ button:hover {
     cursor: pointer;
 }
 
-search {
-    background: $dark-color;
-}
-
 .clear-db-button {
     position: absolute;
     right: 45%;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 92c7fa7dd..87eefe67f 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -38,6 +38,7 @@ import "./Main.scss";
 import { MainOverlayTextBox } from './MainOverlayTextBox';
 import { DocumentView } from './nodes/DocumentView';
 import { PreviewCursor } from './PreviewCursor';
+import { SearchBox } from './SearchBox';
 
 
 @observer
@@ -263,9 +264,9 @@ export class Main extends React.Component {
             <div className="main-buttonDiv" key="workspaces" style={{ top: '34px', left: '2px', position: 'absolute' }} ref={workspacesRef}>
                 <button onClick={toggleWorkspaces}>Workspaces</button></div>,
 
-            <div className="main-searchDiv" key="search" style={{ top: '34px', right: '100px', position: 'absolute' }} ref={workspacesRef}> <SearchBox/> </div>,
+            <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={workspacesRef}> <SearchBox /> </div>,
 
-            <div className="main-buttonDiv" key="logout" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}>
+            <div className="main-buttonDiv" key="logout" style={{ bottom: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}>
                 <button onClick={() => request.get(ServerUtils.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
         ];
     }
diff --git a/src/client/views/SearchBox.scss b/src/client/views/SearchBox.scss
new file mode 100644
index 000000000..e1a1de142
--- /dev/null
+++ b/src/client/views/SearchBox.scss
@@ -0,0 +1,64 @@
+@import "globalCssVariables";
+
+.searchBox {
+    height: 32px;
+    //display: flex;
+    //padding: 4px;
+    -webkit-transition: width 0.4s ease-in-out;
+    transition: width 0.4s ease-in-out;
+    align-items: center;
+
+    .submit-search {
+        display: inline-block;
+        text-align: right;
+        color: $dark-color;
+        -webkit-transition: right 0.4s;
+        transition: right 0.4s;
+    }
+
+    .submit-search:hover {
+        color: $main-accent;
+        transform: scale(1.05);
+        cursor: pointer;
+    }
+
+    input[type=text] {
+        width: 130px;
+        -webkit-transition: width 0.4s;
+        transition: width 0.4s;
+        position: absolute;
+        right: 100px;
+    }
+
+    input[type=text]:focus {
+        width: 500px;
+        outline: 3px solid lightblue;
+    }
+
+    .filter-button {
+        position: absolute;
+        right: 30px;
+    }
+}
+
+.filter-form {
+    background: $dark-color;
+    height: 400px;
+    width: 400px;
+    position: relative;
+    right: 1px;
+    color: $light-color;
+    padding: 10px;
+    flex-direction: column;
+}
+
+#header {
+    text-transform: uppercase;
+    letter-spacing: 2px;
+    font-size: 100%;
+    height: 40px;
+}
+
+#option {
+    height: 20px;
+}
\ No newline at end of file
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
new file mode 100644
index 000000000..7f388719d
--- /dev/null
+++ b/src/client/views/SearchBox.tsx
@@ -0,0 +1,89 @@
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { observable, action } from 'mobx';
+import { Utils } from '../../Utils';
+import { MessageStore } from '../../server/Message';
+import { Server } from '../Server';
+import "./SearchBox.scss";
+import { faSearch } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { actionFieldDecorator } from 'mobx/lib/internal';
+
+library.add(faSearch);
+
+@observer
+export class SearchBox extends React.Component {
+    @observable
+    searchString: string = "";
+
+    @observable private _open: boolean = false;
+
+    @action.bound
+    onChange(e: React.ChangeEvent<HTMLInputElement>) {
+        this.searchString = e.target.value;
+
+    }
+
+    submitSearch = () => {
+        Utils.EmitCallback(Server.Socket, MessageStore.SearchFor, this.searchString, (results: string[]) => {
+            for (const result of results) {
+                console.log(result);
+                Utils.GetQueryVariable()
+            }
+        });
+    }
+
+    @action
+    handleClick = (e: Event): void => {
+        var className = (e.target as any).className;
+        var id = (e.target as any).id;
+        console.log(id);
+        //let imgPrev = document.getElementById("img_preview");
+        console.log(className);
+        if (className !== "filter-button" && className !== "filter-form") {
+            console.log("false");
+            this._open = false;
+        }
+
+    }
+
+    componentWillMount() {
+        document.addEventListener('mousedown', this.handleClick, false);
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener('mousedown', this.handleClick, false);
+    }
+
+    @action
+    toggleDisplay = () => {
+        this._open = !this._open;
+    }
+
+    render() {
+        return (
+            <div id="outer">
+                <div className="searchBox" id="outer">
+                    {/* <input value={this.searchString} onChange={this.onChange} />
+                <button onClick={this.submitSearch} /> */}
+
+                    <input value={this.searchString} onChange={this.onChange} type="text" placeholder="Search.." className="search" />
+                    {/* {this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1).
+                    map(prop => <ContextMenuItem {...prop} key={prop.description} />)} */}
+
+                    <button className="filter-button" onClick={this.toggleDisplay}> Filter </button>
+                    <div className="submit-search" ><FontAwesomeIcon style={{ height: "100%" }} icon="search" size="lg" /></div>
+                </div>
+                <div className="filter-form" id="filter" style={this._open ? { display: "flex" } : { display: "none" }}>
+                    <div className="filter-form" id="header">Filter Search Results</div>
+                    <div className="filter-form" id="option">
+                        filter by collection, key, type of node
+                    </div>
+
+                </div>
+            </div>
+
+        );
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 7d35ba0df5be5f3c2cd95c0f0d148c0c9c04de7a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 5 May 2019 00:54:44 -0400
Subject: fix duplicate goldenlayout caused by presentation view

---
 src/client/views/Main.tsx             |  9 +----
 src/client/views/PresentationView.tsx | 62 ++++++++++++++++++++++-------------
 2 files changed, 41 insertions(+), 30 deletions(-)

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index c3b48d20f..1a9b1cf3c 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -51,9 +51,6 @@ export class Main extends React.Component {
     }
     private set mainContainer(doc: Opt<Doc>) {
         if (doc) {
-            if (!("presentationView" in doc)) {
-                doc.presentationView = new Doc();
-            }
             CurrentUserUtils.UserDocument.activeWorkspace = doc;
         }
     }
@@ -180,11 +177,7 @@ export class Main extends React.Component {
 
     @computed
     get presentationView() {
-        if (this.mainContainer) {
-            let presentation = FieldValue(Cast(this.mainContainer.presentationView, Doc));
-            return presentation ? <PresentationView Document={presentation} key="presentation" /> : (null);
-        }
-        return (null);
+        return <PresentationView key="presentation" />;
     }
 
     @computed
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index b4c12d057..f2d685443 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -1,18 +1,19 @@
 import { observer } from "mobx-react";
 import React = require("react")
-import { observable, action } from "mobx";
+import { observable, action, runInAction, reaction } from "mobx";
 import "./PresentationView.scss"
 import "./Main.tsx";
 import { DocumentManager } from "../util/DocumentManager";
 import { Utils } from "../../Utils";
 import { Doc } from "../../new_fields/Doc";
 import { listSpec } from "../../new_fields/Schema";
-import { Cast, NumCast, FieldValue } from "../../new_fields/Types";
+import { Cast, NumCast, FieldValue, PromiseValue } from "../../new_fields/Types";
 import { Id } from "../../new_fields/RefField";
 import { List } from "../../new_fields/List";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 
 export interface PresViewProps {
-    Document: Doc;
+    //Document: Doc;
 }
 
 
@@ -22,6 +23,11 @@ export interface PresViewProps {
  */
 class PresentationViewItem extends React.Component<PresViewProps> {
 
+    @observable Document?: Doc;
+    constructor(props: PresViewProps) {
+        super(props);
+        this.Document = FieldValue(Cast(FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc))!.presentationView, Doc))!;
+    }
     //look at CollectionFreeformView.focusDocument(d)
     @action
     openDoc = (doc: Doc) => {
@@ -36,7 +42,7 @@ class PresentationViewItem extends React.Component<PresViewProps> {
   **/
     @action
     public RemoveDoc(doc: Doc) {
-        const value = Cast(this.props.Document.data, listSpec(Doc), []);
+        const value = Cast(this.Document!.data, listSpec(Doc), []);
         let index = -1;
         for (let i = 0; i < value.length; i++) {
             if (value[i][Id] === doc[Id]) {
@@ -57,10 +63,10 @@ class PresentationViewItem extends React.Component<PresViewProps> {
         let title = document.title;
 
         //to get currently selected presentation doc
-        let selected = NumCast(this.props.Document.selectedDoc, 0);
+        let selected = NumCast(this.Document!.selectedDoc, 0);
 
         // finally, if it's a normal document, then render it as such.
-        const children = Cast(this.props.Document.data, listSpec(Doc));
+        const children = Cast(this.Document!.data, listSpec(Doc));
         const styles: any = {};
         if (children && children[selected] === document) {
             //this doc is selected
@@ -76,7 +82,7 @@ class PresentationViewItem extends React.Component<PresViewProps> {
     }
 
     render() {
-        const children = Cast(this.props.Document.data, listSpec(Doc), []);
+        const children = Cast(this.Document!.data, listSpec(Doc), []);
 
         return (
             <div>
@@ -94,13 +100,13 @@ export class PresentationView extends React.Component<PresViewProps>  {
     //observable means render is re-called every time variable is changed
     @observable
     collapsed: boolean = false;
-    closePresentation = action(() => this.props.Document.width = 0);
+    closePresentation = action(() => this.Document!.width = 0);
     next = () => {
-        const current = NumCast(this.props.Document.selectedDoc);
-        const allDocs = FieldValue(Cast(this.props.Document.data, listSpec(Doc)));
+        const current = NumCast(this.Document!.selectedDoc);
+        const allDocs = FieldValue(Cast(this.Document!.data, listSpec(Doc)));
         if (allDocs && current < allDocs.length + 1) {
             //can move forwards
-            this.props.Document.selectedDoc = current + 1;
+            this.Document!.selectedDoc = current + 1;
             const doc = allDocs[current + 1];
             let docView = DocumentManager.Instance.getDocumentView(doc);
             if (docView) {
@@ -110,11 +116,11 @@ export class PresentationView extends React.Component<PresViewProps>  {
 
     }
     back = () => {
-        const current = NumCast(this.props.Document.selectedDoc);
-        const allDocs = FieldValue(Cast(this.props.Document.data, listSpec(Doc)));
+        const current = NumCast(this.Document!.selectedDoc);
+        const allDocs = FieldValue(Cast(this.Document!.data, listSpec(Doc)));
         if (allDocs && current - 1 >= 0) {
             //can move forwards
-            this.props.Document.selectedDoc = current - 1;
+            this.Document!.selectedDoc = current - 1;
             const doc = allDocs[current - 1];
             let docView = DocumentManager.Instance.getDocumentView(doc);
             if (docView) {
@@ -125,9 +131,21 @@ export class PresentationView extends React.Component<PresViewProps>  {
 
     private ref = React.createRef<HTMLDivElement>();
 
+    @observable Document?: Doc;
     //initilize class variables
     constructor(props: PresViewProps) {
         super(props);
+        let self = this;
+        reaction(() =>
+            CurrentUserUtils.UserDocument.activeWorkspace,
+            (activeW) => {
+                if (activeW && activeW instanceof Doc) {
+                    PromiseValue(Cast(activeW.presentationView, Doc)).
+                        then(pv => runInAction(() =>
+                            self.Document = pv ? pv : (activeW.presentationView = new Doc())))
+                }
+            },
+            { fireImmediately: true });
         PresentationView.Instance = this;
     }
 
@@ -137,19 +155,21 @@ export class PresentationView extends React.Component<PresViewProps>  {
     @action
     public PinDoc(doc: Doc) {
         //add this new doc to props.Document
-        const data = Cast(this.props.Document.data, listSpec(Doc));
+        const data = Cast(this.Document!.data, listSpec(Doc));
         if (data) {
             data.push(doc);
         } else {
-            this.props.Document.data = new List([doc]);
+            this.Document!.data = new List([doc]);
         }
 
-        this.props.Document.width = 300;
+        this.Document!.width = 300;
     }
 
     render() {
-        let titleStr = this.props.Document.Title;
-        let width = NumCast(this.props.Document.width);
+        if (!this.Document)
+            return (null);
+        let titleStr = this.Document.Title;
+        let width = NumCast(this.Document.width);
 
         //TODO: next and back should be icons
         return (
@@ -163,9 +183,7 @@ export class PresentationView extends React.Component<PresViewProps>  {
 
                 </div>
                 <ul>
-                    <PresentationViewItem
-                        Document={this.props.Document}
-                    />
+                    <PresentationViewItem />
                 </ul>
             </div>
         );
-- 
cgit v1.2.3-70-g09d2


From 9a00685a16ba70f9bed18979508f17f316965045 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 5 May 2019 00:57:12 -0400
Subject: from last

---
 src/client/views/Main.tsx | 9 +--------
 1 file changed, 1 insertion(+), 8 deletions(-)

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 1a9b1cf3c..617580332 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -174,19 +174,12 @@ export class Main extends React.Component {
             }
         }, 100);
     }
-
-    @computed
-    get presentationView() {
-        return <PresentationView key="presentation" />;
-    }
-
     @computed
     get mainContent() {
         let pwidthFunc = () => this.pwidth;
         let pheightFunc = () => this.pheight;
         let noScaling = () => 1;
         let mainCont = this.mainContainer;
-        let pcontent = this.presentationView;
         return <Measure onResize={action((r: any) => { this.pwidth = r.entry.width; this.pheight = r.entry.height; })}>
             {({ measureRef }) =>
                 <div ref={measureRef} id="mainContent-div">
@@ -206,7 +199,7 @@ export class Main extends React.Component {
                             whenActiveChanged={emptyFunction}
                             bringToFront={emptyFunction}
                             ContainingCollectionView={undefined} />}
-                    {pcontent}
+                    <PresentationView key="presentation" />
                 </div>
             }
         </Measure>;
-- 
cgit v1.2.3-70-g09d2


From 11ab63f6c91093951fdc293c3d67e63073fb2f4c Mon Sep 17 00:00:00 2001
From: Monika Hedman <monika_hedman@brown.edu>
Date: Tue, 7 May 2019 17:42:37 -0400
Subject: navigate to searched doc

---
 src/client/util/DocumentManager.ts | 30 +++++++++++++++++++++++++++++-
 src/client/views/Main.tsx          |  2 +-
 src/client/views/SearchBox.tsx     |  2 +-
 src/client/views/SearchItem.tsx    | 16 ++--------------
 src/client/views/nodes/LinkBox.tsx | 23 +----------------------
 5 files changed, 34 insertions(+), 39 deletions(-)

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 69964e2c9..3151bcfb5 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,8 +1,10 @@
 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, NumCast } from '../../new_fields/Types';
 import { listSpec } from '../../new_fields/Schema';
+import { undoBatch } from './UndoManager';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 
 
 export class DocumentManager {
@@ -87,4 +89,30 @@ export class DocumentManager {
             return pairs;
         }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
     }
+
+    @undoBatch
+    public jumpToDocument = async (doc: Doc): Promise<void> => {
+        let docView = DocumentManager.Instance.getDocumentView(doc);
+        if (docView) {
+            docView.props.focus(docView.props.Document);
+        } else {
+            const contextDoc = await Cast(doc.annotationOn, Doc);
+            if (!contextDoc) {
+                CollectionDockingView.Instance.AddRightSplit(Doc.MakeDelegate(doc));
+            } else {
+                const page = NumCast(doc.page, undefined);
+                const curPage = NumCast(contextDoc.curPage, undefined);
+                if (page !== curPage) {
+                    contextDoc.curPage = page;
+                }
+                let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
+                if (contextView) {
+                    contextDoc.panTransformType = "Ease";
+                    contextView.props.focus(contextDoc);
+                } else {
+                    CollectionDockingView.Instance.AddRightSplit(contextDoc);
+                }
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 677902c5b..c9d5c395c 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -267,7 +267,7 @@ export class Main extends React.Component {
                 <button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button>
             </div >,
             <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <SearchBox /> </div>,
-            <div className="main-buttonDiv" key="logout" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}>
+            <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
                 <button onClick={() => request.get(DocServer.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
         ];
 
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index a52598f4c..827d468df 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -17,6 +17,7 @@ import { constant } from 'async';
 import { DocServer } from '../DocServer';
 import { Doc } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/RefField';
+import { DocumentManager } from '../util/DocumentManager';
 
 
 library.add(faSearch);
@@ -35,7 +36,6 @@ export class SearchBox extends React.Component {
     @action.bound
     onChange(e: React.ChangeEvent<HTMLInputElement>) {
         this.searchString = e.target.value;
-        console.log(this.searchString)
     }
 
     @action
diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
index 82cb5404c..81da7ebd2 100644
--- a/src/client/views/SearchItem.tsx
+++ b/src/client/views/SearchItem.tsx
@@ -1,27 +1,15 @@
 import React = require("react");
 import { Doc } from "../../new_fields/Doc";
+import { DocumentManager } from "../util/DocumentManager";
 
 export interface SearchProps {
     doc: Doc;
-    //description: string;
-    //event: (e: React.MouseEvent<HTMLDivElement>) => void;
 }
 
-// export interface SubmenuProps {
-//     description: string;
-//     subitems: ContextMenuProps[];
-// }
-
-// export interface ContextMenuItemProps {
-//     type: ContextMenuProps | SubmenuProps;
-// }
-
-
-
 export class SearchItem extends React.Component<SearchProps> {
 
     onClick = () => {
-        console.log("document clicked: ", this.props.doc);
+        DocumentManager.Instance.jumpToDocument(this.props.doc)
     }
 
     render() {
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 08cfa590b..611cb66b6 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -31,28 +31,7 @@ export class LinkBox extends React.Component<Props> {
     @undoBatch
     onViewButtonPressed = async (e: React.PointerEvent): Promise<void> => {
         e.stopPropagation();
-        let docView = DocumentManager.Instance.getDocumentView(this.props.pairedDoc);
-        if (docView) {
-            docView.props.focus(docView.props.Document);
-        } else {
-            const contextDoc = await Cast(this.props.pairedDoc.annotationOn, Doc);
-            if (!contextDoc) {
-                CollectionDockingView.Instance.AddRightSplit(Doc.MakeDelegate(this.props.pairedDoc));
-            } else {
-                const page = NumCast(this.props.pairedDoc.page, undefined);
-                const curPage = NumCast(contextDoc.curPage, undefined);
-                if (page !== curPage) {
-                    contextDoc.curPage = page;
-                }
-                let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
-                if (contextView) {
-                    contextDoc.panTransformType = "Ease";
-                    contextView.props.focus(contextDoc);
-                } else {
-                    CollectionDockingView.Instance.AddRightSplit(contextDoc);
-                }
-            }
-        }
+        DocumentManager.Instance.jumpToDocument(this.props.pairedDoc);
     }
 
     onEditButtonPressed = (e: React.PointerEvent): void => {
-- 
cgit v1.2.3-70-g09d2


From 0103346e8bae5113f5ea6f34d2745addac3a192e Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sat, 11 May 2019 22:55:44 -0400
Subject: Changed measure params that we use

---
 src/client/views/Main.tsx                              | 2 +-
 src/client/views/collections/CollectionDockingView.tsx | 2 +-
 src/client/views/nodes/IconBox.tsx                     | 2 +-
 src/client/views/nodes/PDFBox.tsx                      | 4 ++--
 src/client/views/nodes/VideoBox.tsx                    | 4 ++--
 5 files changed, 7 insertions(+), 7 deletions(-)

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index c9d5c395c..158de31f5 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -180,7 +180,7 @@ export class Main extends React.Component {
         let pheightFunc = () => this.pheight;
         let noScaling = () => 1;
         let mainCont = this.mainContainer;
-        return <Measure onResize={action((r: any) => { this.pwidth = r.entry.width; this.pheight = r.entry.height; })}>
+        return <Measure offset onResize={action((r: any) => { this.pwidth = r.offset.width; this.pheight = r.offset.height; })}>
             {({ measureRef }) =>
                 <div ref={measureRef} id="mainContent-div">
                     {!mainCont ? (null) :
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 5ee943da9..efcee9c02 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -419,7 +419,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     render() {
         let theContent = this.content;
         return !this._document ? (null) :
-            <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
+            <Measure offset onResize={action((r: any) => { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}>
                 {({ measureRef }) => <div ref={measureRef}>  {theContent} </div>}
             </Measure>;
     }
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 48aecdcd0..071930633 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -59,7 +59,7 @@ export class IconBox extends React.Component<FieldViewProps> {
         return (
             <div className="iconBox-container" onContextMenu={this.specificContextMenu}>
                 {this.minimizedIcon}
-                <Measure onResize={(r) => runInAction(() => { if (r.entry.width || BoolCast(this.props.Document.hideLabel)) this.props.Document.nativeWidth = this.props.Document.width = (r.entry.width + Number(MINIMIZED_ICON_SIZE)); })}>
+                <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)); })}>
                     {({ measureRef }) =>
                         <span ref={measureRef} className="iconBox-label">{label}</span>
                     }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index caa66cbeb..f2fa6f47c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -279,7 +279,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
         //      so this design is flawed.
         var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
         if (!FieldValue(this.Document.nativeHeight, 0)) {
-            var nativeHeight = nativeWidth * r.entry.height / r.entry.width;
+            var nativeHeight = nativeWidth * r.offset.height / r.offset.width;
             this.props.Document.height = nativeHeight / nativeWidth * FieldValue(this.Document.width, 0);
             this.props.Document.nativeHeight = nativeHeight;
         }
@@ -297,7 +297,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
         let xf = FieldValue(this.Document.nativeHeight, 0) / renderHeight;
         let body = NumCast(this.props.Document.nativeHeight) ?
             this.pdfPage :
-            <Measure onResize={this.setScaling}>
+            <Measure offset onResize={this.setScaling}>
                 {({ measureRef }) =>
                     <div className="pdfBox-page" ref={measureRef}>
                         {this.pdfPage}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 422508f90..97c5d8818 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -35,7 +35,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
             // bcz: the nativeHeight should really be set when the document is imported.
             var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
             var nativeHeight = FieldValue(this.Document.nativeHeight, 0);
-            var newNativeHeight = nativeWidth * r.entry.height / r.entry.width;
+            var newNativeHeight = nativeWidth * r.offset.height / r.offset.width;
             if (!nativeHeight && newNativeHeight !== nativeHeight && !isNaN(newNativeHeight)) {
                 this.Document.height = newNativeHeight / nativeWidth * FieldValue(this.Document.width, 0);
                 this.Document.nativeHeight = newNativeHeight;
@@ -71,7 +71,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         let content = this.videoContent(field.url.href);
         return NumCast(this.props.Document.nativeHeight) ?
             content :
-            <Measure onResize={this.setScaling}>
+            <Measure offset onResize={this.setScaling}>
                 {({ measureRef }) =>
                     <div style={{ width: "100%", height: "auto" }} ref={measureRef}>
                         {content}
-- 
cgit v1.2.3-70-g09d2


From c3a24f3cd2d1d3baf5738c649552baadf3677385 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 14 May 2019 01:31:11 -0400
Subject: Refactored most of presentation view

---
 src/client/views/Main.scss                         |  19 ---
 src/client/views/Main.tsx                          |  57 ++++---
 src/client/views/PresentationView.scss             |  71 +++++----
 src/client/views/PresentationView.tsx              | 166 ++++++++-------------
 src/client/views/TemplateMenu.tsx                  |   1 -
 .../views/collections/CollectionDockingView.tsx    |  13 +-
 src/client/views/nodes/LinkMenu.tsx                |   2 +-
 src/new_fields/Doc.ts                              |  10 +-
 8 files changed, 155 insertions(+), 184 deletions(-)

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 2430e8f6c..d63b0147b 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -27,25 +27,6 @@ div {
     z-index: 9999;
 }
 
-h1 {
-    font-size: 50px;
-    position: fixed;
-    top: 30px;
-    left: 50%;
-    transform: translateX(-50%);
-    color: $dark-color;
-    text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
-    z-index: 9999;
-    font-family: $sans-serif;
-    font-weight: 700;
-    -webkit-touch-callout: none;
-    -webkit-user-select: none;
-    -khtml-user-select: none;
-    -moz-user-select: none;
-    -ms-user-select: none;
-    user-select: none;
-}
-
 .jsx-parser {
     width: 100%;
     pointer-events: none;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 158de31f5..66205f8ca 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,7 +1,7 @@
 import { IconName, library } from '@fortawesome/fontawesome-svg-core';
 import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, observable, runInAction } from 'mobx';
+import { action, computed, configure, observable, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import "normalize.css";
 import * as React from 'react';
@@ -51,6 +51,9 @@ export class Main extends React.Component {
     }
     private set mainContainer(doc: Opt<Doc>) {
         if (doc) {
+            if (!("presentationView" in doc)) {
+                doc.presentationView = new Doc();
+            }
             CurrentUserUtils.UserDocument.activeWorkspace = doc;
         }
     }
@@ -174,32 +177,42 @@ export class Main extends React.Component {
             }
         }, 100);
     }
+    @action
+    onResize = (r: any) => {
+        this.pwidth = r.offset.width;
+        this.pheight = r.offset.height;
+    }
+    getPWidth = () => {
+        return this.pwidth;
+    }
+    getPHeight = () => {
+        return this.pheight;
+    }
     @computed
     get mainContent() {
-        let pwidthFunc = () => this.pwidth;
-        let pheightFunc = () => this.pheight;
-        let noScaling = () => 1;
         let mainCont = this.mainContainer;
-        return <Measure offset onResize={action((r: any) => { this.pwidth = r.offset.width; this.pheight = r.offset.height; })}>
+        let content = !mainCont ? (null) :
+            <DocumentView Document={mainCont}
+                toggleMinimized={emptyFunction}
+                addDocument={undefined}
+                removeDocument={undefined}
+                ScreenToLocalTransform={Transform.Identity}
+                ContentScaling={returnOne}
+                PanelWidth={this.getPWidth}
+                PanelHeight={this.getPHeight}
+                isTopMost={true}
+                selectOnLoad={false}
+                focus={emptyFunction}
+                parentActive={returnTrue}
+                whenActiveChanged={emptyFunction}
+                bringToFront={emptyFunction}
+                ContainingCollectionView={undefined} />;
+        const pres = mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined;
+        return <Measure offset onResize={this.onResize}>
             {({ measureRef }) =>
                 <div ref={measureRef} id="mainContent-div">
-                    {!mainCont ? (null) :
-                        <DocumentView Document={mainCont}
-                            toggleMinimized={emptyFunction}
-                            addDocument={undefined}
-                            removeDocument={undefined}
-                            ScreenToLocalTransform={Transform.Identity}
-                            ContentScaling={noScaling}
-                            PanelWidth={pwidthFunc}
-                            PanelHeight={pheightFunc}
-                            isTopMost={true}
-                            selectOnLoad={false}
-                            focus={emptyFunction}
-                            parentActive={returnTrue}
-                            whenActiveChanged={emptyFunction}
-                            bringToFront={emptyFunction}
-                            ContainingCollectionView={undefined} />}
-                    <PresentationView key="presentation" />
+                    {content}
+                    {pres ? <PresentationView Document={pres} key="presentation" /> : null}
                 </div>
             }
         </Measure>;
diff --git a/src/client/views/PresentationView.scss b/src/client/views/PresentationView.scss
index 7c5677f0d..fb4a851c4 100644
--- a/src/client/views/PresentationView.scss
+++ b/src/client/views/PresentationView.scss
@@ -4,15 +4,14 @@
     z-index: 1;
     box-shadow: #AAAAAA .2vw .2vw .4vw;
     right: 0;
-    top:0;
-    bottom:0;
+    top: 0;
+    bottom: 0;
 }
 
 .presentationView-item {
-    width: 220px;
-    height: 40px;
-    vertical-align: center;
-    padding-top: 15px;
+    padding: 10px;
+    display: inline-block;
+    width: 100%;
     -webkit-touch-callout: none;
     -webkit-user-select: none;
     -khtml-user-select: none;
@@ -22,47 +21,59 @@
     transition: all .1s;
 }
 
+.presentationView-listCont {
+    padding-left: 10px;
+    padding-right: 10px;
+}
+
 .presentationView-item:hover {
     transition: all .1s;
     background: #AAAAAA
 }
 
+.presentationView-selected {
+    background: gray;
+}
+
 .presentationView-heading {
-     margin-top: 0px;
-    height: 40px;
     background: lightseagreen;
-    padding: 30px;
+    padding: 10px;
+    display: inline-block;
+    width: 100%;
 }
+
 .presentationView-title {
-   padding-top: 3px; 
-   padding-bottom: 3px; 
-   font-size: 25px;
-   float:left;
+    padding-top: 3px;
+    padding-bottom: 3px;
+    font-size: 25px;
+    display: inline-block;
 }
-.presentation-icon{
+
+.presentation-icon {
     float: right;
-    display: inline;
-    width: 10px;
-    margin-top: 7px;
 }
-.presentationView-header {
-    padding-top: 1px; 
-    padding-bottom: 1px; 
+
+.presentationView-name {
     font-size: 15px;
-    float:left;
- }
+    display: inline-block;
+}
+
+.presentation-button {
+    margin-right: 12.5%;
+    margin-left: 12.5%;
+    width: 25%;
+}
 
- .presentation-next{
-     float: right;
- }
- .presentation-back{
-     float: left;
- }
- .presentation-next:hover{
+.presentation-buttons {
+    padding: 10px;
+}
+
+.presentation-next:hover {
     transition: all .1s;
     background: #AAAAAA
 }
-.presentation-back:hover{
+
+.presentation-back:hover {
     transition: all .1s;
     background: #AAAAAA
 }
\ No newline at end of file
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index 3fb24a339..098e725c7 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -5,15 +5,20 @@ import "./PresentationView.scss"
 import "./Main.tsx";
 import { DocumentManager } from "../util/DocumentManager";
 import { Utils } from "../../Utils";
-import { Doc } from "../../new_fields/Doc";
+import { Doc, DocListCast, DocListCastAsync } from "../../new_fields/Doc";
 import { listSpec } from "../../new_fields/Schema";
-import { Cast, NumCast, FieldValue, PromiseValue } from "../../new_fields/Types";
+import { Cast, NumCast, FieldValue, PromiseValue, StrCast } from "../../new_fields/Types";
 import { Id } from "../../new_fields/RefField";
 import { List } from "../../new_fields/List";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 
 export interface PresViewProps {
-    //Document: Doc;
+    Document: Doc;
+}
+
+interface PresListProps extends PresViewProps {
+    deleteDocument(index: number): void;
+    gotoDocument(index: number): void;
 }
 
 
@@ -21,72 +26,40 @@ export interface PresViewProps {
 /**
  * Component that takes in a document prop and a boolean whether it's collapsed or not.
  */
-class PresentationViewItem extends React.Component<PresViewProps> {
-
-    @observable Document: Doc;
-    constructor(props: PresViewProps) {
-        super(props);
-        this.Document = FieldValue(Cast(FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc))!.presentationView, Doc))!;
-    }
-    //look at CollectionFreeformView.focusDocument(d)
-    @action
-    openDoc = (doc: Doc) => {
-        let docView = DocumentManager.Instance.getDocumentView(doc);
-        if (docView) {
-            docView.props.focus(docView.props.Document);
-        }
-    }
-
-    /**
-  * Removes a document from the presentation view
-  **/
-    @action
-    public RemoveDoc(doc: Doc) {
-        const value = Cast(this.Document.data, listSpec(Doc), []);
-        let index = -1;
-        for (let i = 0; i < value.length; i++) {
-            if (value[i][Id] === doc[Id]) {
-                index = i;
-                break;
-            }
-        }
-        if (index !== -1) {
-            value.splice(index, 1);
-        }
-    }
+class PresentationViewList extends React.Component<PresListProps> {
 
     /**
      * Renders a single child document. It will just append a list element.
      * @param document The document to render.
      */
-    renderChild(document: Doc) {
+    renderChild = (document: Doc, index: number) => {
         let title = document.title;
 
         //to get currently selected presentation doc
-        let selected = NumCast(this.Document.selectedDoc, 0);
+        let selected = NumCast(this.props.Document.selectedDoc, 0);
 
-        // finally, if it's a normal document, then render it as such.
-        const children = Cast(this.Document.data, listSpec(Doc));
-        const styles: any = {};
-        if (children && children[selected] === document) {
+        let className = "presentationView-item";
+        if (selected === index) {
             //this doc is selected
-            styles.background = "gray";
+            className += " presentationView-selected";
         }
         return (
-            <li className="presentationView-item" style={styles} key={Utils.GenerateGuid()}>
-                <div className="presentationView-header" onClick={() => this.openDoc(document)}>{title}</div>
-                <div className="presentation-icon" onClick={() => this.RemoveDoc(document)}>X</div>
-            </li>
+            <div className={className} key={document[Id] + index} onClick={e => { this.props.gotoDocument(index); e.stopPropagation(); }}>
+                <strong className="presentationView-name">
+                    {`${index + 1}. ${title}`}
+                </strong>
+                <button className="presentation-icon" onClick={e => { this.props.deleteDocument(index); e.stopPropagation(); }}>X</button>
+            </div>
         );
 
     }
 
     render() {
-        const children = Cast(this.Document.data, listSpec(Doc), []);
+        const children = DocListCast(this.props.Document.data);
 
         return (
-            <div>
-                {children.map(value => this.renderChild(value))}
+            <div className="presentationView-listCont">
+                {children.map(this.renderChild)}
             </div>
         );
     }
@@ -100,59 +73,42 @@ export class PresentationView extends React.Component<PresViewProps>  {
     //observable means render is re-called every time variable is changed
     @observable
     collapsed: boolean = false;
-    closePresentation = action(() => this.Document!.width = 0);
+    closePresentation = action(() => this.props.Document.width = 0);
     next = () => {
-        const current = NumCast(this.Document!.selectedDoc);
-        const allDocs = FieldValue(Cast(this.Document!.data, listSpec(Doc)));
-        if (allDocs && current < allDocs.length + 1) {
-            //can move forwards
-            this.Document!.selectedDoc = current + 1;
-            const doc = allDocs[current + 1];
-            let docView = DocumentManager.Instance.getDocumentView(doc);
-            if (docView) {
-                docView.props.focus(docView.props.Document);
-            }
-        }
+        const current = NumCast(this.props.Document.selectedDoc);
+        this.gotoDocument(current + 1);
 
     }
     back = () => {
-        const current = NumCast(this.Document!.selectedDoc);
-        const allDocs = FieldValue(Cast(this.Document!.data, listSpec(Doc)));
-        if (allDocs && current - 1 >= 0) {
-            //can move forwards
-            this.Document!.selectedDoc = current - 1;
-            const doc = allDocs[current - 1];
-            let docView = DocumentManager.Instance.getDocumentView(doc);
-            if (docView) {
-                docView.props.focus(docView.props.Document);
-            }
+        const current = NumCast(this.props.Document.selectedDoc);
+        this.gotoDocument(current - 1);
+    }
+
+    @action
+    public RemoveDoc = (index: number) => {
+        const value = FieldValue(Cast(this.props.Document.data, listSpec(Doc)));
+        if (value) {
+            value.splice(index, 1);
         }
     }
 
-    private ref = React.createRef<HTMLDivElement>();
+    public gotoDocument = async (index: number) => {
+        const list = FieldValue(Cast(this.props.Document.data, listSpec(Doc)));
+        if (!list) {
+            return;
+        }
+        if (index < 0 || index >= list.length) {
+            return;
+        }
+
+        this.props.Document.selectedDoc = index;
+        const doc = await list[index];
+        DocumentManager.Instance.jumpToDocument(doc);
+    }
 
-    @observable Document?: Doc;
     //initilize class variables
     constructor(props: PresViewProps) {
         super(props);
-        let self = this;
-        reaction(() =>
-            CurrentUserUtils.UserDocument.activeWorkspace,
-            (activeW) => {
-                if (activeW && activeW instanceof Doc) {
-                    PromiseValue(Cast(activeW.presentationView, 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 });
         PresentationView.Instance = this;
     }
 
@@ -162,36 +118,32 @@ export class PresentationView extends React.Component<PresViewProps>  {
     @action
     public PinDoc(doc: Doc) {
         //add this new doc to props.Document
-        const data = Cast(this.Document!.data, listSpec(Doc));
+        const data = Cast(this.props.Document.data, listSpec(Doc));
         if (data) {
             data.push(doc);
         } else {
-            this.Document!.data = new List([doc]);
+            this.props.Document.data = new List([doc]);
         }
 
-        this.Document!.width = 300;
+        this.props.Document.width = 300;
     }
 
     render() {
-        if (!this.Document)
-            return (null);
-        let titleStr = this.Document.Title;
-        let width = NumCast(this.Document.width);
+        let titleStr = StrCast(this.props.Document.title);
+        let width = NumCast(this.props.Document.width);
 
         //TODO: next and back should be icons
         return (
             <div className="presentationView-cont" style={{ width: width, overflow: "hidden" }}>
                 <div className="presentationView-heading">
                     <div className="presentationView-title">{titleStr}</div>
-                    <div className='presentation-icon' onClick={this.closePresentation}>X</div></div>
-                <div>
-                    <div className="presentation-back" onClick={this.back}>back</div>
-                    <div className="presentation-next" onClick={this.next}>next</div>
-
+                    <button className='presentation-icon' onClick={this.closePresentation}>X</button>
+                </div>
+                <div className="presentation-buttons">
+                    <button className="presentation-button" onClick={this.back}>back</button>
+                    <button className="presentation-button" onClick={this.next}>next</button>
                 </div>
-                <ul>
-                    <PresentationViewItem />
-                </ul>
+                <PresentationViewList Document={this.props.Document} deleteDocument={this.RemoveDoc} gotoDocument={this.gotoDocument} />
             </div>
         );
     }
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e2b3bd07a..22c4edc25 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -38,7 +38,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
 
     constructor(props: TemplateMenuProps) {
         super(props);
-        console.log("");
     }
 
     @action
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index a755e0f91..6651a834d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,6 +1,6 @@
 import 'golden-layout/src/css/goldenlayout-base.css';
 import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, observable, reaction } from "mobx";
+import { action, observable, reaction, Lambda } from "mobx";
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
 import Measure from "react-measure";
@@ -188,12 +188,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             this._goldenLayout.init();
         }
     }
+    reactionDisposer?: Lambda;
     componentDidMount: () => void = () => {
+        console.log("Docking mount");
         if (this._containerRef.current) {
-            reaction(
+            this.reactionDisposer = reaction(
                 () => StrCast(this.props.Document.dockingConfig),
                 () => {
                     if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
+                        // Because this is in a set timeout, if this component unmounts right after mounting,
+                        // we will leak a GoldenLayout, because we try to destroy it before we ever create it
                         setTimeout(() => this.setupGoldenLayout(), 1);
                     }
                     this._ignoreStateChange = "";
@@ -203,6 +207,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         }
     }
     componentWillUnmount: () => void = () => {
+        console.log("Docking unmount");
         try {
             this._goldenLayout.unbind('itemDropped', this.itemDropped);
             this._goldenLayout.unbind('tabCreated', this.tabCreated);
@@ -214,6 +219,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         if (this._goldenLayout) this._goldenLayout.destroy();
         this._goldenLayout = null;
         window.removeEventListener('resize', this.onResize);
+
+        if (this.reactionDisposer) {
+            this.reactionDisposer();
+        }
     }
     @action
     onResize = (event: any) => {
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 5dabfc30d..11117122d 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -36,7 +36,7 @@ export class LinkMenu extends React.Component<Props> {
         if (this._editingLink === undefined) {
             return (
                 <div id="linkMenu-container">
-                    <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input>
+                    {/* <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> */}
                     <div id="linkMenu-list">
                         {this.renderLinkItems(linkTo, "linkedTo", "Destination: ")}
                         {this.renderLinkItems(linkFrom, "linkedFrom", "Source: ")}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 4c837fcbd..89901490d 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -29,15 +29,21 @@ 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.  
+ * If a default value is given, the returned value should not be modified as it might be a temporary value.  
+ * If no default value is given, and the returned value is not undefined, it can be safely modified.  
+ */
 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);
+    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)
+    return Cast(field, listSpec(Doc), []).filter(d => d && d instanceof Doc).map(d => d as Doc);
 }
 
 @Deserializable("doc").withFields(["id"])
-- 
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/Main.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 1ae0370e6a116404085f6864c8b644fcde80f460 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 14 May 2019 19:59:23 -0400
Subject: Added History module and ability to go back and forward when
 following links

---
 src/client/util/History.ts                         | 122 +++++++++++++++++++++
 src/client/views/Main.tsx                          |  19 +---
 .../views/collections/CollectionDockingView.tsx    |   2 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  36 +++++-
 src/new_fields/Doc.ts                              |   7 +-
 5 files changed, 159 insertions(+), 27 deletions(-)
 create mode 100644 src/client/util/History.ts

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/util/History.ts b/src/client/util/History.ts
new file mode 100644
index 000000000..92d2b2b44
--- /dev/null
+++ b/src/client/util/History.ts
@@ -0,0 +1,122 @@
+import { Doc, Opt, Field } from "../../new_fields/Doc";
+import { DocServer } from "../DocServer";
+import { Main } from "../views/Main";
+import { RouteStore } from "../../server/RouteStore";
+
+export namespace HistoryUtil {
+    export interface DocInitializerList {
+        [key: string]: string | number;
+    }
+
+    export interface DocUrl {
+        type: "doc";
+        docId: string;
+        initializers: {
+            [docId: string]: DocInitializerList;
+        };
+    }
+
+    export type ParsedUrl = DocUrl;
+
+    // const handlers: ((state: ParsedUrl | null) => void)[] = [];
+    function onHistory(e: PopStateEvent) {
+        if (window.location.pathname !== RouteStore.home) {
+            const url = e.state as ParsedUrl || parseUrl(window.location.pathname);
+            if (url) {
+                switch (url.type) {
+                    case "doc":
+                        onDocUrl(url);
+                        break;
+                }
+            }
+        }
+        // for (const handler of handlers) {
+        //     handler(e.state);
+        // }
+    }
+
+    export function pushState(state: ParsedUrl) {
+        history.pushState(state, "", createUrl(state));
+    }
+
+    export function replaceState(state: ParsedUrl) {
+        history.replaceState(state, "", createUrl(state));
+    }
+
+    function copyState(state: ParsedUrl): ParsedUrl {
+        return JSON.parse(JSON.stringify(state));
+    }
+
+    export function getState(): ParsedUrl {
+        return copyState(history.state);
+    }
+
+    // export function addHandler(handler: (state: ParsedUrl | null) => void) {
+    //     handlers.push(handler);
+    // }
+
+    // export function removeHandler(handler: (state: ParsedUrl | null) => void) {
+    //     const index = handlers.indexOf(handler);
+    //     if (index !== -1) {
+    //         handlers.splice(index, 1);
+    //     }
+    // }
+
+    export function parseUrl(pathname: string): ParsedUrl | undefined {
+        let pathnameSplit = pathname.split("/");
+        if (pathnameSplit.length !== 2) {
+            return undefined;
+        }
+        const type = pathnameSplit[0];
+        const data = pathnameSplit[1];
+
+        if (type === "doc") {
+            const s = data.split("?");
+            if (s.length < 1 || s.length > 2) {
+                return undefined;
+            }
+            const docId = s[0];
+            const initializers = s.length === 2 ? JSON.parse(decodeURIComponent(s[1])) : {};
+            return {
+                type: "doc",
+                docId,
+                initializers
+            };
+        }
+
+        return undefined;
+    }
+
+    export function createUrl(params: ParsedUrl): string {
+        let baseUrl = DocServer.prepend(`/${params.type}`);
+        switch (params.type) {
+            case "doc":
+                const initializers = encodeURIComponent(JSON.stringify(params.initializers));
+                const id = params.docId;
+                let url = baseUrl + `/${id}`;
+                if (Object.keys(params.initializers).length) {
+                    url += `?${initializers}`;
+                }
+                return url;
+        }
+        return "";
+    }
+
+    export async function initDoc(id: string, initializer: DocInitializerList) {
+        const doc = await DocServer.GetRefField(id);
+        if (!(doc instanceof Doc)) {
+            return;
+        }
+        Doc.assign(doc, initializer);
+    }
+
+    async function onDocUrl(url: DocUrl) {
+        const field = await DocServer.GetRefField(url.docId);
+        await Promise.all(Object.keys(url.initializers).map(id => initDoc(id, url.initializers[id])));
+        if (field instanceof Doc) {
+            Main.Instance.openWorkspace(field, true);
+        }
+    }
+
+    window.onpopstate = onHistory;
+}
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 66205f8ca..55252ab1d 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -38,6 +38,8 @@ 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 { HistoryUtil } from '../util/History';
+
 
 @observer
 export class Main extends React.Component {
@@ -95,21 +97,6 @@ export class Main extends React.Component {
         // }
     }
 
-    componentDidMount() { window.onpopstate = this.onHistory; }
-
-    componentWillUnmount() { window.onpopstate = null; }
-
-    onHistory = () => {
-        if (window.location.pathname !== RouteStore.home) {
-            let pathname = window.location.pathname.split("/");
-            DocServer.GetRefField(pathname[pathname.length - 1]).then(action((field: Opt<Field>) => {
-                if (field instanceof Doc) {
-                    this.openWorkspace(field, true);
-                }
-            }));
-        }
-    }
-
     initEventListeners = () => {
         // window.addEventListener("pointermove", (e) => this.reportLocation(e))
         window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler
@@ -165,7 +152,7 @@ export class Main extends React.Component {
     openWorkspace = async (doc: Doc, fromHistory = false) => {
         CurrentUserUtils.MainDocId = doc[Id];
         this.mainContainer = doc;
-        fromHistory || window.history.pushState(null, StrCast(doc.title), "/doc/" + doc[Id]);
+        fromHistory || HistoryUtil.pushState({ type: "doc", docId: doc[Id], initializers: {} });
         const col = await Cast(CurrentUserUtils.UserDocument.optionalRightCollection, Doc);
         // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized)
         setTimeout(async () => {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6651a834d..58f1e33a1 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -190,7 +190,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
     reactionDisposer?: Lambda;
     componentDidMount: () => void = () => {
-        console.log("Docking mount");
         if (this._containerRef.current) {
             this.reactionDisposer = reaction(
                 () => StrCast(this.props.Document.dockingConfig),
@@ -207,7 +206,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         }
     }
     componentWillUnmount: () => void = () => {
-        console.log("Docking unmount");
         try {
             this._goldenLayout.unbind('itemDropped', this.itemDropped);
             this._goldenLayout.unbind('tabCreated', this.tabCreated);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c36c708db..e60bb2fb2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -24,6 +24,7 @@ import { FieldValue, Cast, NumCast } 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";
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -219,6 +220,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     setPan(panX: number, panY: number) {
+        this.panDisposer && clearTimeout(this.panDisposer);
+        this.props.Document.panTransformType = "None";
         var scale = this.getLocalTransform().inverse().Scale;
         const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
         const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
@@ -245,16 +248,37 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         doc.zIndex = docs.length + 1;
     }
 
+    panDisposer?: NodeJS.Timeout;
     focusDocument = (doc: Doc) => {
+        const panX = this.Document.panX;
+        const panY = this.Document.panY;
+        const id = this.Document[Id];
+        const state = HistoryUtil.getState();
+        // TODO This technically isn't correct if type !== "doc", as 
+        // currently nothing is done, but we should probably push a new state
+        if (state.type === "doc" && panX !== undefined && panY !== undefined) {
+            const init = state.initializers[id];
+            if (!init) {
+                state.initializers[id] = {
+                    panX, panY
+                };
+                HistoryUtil.pushState(state);
+            } else if (init.panX !== panX || init.panY !== panY) {
+                init.panX = panX;
+                init.panY = panY;
+                HistoryUtil.pushState(state);
+            }
+        }
         SelectionManager.DeselectAll();
+        const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
+        const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2;
+        const newState = HistoryUtil.getState();
+        newState.initializers[id] = { panX: newPanX, panY: newPanY };
+        HistoryUtil.pushState(newState);
+        this.setPan(newPanX, newPanY);
         this.props.Document.panTransformType = "Ease";
-        this.setPan(
-            NumCast(doc.x) + NumCast(doc.width) / 2,
-            NumCast(doc.y) + NumCast(doc.height) / 2);
         this.props.focus(this.props.Document);
-        if (this.props.Document.panTransformType === "Ease") {
-            setTimeout(() => this.props.Document.panTransformType = "None", 2000);  // wait 3 seconds, then reset to false
-        }
+        this.panDisposer = setTimeout(() => this.props.Document.panTransformType = "None", 2000);  // wait 3 seconds, then reset to false
     }
 
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 89901490d..d6043ef7a 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -159,9 +159,10 @@ export namespace Doc {
         for (const key in fields) {
             if (fields.hasOwnProperty(key)) {
                 const value = fields[key];
-                if (value !== undefined) {
-                    doc[key] = value;
-                }
+                // Do we want to filter out undefineds?
+                // if (value !== undefined) {
+                doc[key] = value;
+                // }
             }
         }
         return doc;
-- 
cgit v1.2.3-70-g09d2


From c96e61de2cea0fee1f6c80caf426535dbdc3d37c Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Thu, 16 May 2019 16:08:53 -0400
Subject: Changed Clear DB Button

---
 .vscode/launch.json       | 9 +++++++++
 src/client/views/Main.tsx | 2 +-
 2 files changed, 10 insertions(+), 1 deletion(-)

(limited to 'src/client/views/Main.tsx')

diff --git a/.vscode/launch.json b/.vscode/launch.json
index e92a4949a..452acc823 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -13,6 +13,15 @@
             "url": "http://localhost:1050/login",
             "webRoot": "${workspaceFolder}",
         },
+        {
+            "type": "chrome",
+            "request": "launch",
+            "name": "Launch Chrome against Dash server",
+            "sourceMaps": true,
+            "breakOnLoad": true,
+            "url": "http://dash-web.eastus2.cloudapp.azure.com:1050/login",
+            "webRoot": "${workspaceFolder}",
+        },
         {
             "type": "node",
             "request": "attach",
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 7aef7d3e5..866da010a 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={DocServer.DeleteDatabase}>Clear Database</button>,
+            <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.ctrlKey && 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>
-- 
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/Main.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/Main.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 ea0b202809a2c4ddddf5f391cdf6fe8bc6523092 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sat, 18 May 2019 15:25:42 -0400
Subject: Refactored some things to get Debug viewer working again

---
 src/client/util/History.ts                         |   4 +-
 src/client/views/Main.tsx                          | 280 +---------------
 src/client/views/MainView.tsx                      | 274 ++++++++++++++++
 src/client/views/PresentationView.tsx              |   1 -
 .../views/collections/CollectionTreeView.tsx       |   8 +-
 src/debug/Viewer.tsx                               | 364 +++++++++++----------
 6 files changed, 468 insertions(+), 463 deletions(-)
 create mode 100644 src/client/views/MainView.tsx

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 92d2b2b44..545ea8629 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,7 +1,7 @@
 import { Doc, Opt, Field } from "../../new_fields/Doc";
 import { DocServer } from "../DocServer";
-import { Main } from "../views/Main";
 import { RouteStore } from "../../server/RouteStore";
+import { MainView } from "../views/MainView";
 
 export namespace HistoryUtil {
     export interface DocInitializerList {
@@ -114,7 +114,7 @@ export namespace HistoryUtil {
         const field = await DocServer.GetRefField(url.docId);
         await Promise.all(Object.keys(url.initializers).map(id => initDoc(id, url.initializers[id])));
         if (field instanceof Doc) {
-            Main.Instance.openWorkspace(field, true);
+            MainView.Instance.openWorkspace(field, true);
         }
     }
 
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 177047a9a..3d9750a85 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,281 +1,11 @@
-import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, observable, runInAction, trace } from 'mobx';
-import { observer } from 'mobx-react';
-import "normalize.css";
-import * as React from 'react';
+import { MainView } from "./MainView";
+import { Docs } from "../documents/Documents";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 import * as ReactDOM from 'react-dom';
-import Measure from 'react-measure';
-import * as request from 'request';
-import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { RouteStore } from '../../server/RouteStore';
-import { emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils';
-import { Docs } from '../documents/Documents';
-import { SetupDrag, DragManager } from '../util/DragManager';
-import { Transform } from '../util/Transform';
-import { UndoManager } from '../util/UndoManager';
-import { PresentationView } from './PresentationView';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { ContextMenu } from './ContextMenu';
-import { DocumentDecorations } from './DocumentDecorations';
-import { InkingControl } from './InkingControl';
-import "./Main.scss";
-import { MainOverlayTextBox } from './MainOverlayTextBox';
-import { DocumentView } from './nodes/DocumentView';
-import { PreviewCursor } from './PreviewCursor';
-import { SearchBox } from './SearchBox';
-import { SelectionManager } from '../util/SelectionManager';
-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 { HistoryUtil } from '../util/History';
-
-
-@observer
-export class Main extends React.Component {
-    public static Instance: Main;
-    @observable private _workspacesShown: boolean = false;
-    @observable public pwidth: number = 0;
-    @observable public pheight: number = 0;
-
-    @computed private get mainContainer(): Opt<Doc> {
-        return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc));
-    }
-    private set mainContainer(doc: Opt<Doc>) {
-        if (doc) {
-            if (!("presentationView" in doc)) {
-                doc.presentationView = new Doc();
-            }
-            CurrentUserUtils.UserDocument.activeWorkspace = doc;
-        }
-    }
-
-    constructor(props: Readonly<{}>) {
-        super(props);
-        Main.Instance = this;
-        // causes errors to be generated when modifying an observable outside of an action
-        configure({ enforceActions: "observed" });
-        if (window.location.pathname !== RouteStore.home) {
-            let pathname = window.location.pathname.split("/");
-            if (pathname.length > 1 && pathname[pathname.length - 2] === 'doc') {
-                CurrentUserUtils.MainDocId = pathname[pathname.length - 1];
-            }
-        }
-
-        library.add(faFont);
-        library.add(faImage);
-        library.add(faFilePdf);
-        library.add(faObjectGroup);
-        library.add(faTable);
-        library.add(faGlobeAsia);
-        library.add(faUndoAlt);
-        library.add(faRedoAlt);
-        library.add(faPenNib);
-        library.add(faFilm);
-        library.add(faMusic);
-        library.add(faTree);
-        this.initEventListeners();
-        this.initAuthenticationRouters();
-    }
-
-    initEventListeners = () => {
-        // window.addEventListener("pointermove", (e) => this.reportLocation(e))
-        window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler
-        window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler
-        window.addEventListener("keydown", (e) => {
-            if (e.key === "Escape") {
-                DragManager.AbortDrag();
-                SelectionManager.DeselectAll();
-            }
-        }, 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)) {
-                ContextMenu.Instance.clearItems();
-            }
-        }), true);
-    }
-
-    initAuthenticationRouters = async () => {
-        // Load the user's active workspace, or create a new one if initial session after signup
-        if (!CurrentUserUtils.MainDocId) {
-            const doc = await Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc);
-            if (doc) {
-                this.openWorkspace(doc);
-            } else {
-                this.createNewWorkspace();
-            }
-        } else {
-            DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field =>
-                field instanceof Doc ? this.openWorkspace(field) :
-                    this.createNewWorkspace(CurrentUserUtils.MainDocId));
-        }
-    }
-
-    @action
-    createNewWorkspace = async (id?: string) => {
-        const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
-        if (list) {
-            let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, title: `WS collection ${list.length + 1}` });
-            var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
-            let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` });
-            list.push(mainDoc);
-            // 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"], [], { title: "New Mobile Uploads" });
-                mainDoc.optionalRightCollection = pendingDocument;
-            }, 0);
-        }
-    }
-
-    @action
-    openWorkspace = async (doc: Doc, fromHistory = false) => {
-        CurrentUserUtils.MainDocId = doc[Id];
-        this.mainContainer = doc;
-        fromHistory || HistoryUtil.pushState({ type: "doc", docId: doc[Id], initializers: {} });
-        const col = await Cast(CurrentUserUtils.UserDocument.optionalRightCollection, Doc);
-        // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized)
-        setTimeout(async () => {
-            if (col) {
-                const l = Cast(col.data, listSpec(Doc));
-                if (l && l.length > 0) {
-                    CollectionDockingView.Instance.AddRightSplit(col);
-                }
-            }
-        }, 100);
-    }
-    @action
-    onResize = (r: any) => {
-        this.pwidth = r.offset.width;
-        this.pheight = r.offset.height;
-    }
-    getPWidth = () => {
-        return this.pwidth;
-    }
-    getPHeight = () => {
-        return this.pheight;
-    }
-    @computed
-    get mainContent() {
-        let mainCont = this.mainContainer;
-        let content = !mainCont ? (null) :
-            <DocumentView Document={mainCont}
-                toggleMinimized={emptyFunction}
-                addDocument={undefined}
-                addDocTab={emptyFunction}
-                removeDocument={undefined}
-                ScreenToLocalTransform={Transform.Identity}
-                ContentScaling={returnOne}
-                PanelWidth={this.getPWidth}
-                PanelHeight={this.getPHeight}
-                isTopMost={true}
-                selectOnLoad={false}
-                focus={emptyFunction}
-                parentActive={returnTrue}
-                whenActiveChanged={emptyFunction}
-                bringToFront={emptyFunction}
-                ContainingCollectionView={undefined} />;
-        const pres = mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined;
-        return <Measure offset onResize={this.onResize}>
-            {({ measureRef }) =>
-                <div ref={measureRef} id="mainContent-div">
-                    {content}
-                    {pres ? <PresentationView Document={pres} key="presentation" /> : null}
-                </div>
-            }
-        </Measure>;
-    }
-
-    /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
-    nodesMenu() {
-
-        let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
-        let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c27211_sample_explain.pdf";
-        let weburl = "https://cs.brown.edu/courses/cs166/";
-        let audiourl = "http://techslides.com/demos/samples/sample.mp3";
-        let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
-
-        let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
-        let addColNode = action(() => Docs.FreeformDocument([], { width: 200, height: 200, title: "a freeform 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" }));
-        let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
-        let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
-        let addWebNode = action(() => Docs.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
-        let addAudioNode = action(() => Docs.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
-
-        let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
-            [React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
-            [React.createRef<HTMLDivElement>(), "image", "Add Image", addImageNode],
-            [React.createRef<HTMLDivElement>(), "file-pdf", "Add PDF", addPDFNode],
-            [React.createRef<HTMLDivElement>(), "film", "Add Video", addVideoNode],
-            [React.createRef<HTMLDivElement>(), "music", "Add Audio", addAudioNode],
-            [React.createRef<HTMLDivElement>(), "globe-asia", "Add Web Clipping", addWebNode],
-            [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
-            [React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
-            [React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
-        ];
-
-        return < div id="add-nodes-menu" >
-            <input type="checkbox" id="add-menu-toggle" />
-            <label htmlFor="add-menu-toggle" title="Add Node"><p>+</p></label>
-
-            <div id="add-options-content">
-                <ul id="add-options-list">
-                    {btns.map(btn =>
-                        <li key={btn[1]} ><div ref={btn[0]}>
-                            <button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}>
-                                <FontAwesomeIcon icon={btn[1]} size="sm" />
-                            </button>
-                        </div></li>)}
-                </ul>
-            </div>
-        </div >;
-    }
-
-    /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
-    @computed
-    get miscButtons() {
-        let logoutRef = React.createRef<HTMLDivElement>();
-
-        return [
-            <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.altKey && DocServer.DeleteDatabase()}>Clear Database</button>,
-            <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>
-                <button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button>
-            </div >,
-            <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <SearchBox /> </div>,
-            <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
-                <button onClick={() => request.get(DocServer.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
-        ];
-
-    }
-
-    render() {
-        return (
-            <div id="main-div">
-                <DocumentDecorations />
-                {this.mainContent}
-                <PreviewCursor />
-                <ContextMenu />
-                {this.nodesMenu()}
-                {this.miscButtons}
-                <InkingControl />
-                <MainOverlayTextBox />
-            </div>
-        );
-    }
-}
+import * as React from 'react';
 
 (async () => {
     await Docs.initProtos();
     await CurrentUserUtils.loadCurrentUser();
-    ReactDOM.render(<Main />, document.getElementById('root'));
+    ReactDOM.render(<MainView />, document.getElementById('root'));
 })();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
new file mode 100644
index 000000000..d0d77bbf4
--- /dev/null
+++ b/src/client/views/MainView.tsx
@@ -0,0 +1,274 @@
+import { IconName, library } from '@fortawesome/fontawesome-svg-core';
+import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, configure, observable, runInAction, trace } from 'mobx';
+import { observer } from 'mobx-react';
+import "normalize.css";
+import * as React from 'react';
+import Measure from 'react-measure';
+import * as request from 'request';
+import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
+import { RouteStore } from '../../server/RouteStore';
+import { emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils';
+import { Docs } from '../documents/Documents';
+import { SetupDrag, DragManager } from '../util/DragManager';
+import { Transform } from '../util/Transform';
+import { UndoManager } from '../util/UndoManager';
+import { PresentationView } from './PresentationView';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { ContextMenu } from './ContextMenu';
+import { DocumentDecorations } from './DocumentDecorations';
+import { InkingControl } from './InkingControl';
+import "./Main.scss";
+import { MainOverlayTextBox } from './MainOverlayTextBox';
+import { DocumentView } from './nodes/DocumentView';
+import { PreviewCursor } from './PreviewCursor';
+import { SearchBox } from './SearchBox';
+import { SelectionManager } from '../util/SelectionManager';
+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 { HistoryUtil } from '../util/History';
+
+
+@observer
+export class MainView extends React.Component {
+    public static Instance: MainView;
+    @observable private _workspacesShown: boolean = false;
+    @observable public pwidth: number = 0;
+    @observable public pheight: number = 0;
+
+    @computed private get mainContainer(): Opt<Doc> {
+        return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc));
+    }
+    private set mainContainer(doc: Opt<Doc>) {
+        if (doc) {
+            if (!("presentationView" in doc)) {
+                doc.presentationView = new Doc();
+            }
+            CurrentUserUtils.UserDocument.activeWorkspace = doc;
+        }
+    }
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+        MainView.Instance = this;
+        // causes errors to be generated when modifying an observable outside of an action
+        configure({ enforceActions: "observed" });
+        if (window.location.pathname !== RouteStore.home) {
+            let pathname = window.location.pathname.split("/");
+            if (pathname.length > 1 && pathname[pathname.length - 2] === 'doc') {
+                CurrentUserUtils.MainDocId = pathname[pathname.length - 1];
+            }
+        }
+
+        library.add(faFont);
+        library.add(faImage);
+        library.add(faFilePdf);
+        library.add(faObjectGroup);
+        library.add(faTable);
+        library.add(faGlobeAsia);
+        library.add(faUndoAlt);
+        library.add(faRedoAlt);
+        library.add(faPenNib);
+        library.add(faFilm);
+        library.add(faMusic);
+        library.add(faTree);
+        this.initEventListeners();
+        this.initAuthenticationRouters();
+    }
+
+    initEventListeners = () => {
+        // window.addEventListener("pointermove", (e) => this.reportLocation(e))
+        window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler
+        window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler
+        window.addEventListener("keydown", (e) => {
+            if (e.key === "Escape") {
+                DragManager.AbortDrag();
+                SelectionManager.DeselectAll();
+            }
+        }, 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)) {
+                ContextMenu.Instance.clearItems();
+            }
+        }), true);
+    }
+
+    initAuthenticationRouters = async () => {
+        // Load the user's active workspace, or create a new one if initial session after signup
+        if (!CurrentUserUtils.MainDocId) {
+            const doc = await Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc);
+            if (doc) {
+                this.openWorkspace(doc);
+            } else {
+                this.createNewWorkspace();
+            }
+        } else {
+            DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field =>
+                field instanceof Doc ? this.openWorkspace(field) :
+                    this.createNewWorkspace(CurrentUserUtils.MainDocId));
+        }
+    }
+
+    @action
+    createNewWorkspace = async (id?: string) => {
+        const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
+        if (list) {
+            let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, title: `WS collection ${list.length + 1}` });
+            var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
+            let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` });
+            list.push(mainDoc);
+            // 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"], [], { title: "New Mobile Uploads" });
+                mainDoc.optionalRightCollection = pendingDocument;
+            }, 0);
+        }
+    }
+
+    @action
+    openWorkspace = async (doc: Doc, fromHistory = false) => {
+        CurrentUserUtils.MainDocId = doc[Id];
+        this.mainContainer = doc;
+        fromHistory || HistoryUtil.pushState({ type: "doc", docId: doc[Id], initializers: {} });
+        const col = await Cast(CurrentUserUtils.UserDocument.optionalRightCollection, Doc);
+        // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized)
+        setTimeout(async () => {
+            if (col) {
+                const l = Cast(col.data, listSpec(Doc));
+                if (l && l.length > 0) {
+                    CollectionDockingView.Instance.AddRightSplit(col);
+                }
+            }
+        }, 100);
+    }
+    @action
+    onResize = (r: any) => {
+        this.pwidth = r.offset.width;
+        this.pheight = r.offset.height;
+    }
+    getPWidth = () => {
+        return this.pwidth;
+    }
+    getPHeight = () => {
+        return this.pheight;
+    }
+    @computed
+    get mainContent() {
+        let mainCont = this.mainContainer;
+        let content = !mainCont ? (null) :
+            <DocumentView Document={mainCont}
+                toggleMinimized={emptyFunction}
+                addDocument={undefined}
+                addDocTab={emptyFunction}
+                removeDocument={undefined}
+                ScreenToLocalTransform={Transform.Identity}
+                ContentScaling={returnOne}
+                PanelWidth={this.getPWidth}
+                PanelHeight={this.getPHeight}
+                isTopMost={true}
+                selectOnLoad={false}
+                focus={emptyFunction}
+                parentActive={returnTrue}
+                whenActiveChanged={emptyFunction}
+                bringToFront={emptyFunction}
+                ContainingCollectionView={undefined} />;
+        const pres = mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined;
+        return <Measure offset onResize={this.onResize}>
+            {({ measureRef }) =>
+                <div ref={measureRef} id="mainContent-div">
+                    {content}
+                    {pres ? <PresentationView Document={pres} key="presentation" /> : null}
+                </div>
+            }
+        </Measure>;
+    }
+
+    /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
+    nodesMenu() {
+
+        let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
+        let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c27211_sample_explain.pdf";
+        let weburl = "https://cs.brown.edu/courses/cs166/";
+        let audiourl = "http://techslides.com/demos/samples/sample.mp3";
+        let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
+
+        let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
+        let addColNode = action(() => Docs.FreeformDocument([], { width: 200, height: 200, title: "a freeform 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" }));
+        let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
+        let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
+        let addWebNode = action(() => Docs.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
+        let addAudioNode = action(() => Docs.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
+
+        let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
+            [React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
+            [React.createRef<HTMLDivElement>(), "image", "Add Image", addImageNode],
+            [React.createRef<HTMLDivElement>(), "file-pdf", "Add PDF", addPDFNode],
+            [React.createRef<HTMLDivElement>(), "film", "Add Video", addVideoNode],
+            [React.createRef<HTMLDivElement>(), "music", "Add Audio", addAudioNode],
+            [React.createRef<HTMLDivElement>(), "globe-asia", "Add Web Clipping", addWebNode],
+            [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
+            [React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
+            [React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
+        ];
+
+        return < div id="add-nodes-menu" >
+            <input type="checkbox" id="add-menu-toggle" />
+            <label htmlFor="add-menu-toggle" title="Add Node"><p>+</p></label>
+
+            <div id="add-options-content">
+                <ul id="add-options-list">
+                    {btns.map(btn =>
+                        <li key={btn[1]} ><div ref={btn[0]}>
+                            <button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}>
+                                <FontAwesomeIcon icon={btn[1]} size="sm" />
+                            </button>
+                        </div></li>)}
+                </ul>
+            </div>
+        </div >;
+    }
+
+    /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
+    @computed
+    get miscButtons() {
+        let logoutRef = React.createRef<HTMLDivElement>();
+
+        return [
+            <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.altKey && DocServer.DeleteDatabase()}>Clear Database</button>,
+            <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>
+                <button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button>
+            </div >,
+            <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <SearchBox /> </div>,
+            <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
+                <button onClick={() => request.get(DocServer.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
+        ];
+
+    }
+
+    render() {
+        return (
+            <div id="main-div">
+                <DocumentDecorations />
+                {this.mainContent}
+                <PreviewCursor />
+                <ContextMenu />
+                {this.nodesMenu()}
+                {this.miscButtons}
+                <InkingControl />
+                <MainOverlayTextBox />
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index 7d0dc2913..9c37e9000 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -2,7 +2,6 @@ import { observer } from "mobx-react";
 import React = require("react");
 import { observable, action, runInAction, reaction } from "mobx";
 import "./PresentationView.scss";
-import "./Main.tsx";
 import { DocumentManager } from "../util/DocumentManager";
 import { Utils } from "../../Utils";
 import { Doc, DocListCast, DocListCastAsync } from "../../new_fields/Doc";
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 35a9f34cf..4cc4ae6b6 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -14,12 +14,12 @@ import { Doc, DocListCast } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/RefField';
 import { ContextMenu } from '../ContextMenu';
 import { undoBatch } from '../../util/UndoManager';
-import { Main } from '../Main';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { CollectionDockingView } from './CollectionDockingView';
 import { DocumentManager } from '../../util/DocumentManager';
 import { List } from '../../../new_fields/List';
 import { Docs } from '../../documents/Documents';
+import { MainView } from '../MainView';
 
 
 export interface TreeViewProps {
@@ -52,7 +52,7 @@ class TreeView extends React.Component<TreeViewProps> {
 
     @undoBatch openRight = async () => {
         if (this.props.document.dockingConfig) {
-            Main.Instance.openWorkspace(this.props.document);
+            MainView.Instance.openWorkspace(this.props.document);
         } else {
             CollectionDockingView.Instance.AddRightSplit(this.props.document);
         }
@@ -140,7 +140,7 @@ 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(() => Main.Instance.openWorkspace(this.props.document)) });
+            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,
@@ -215,7 +215,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     }
     onContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-            ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => Main.Instance.createNewWorkspace()) });
+            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)) });
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 4cac09dee..d9b07aac6 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -3,184 +3,186 @@ import "normalize.css";
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { observer } from 'mobx-react';
-
-// configure({
-//     enforceActions: "observed"
-// });
-
-// @observer
-// class FieldViewer extends React.Component<{ field: BasicField<any> }> {
-//     render() {
-//         return <span>{JSON.stringify(this.props.field.Data)} ({this.props.field.Id})</span>;
-//     }
-// }
-
-// @observer
-// class KeyViewer extends React.Component<{ field: Key }> {
-//     render() {
-//         return this.props.field.Name;
-//     }
-// }
-
-// @observer
-// class ListViewer extends React.Component<{ field: ListField<Field> }>{
-//     @observable
-//     expanded = false;
-
-//     render() {
-//         let content;
-//         if (this.expanded) {
-//             content = (
-//                 <div>
-//                     {this.props.field.Data.map(field => <DebugViewer fieldId={field.Id} key={field.Id} />)}
-//                 </div>
-//             );
-//         } else {
-//             content = <>[...] ({this.props.field.Id})</>;
-//         }
-//         return (
-//             <div>
-//                 <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
-//                 {content}
-//             </div >
-//         );
-//     }
-// }
-
-// @observer
-// class DocumentViewer extends React.Component<{ field: Document }> {
-//     private keyMap: ObservableMap<string, Key> = new ObservableMap;
-
-//     private disposer?: Lambda;
-
-//     componentDidMount() {
-//         let f = () => {
-//             Array.from(this.props.field._proxies.keys()).forEach(id => {
-//                 if (!this.keyMap.has(id)) {
-//                     Server.GetField(id, (field) => {
-//                         if (field && field instanceof Key) {
-//                             this.keyMap.set(id, field);
-//                         }
-//                     });
-//                 }
-//             });
-//         };
-//         this.disposer = this.props.field._proxies.observe(f);
-//         f();
-//     }
-
-//     componentWillUnmount() {
-//         if (this.disposer) {
-//             this.disposer();
-//         }
-//     }
-
-//     render() {
-//         let fields = Array.from(this.props.field._proxies.entries()).map(kv => {
-//             let key = this.keyMap.get(kv[0]);
-//             return (
-//                 <div key={kv[0]}>
-//                     <b>({key ? key.Name : kv[0]}): </b>
-//                     <DebugViewer fieldId={kv[1]}></DebugViewer>
-//                 </div>
-//             );
-//         });
-//         return (
-//             <div>
-//                 Document ({this.props.field.Id})
-//                 <div style={{ paddingLeft: "25px" }}>
-//                     {fields}
-//                 </div>
-//             </div>
-//         );
-//     }
-// }
-
-// @observer
-// class DebugViewer extends React.Component<{ fieldId: string }> {
-//     @observable
-//     private field?: Field;
-
-//     @observable
-//     private error?: string;
-
-//     constructor(props: { fieldId: string }) {
-//         super(props);
-//         this.update();
-//     }
-
-//     update() {
-//         Server.GetField(this.props.fieldId, action((field: Opt<Field>) => {
-//             this.field = field;
-//             if (!field) {
-//                 this.error = `Field with id ${this.props.fieldId} not found`;
-//             }
-//         }));
-
-//     }
-
-//     render() {
-//         let content;
-//         if (this.field) {
-//             // content = this.field.ToJson();
-//             if (this.field instanceof ListField) {
-//                 content = (<ListViewer field={this.field} />);
-//             } else if (this.field instanceof Document) {
-//                 content = (<DocumentViewer field={this.field} />);
-//             } else if (this.field instanceof BasicField) {
-//                 content = (<FieldViewer field={this.field} />);
-//             } else if (this.field instanceof Key) {
-//                 content = (<KeyViewer field={this.field} />);
-//             } else {
-//                 content = (<span>Unrecognized field type</span>);
-//             }
-//         } else if (this.error) {
-//             content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>;
-//         } else {
-//             content = <span>Field loading: {this.props.fieldId}</span>;
-//         }
-//         return content;
-//     }
-// }
-
-// @observer
-// class Viewer extends React.Component {
-//     @observable
-//     private idToAdd: string = '';
-
-//     @observable
-//     private ids: string[] = [];
-
-//     @action
-//     inputOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-//         this.idToAdd = e.target.value;
-//     }
-
-//     @action
-//     onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
-//         if (e.key === "Enter") {
-//             this.ids.push(this.idToAdd);
-//             this.idToAdd = "";
-//         }
-//     }
-
-//     render() {
-//         return (
-//             <>
-//                 <input value={this.idToAdd}
-//                     onChange={this.inputOnChange}
-//                     onKeyDown={this.onKeyPress} />
-//                 <div>
-//                     {this.ids.map(id => <DebugViewer fieldId={id} key={id}></DebugViewer>)}
-//                 </div>
-//             </>
-//         );
-//     }
-// }
-
-// ReactDOM.render((
-//     <div style={{ position: "absolute", width: "100%", height: "100%" }}>
-//         <Viewer />
-//     </div>),
-//     document.getElementById('root')
-// );
\ No newline at end of file
+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"
+});
+
+@observer
+class ListViewer extends React.Component<{ field: List<Field> }>{
+    @observable
+    expanded = false;
+
+    render() {
+        let content;
+        if (this.expanded) {
+            content = (
+                <div>
+                    {this.props.field.map((field, index) => <DebugViewer field={field} key={index} />)}
+                </div>
+            );
+        } else {
+            content = <>[...]</>;
+        }
+        return (
+            <div>
+                <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
+                {content}
+            </div >
+        );
+    }
+}
+
+@observer
+class DocumentViewer extends React.Component<{ field: Doc }> {
+    @observable
+    expanded = false;
+    render() {
+        let content;
+        if (this.expanded) {
+            const keys = Object.keys(this.props.field);
+            let fields = keys.map(key => {
+                return (
+                    <div key={key}>
+                        <b>({key}): </b>
+                        <DebugViewer field={this.props.field[key]}></DebugViewer>
+                    </div>
+                );
+            });
+            content = (
+                <div>
+                    Document ({this.props.field[Id]})
+                <div style={{ paddingLeft: "25px" }}>
+                        {fields}
+                    </div>
+                </div>
+            );
+        } else {
+            content = <>[...] ({this.props.field[Id]})</>;
+        }
+        return (
+            <div>
+                <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
+                {content}
+            </div >
+        );
+    }
+}
+
+@observer
+class DebugViewer extends React.Component<{ field: FieldResult }> {
+
+    render() {
+        let content;
+        const field = this.props.field;
+        if (field instanceof List) {
+            content = (<ListViewer field={field} />);
+        } else if (field instanceof Doc) {
+            content = (<DocumentViewer field={field} />);
+        } else if (typeof field === "string") {
+            content = <p>"{field}"</p>;
+        } else if (typeof field === "number" || typeof field === "boolean") {
+            content = <p>{field}</p>;
+        } else if (field instanceof URLField) {
+            content = <p>{field.url.href}</p>;
+        } else {
+            content = <p>Unrecognized field type</p>;
+        }
+        return content;
+    }
+}
+
+@observer
+class Viewer extends React.Component {
+    @observable
+    private idToAdd: string = '';
+
+    @observable
+    private fields: Field[] = [];
+
+    @action
+    inputOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        this.idToAdd = e.target.value;
+    }
+
+    @action
+    onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
+        if (e.key === "Enter") {
+            DocServer.GetRefField(this.idToAdd).then(action((field: any) => {
+                if (field !== undefined) {
+                    this.fields.push(field);
+                }
+            }));
+            this.idToAdd = "";
+        }
+    }
+
+    render() {
+        return (
+            <>
+                <input value={this.idToAdd}
+                    onChange={this.inputOnChange}
+                    onKeyDown={this.onKeyPress} />
+                <div>
+                    {this.fields.map((field, index) => <DebugViewer field={field} key={index}></DebugViewer>)}
+                </div>
+            </>
+        );
+    }
+}
+
+ReactDOM.render((
+    <div style={{ position: "absolute", width: "100%", height: "100%" }}>
+        <Viewer />
+    </div>),
+    document.getElementById('root')
+);
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2

