From 89ca54d2d335cf7d44107b03014333ee5501eb7b Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 10 May 2019 11:42:42 -0400
Subject: fixed search!

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

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 159815ea5..5ee943da9 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -412,7 +412,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                     parentActive={returnTrue}
                     whenActiveChanged={emptyFunction}
                     focus={emptyFunction}
-                    bringToFront={emptyFunction}
                     ContainingCollectionView={undefined} />
             </div >);
     }
-- 
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/collections/CollectionDockingView.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 f52f5a408c62b93ae79eb690ff86538a6fbf60ed Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sun, 12 May 2019 22:56:13 -0400
Subject: Started adding flyout to go up a level

---
 src/client/goldenLayout.js                         |  1 +
 .../views/collections/CollectionDockingView.tsx    | 18 +++++++++-
 .../views/collections/ParentDocumentSelector.scss  |  8 +++++
 .../views/collections/ParentDocumentSelector.tsx   | 39 ++++++++++++++++++++++
 src/server/Search.ts                               |  3 +-
 5 files changed, 67 insertions(+), 2 deletions(-)
 create mode 100644 src/client/views/collections/ParentDocumentSelector.scss
 create mode 100644 src/client/views/collections/ParentDocumentSelector.tsx

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

diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index 56a71f1ac..ab2bcefed 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -2902,6 +2902,7 @@
 		 * @returns {void}
 		 */
         _$destroy: function () {
+            this._layoutManager.emit('tabDestroyed', this);
             this.element.off('mousedown touchstart', this._onTabClickFn);
             this.closeElement.off('click touchstart', this._onCloseClickFn);
             if (this._dragListener) {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index efcee9c02..8739a213f 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -18,6 +18,7 @@ import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionDockingView.scss";
 import { SubCollectionViewProps } from "./CollectionSubView";
 import React = require("react");
+import { ParentDocSelector } from './ParentDocumentSelector';
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -157,6 +158,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 try {
                     this._goldenLayout.unbind('itemDropped', this.itemDropped);
                     this._goldenLayout.unbind('tabCreated', this.tabCreated);
+                    this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
                     this._goldenLayout.unbind('stackCreated', this.stackCreated);
                 } catch (e) { }
                 this._goldenLayout.destroy();
@@ -164,6 +166,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             }
             this._goldenLayout.on('itemDropped', this.itemDropped);
             this._goldenLayout.on('tabCreated', this.tabCreated);
+            this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
             this._goldenLayout.on('stackCreated', this.stackCreated);
             this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
             this._goldenLayout.container = this._containerRef.current;
@@ -196,6 +199,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             this._goldenLayout.unbind('itemDropped', this.itemDropped);
             this._goldenLayout.unbind('tabCreated', this.tabCreated);
             this._goldenLayout.unbind('stackCreated', this.stackCreated);
+            this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
         } catch (e) {
 
         }
@@ -292,8 +296,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             }
             DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async doc => {
                 if (doc instanceof Doc) {
-                    let counter: any = this.htmlToElement(`<div class="messageCounter">0</div>`);
+                    let counter: any = this.htmlToElement(`<span class="messageCounter">0</div>`);
                     tab.element.append(counter);
+                    let upDiv = document.createElement("span");
+                    ReactDOM.render(<ParentDocSelector Document={doc} />, upDiv);
+                    tab.reactComponents = [upDiv];
+                    tab.element.append(upDiv);
                     counter.DashDocId = tab.contentItem.config.props.documentId;
                     tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title],
                         () => {
@@ -320,6 +328,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 tab.contentItem.remove();
             });
     }
+
+    tabDestroyed = (tab: any) => {
+        if (tab.reactComponents) {
+            for (const ele of tab.reactComponents) {
+                ReactDOM.unmountComponentAtNode(ele);
+            }
+        }
+    }
     _removedDocs: Doc[] = [];
 
     stackCreated = (stack: any) => {
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss
new file mode 100644
index 000000000..f3c605f3e
--- /dev/null
+++ b/src/client/views/collections/ParentDocumentSelector.scss
@@ -0,0 +1,8 @@
+.PDS-flyout {
+    position: absolute;
+    z-index: 9999;
+    background-color: #d3d3d3;
+    box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
+    min-width: 150px;
+    color: black;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
new file mode 100644
index 000000000..1fdb9d4d9
--- /dev/null
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -0,0 +1,39 @@
+import * as React from "react";
+import './ParentDocumentSelector.scss';
+import { Doc } from "../../../new_fields/Doc";
+import { observer } from "mobx-react";
+import { observable, action } from "mobx";
+
+@observer
+export class ParentDocSelector extends React.Component<{ Document: Doc }> {
+    @observable hover = false;
+
+    @action
+    onMouseLeave = () => {
+        this.hover = false;
+    }
+
+    @action
+    onMouseEnter = () => {
+        this.hover = true;
+    }
+
+    render() {
+        let flyout;
+        if (this.hover) {
+            flyout = (
+                <div className="PDS-flyout">
+                    <p>Hello world</p>
+                </div>
+            );
+        }
+        return (
+            <span style={{ position: "relative", display: "inline-block" }}
+                onMouseEnter={this.onMouseEnter}
+                onMouseLeave={this.onMouseLeave}>
+                <p>^</p>
+                {flyout}
+            </span>
+        );
+    }
+}
diff --git a/src/server/Search.ts b/src/server/Search.ts
index 0f7004bdf..5ca5578a7 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -22,7 +22,8 @@ export class Search {
         try {
             const searchResults = JSON.parse(await rp.get(this.url + "dash/select", {
                 qs: {
-                    q: query
+                    q: query,
+                    fl: "id"
                 }
             }));
             const fields = searchResults.response.docs;
-- 
cgit v1.2.3-70-g09d2


From 48b0f98ea2519d861a0eecee541dc0986a2c2f12 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 13 May 2019 10:56:12 -0400
Subject: small fixes

---
 src/client/util/DocumentManager.ts                 | 10 +++++++---
 src/client/views/InkingControl.tsx                 |  9 +++------
 .../views/collections/CollectionDockingView.tsx    | 22 +++++++++++++++-------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  6 ++----
 src/new_fields/Doc.ts                              | 12 +++++++++++-
 6 files changed, 39 insertions(+), 22 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 9a7a94228..47bcb153f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -5,6 +5,7 @@ import { FieldValue, Cast, NumCast, BoolCast } from '../../new_fields/Types';
 import { listSpec } from '../../new_fields/Schema';
 import { undoBatch } from './UndoManager';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { Id } from '../../new_fields/RefField';
 
 
 export class DocumentManager {
@@ -26,13 +27,13 @@ export class DocumentManager {
         // this.DocumentViews = new Array<DocumentView>();
     }
 
-    public getDocumentView(toFind: Doc): DocumentView | null {
+    public getDocumentViewById(id: string): DocumentView | null {
 
         let toReturn: DocumentView | null = null;
 
         //gets document view that is in a freeform canvas collection
         DocumentManager.Instance.DocumentViews.map(view => {
-            if (view.props.Document === toFind) {
+            if (view.props.Document[Id] === id) {
                 toReturn = view;
                 return;
             }
@@ -40,7 +41,7 @@ export class DocumentManager {
         if (!toReturn) {
             DocumentManager.Instance.DocumentViews.map(view => {
                 let doc = view.props.Document.proto;
-                if (doc && Object.is(doc, toFind)) {
+                if (doc && doc.Id === id) {
                     toReturn = view;
                 }
             });
@@ -48,6 +49,9 @@ export class DocumentManager {
 
         return toReturn;
     }
+
+    public getDocumentView(toFind: Doc): DocumentView | null { return this.getDocumentViewById(toFind[Id]); }
+
     public getDocumentViews(toFind: Doc): DocumentView[] {
 
         let toReturn: DocumentView[] = [];
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 4b3dbd4e0..17d4a1e49 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -35,12 +35,9 @@ export class InkingControl extends React.Component {
     @action
     switchColor = (color: ColorResult): void => {
         this._selectedColor = color.hex;
-        if (SelectionManager.SelectedDocuments().length === 1) {
-            var sdoc = SelectionManager.SelectedDocuments()[0];
-            if (sdoc.props.ContainingCollectionView) {
-                Doc.SetOnPrototype(sdoc.props.Document, "backgroundColor", color.hex);
-            }
-        }
+        SelectionManager.SelectedDocuments().forEach(doc =>
+            doc.props.ContainingCollectionView && Doc.SetOnPrototype(doc.props.Document, "backgroundColor", color.hex)
+        );
     }
 
     @action
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 8739a213f..0aa067972 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -19,6 +19,7 @@ import "./CollectionDockingView.scss";
 import { SubCollectionViewProps } from "./CollectionSubView";
 import React = require("react");
 import { ParentDocSelector } from './ParentDocumentSelector';
+import { DocumentManager } from '../../util/DocumentManager';
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -73,26 +74,33 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     @undoBatch
     @action
-    public CloseRightSplit(document: Doc) {
+    public CloseRightSplit(document: Doc): boolean {
+        let retVal = false;
         if (this._goldenLayout.root.contentItems[0].isRow) {
-            this._goldenLayout.root.contentItems[0].contentItems.map((child: any, i: number) => {
+            retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
                 if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
-                    child.contentItems[0].config.props.documentId == document[Id]) {
+                    Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) {
                     child.contentItems[0].remove();
                     this.layoutChanged(document);
-                    this.stateChanged();
+                    return true;
                 } else
-                    child.contentItems.map((tab: any, j: number) => {
-                        if (tab.config.component === "DocumentFrameRenderer" && tab.config.props.documentId === document[Id]) {
+                    Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
+                        if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {
                             child.contentItems[j].remove();
                             child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
                             let docs = Cast(this.props.Document.data, listSpec(Doc));
                             docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1);
-                            this.stateChanged();
+                            return true;
                         }
+                        return false;
                     });
+                return false;
             })
         }
+        if (retVal) {
+            this.stateChanged();
+        }
+        return retVal;
     }
 
     @action
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 080c484f4..1c29ebaae 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -79,7 +79,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     y += 40 * this.props.getTransform().Scale;
                 })
             })();
-        } else if (e.key === "t" && e.ctrlKey) {
+        } else if (e.key === "b" && e.ctrlKey) {
             //heuristically converts pasted text into a table.
             // assumes each entry is separated by a tab
             // skips all rows until it gets to a row with more than one entry
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 817a23ce8..2a041bf70 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -189,10 +189,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                             SelectionManager.DeselectAll();
                             expandedDocs.forEach(maxDoc => {
                                 maxDoc.isMinimized = false;
-                                if (!dataDocs || dataDocs.indexOf(maxDoc) == -1) {
-                                    CollectionDockingView.Instance.AddRightSplit(maxDoc);
-                                } else {
-                                    CollectionDockingView.Instance.CloseRightSplit(maxDoc);
+                                if (!CollectionDockingView.Instance.CloseRightSplit(maxDoc)) {
+                                    CollectionDockingView.Instance.AddRightSplit(Doc.MakeCopy(maxDoc));
                                 }
                             });
                         }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 625ba0d6a..a8c9d28e1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -134,7 +134,8 @@ export namespace Doc {
         return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
     }
     export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
-        const proto = doc.proto;
+        const proto = Object.getOwnPropertyNames(doc).indexOf("isProto") == -1 ? doc.proto : doc;
+
         if (proto) {
             proto[key] = value;
         }
@@ -160,6 +161,15 @@ export namespace Doc {
         return doc;
     }
 
+    // compare whether documents or their protos match
+    export function AreProtosEqual(doc: Doc, other: Doc) {
+        let r = (doc[Id] === other[Id]);
+        let r2 = (doc.proto && doc.proto.Id === other[Id]);
+        let r3 = (other.proto && other.proto.Id === doc[Id]);
+        let r4 = (doc.proto && other.proto && doc.proto[Id] === other.proto[Id]);
+        return r || r2 || r3 || r4 ? true : false;
+    }
+
     export function MakeAlias(doc: Doc) {
         const alias = new Doc;
 
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From 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/collections/CollectionDockingView.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 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/collections/CollectionDockingView.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

