From ae62e415d18b83fb1ef6b4a8f438b1d7e8d3157a Mon Sep 17 00:00:00 2001
From: HJF Bulterman <henri_bulterman@brown.edu>
Date: Fri, 9 Aug 2019 11:31:57 -0400
Subject: initial commit - individual presentation document view showing but
 with errors

---
 src/client/views/MainView.tsx | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f5a6715e5..14cfc6792 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -58,7 +58,8 @@ export class MainView extends React.Component {
     private set mainContainer(doc: Opt<Doc>) {
         if (doc) {
             if (!("presentationView" in doc)) {
-                doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })]);
+                let initialDoc = Docs.Create.TreeDocument([], { title: "Presentation" });
+                doc.presentationView = Docs.Create.PresDocument(new List<Doc>([initialDoc]));
             }
             CurrentUserUtils.UserDocument.activeWorkspace = doc;
         }
@@ -384,11 +385,13 @@ export class MainView extends React.Component {
         let addButtonDocument = action(() => Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" }));
         let addImportCollectionNode = action(() => Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 }));
 
+        let addPresentationNode = action(() => Docs.Create.PresDocument(new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })])));
+
         let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
             [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
             [React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
             // [React.createRef<HTMLDivElement>(), "clone", "Add Docking Frame", addDockingNode],
-            [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode],
+            [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addPresentationNode], //remove at some point in favor of addImportCollectionNode
         ];
         if (!ClientUtils.RELEASE) btns.unshift([React.createRef<HTMLDivElement>(), "cat", "Add Cat Image", addImageNode]);
 
-- 
cgit v1.2.3-70-g09d2


From 0af800d2edf120ab2a20842ed67ea7d7616996b9 Mon Sep 17 00:00:00 2001
From: HJF Bulterman <henri_bulterman@brown.edu>
Date: Fri, 9 Aug 2019 15:37:49 -0400
Subject: working for about 85%

---
 src/client/views/MainView.tsx                      | 27 +++++++++++++++++-----
 .../views/collections/CollectionDockingView.tsx    |  9 ++++++++
 src/client/views/nodes/DocumentView.tsx            |  3 ++-
 src/client/views/nodes/PresBox.tsx                 |  7 +++---
 4 files changed, 36 insertions(+), 10 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 14cfc6792..cb81f9aad 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -247,7 +247,6 @@ export class MainView extends React.Component {
     @computed get dockingContent() {
         let flyoutWidth = this.flyoutWidth;
         let mainCont = this.mainContainer;
-        let castRes = mainCont ? FieldValue(Cast(mainCont.presentationView, listSpec(Doc))) : undefined;
         return <Measure offset onResize={this.onResize}>
             {({ measureRef }) =>
                 <div ref={measureRef} id="mainContent-div" style={{ width: `calc(100% - ${flyoutWidth}px`, transform: `translate(${flyoutWidth}px, 0px)` }} onDrop={this.onDrop}>
@@ -271,7 +270,7 @@ export class MainView extends React.Component {
                             zoomToScale={emptyFunction}
                             getScale={returnOne}
                         />}
-                    {castRes ? <PresentationView Documents={castRes} key="presentation" /> : null}
+                    {/* {presentationDoc ? <PresentationView Documents={Cast(presentationDoc.data, listSpec(Doc))!} key="presentation" /> : null} */}
                 </div>
             }
         </Measure>;
@@ -385,13 +384,11 @@ export class MainView extends React.Component {
         let addButtonDocument = action(() => Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" }));
         let addImportCollectionNode = action(() => Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 }));
 
-        let addPresentationNode = action(() => Docs.Create.PresDocument(new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })])));
-
         let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
             [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
             [React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
             // [React.createRef<HTMLDivElement>(), "clone", "Add Docking Frame", addDockingNode],
-            [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addPresentationNode], //remove at some point in favor of addImportCollectionNode
+            [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode
         ];
         if (!ClientUtils.RELEASE) btns.unshift([React.createRef<HTMLDivElement>(), "cat", "Add Cat Image", addImageNode]);
 
@@ -402,7 +399,7 @@ export class MainView extends React.Component {
             <div id="add-options-content">
                 <ul id="add-options-list">
                     <li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li>
-                    <li key="presentation"><button className="add-button round-button" title="Open Presentation View" onClick={() => PresentationView.Instance.toggle(undefined)}><FontAwesomeIcon icon="table" size="sm" /></button></li>
+                    <li key="presentation"><button className="add-button round-button" title="Open Presentation View" onClick={this.togglePresentationView}><FontAwesomeIcon icon="table" size="sm" /></button></li>
                     <li key="undo"><button className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
                     <li key="redo"><button className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
                     {btns.map(btn =>
@@ -453,6 +450,24 @@ export class MainView extends React.Component {
         this.isSearchVisible = !this.isSearchVisible;
     }
 
+    togglePresentationView = () => {
+        let presDoc = this.presentationDoc;
+        if (!presDoc) {
+            return;
+        }
+        let isOpen = CollectionDockingView.Instance.Has(presDoc);
+        if (isOpen) {
+            CollectionDockingView.Instance.CloseRightSplit(presDoc);
+        } else {
+            CollectionDockingView.Instance.AddRightSplit(presDoc, undefined);
+        }
+    }
+
+    private get presentationDoc() {
+        let mainCont = this.mainContainer;
+        return mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined;
+    }
+
     render() {
         return (
             <div id="main-div">
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1859ebee7..bd83a46a3 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -162,6 +162,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         this.stateChanged();
     }
 
+    public Has = (document: Doc) => {
+        let docs = Cast(this.props.Document.data, listSpec(Doc));
+        if (!docs) {
+            return false;
+        }
+        return docs.includes(document);
+    }
+
     //
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
     //
@@ -525,6 +533,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
         this.props.glContainer.on("tab", this.onActiveContentItemChanged);
         this.onActiveContentItemChanged();
+        // setTimeout(() => MainView.Instance.openPresentationView(), 2000);
     }
 
     componentWillUnmount() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0ca303dde..6d4c18050 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -41,6 +41,7 @@ import { ClientUtils } from '../../util/ClientUtils';
 import { EditableView } from '../EditableView';
 import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons';
 import { DocumentDecorations } from '../DocumentDecorations';
+import { PresBox } from './PresBox';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
@@ -553,7 +554,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
         cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
         cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
-        cm.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" }); //change this to
+        cm.addItem({ description: "Pin to Presentation", event: () => PresBox.Instance.PinDoc(this.props.Document), icon: "map-pin" }); //this should work, and it does! A miracle!
         cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
         let makes: ContextMenuProps[] = [];
         makes.push({ description: "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 2feb32693..8316c4469 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -32,7 +32,7 @@ export interface PresViewProps {
     Documents: List<Doc>;
 }
 
-const expandedWidth = 400;
+const expandedWidth = 450;
 
 @observer
 export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
@@ -825,10 +825,11 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
 
     render() {
 
-        let width = NumCast(this.curPresentation.width);
+        let width = "100%"; //NumCast(this.curPresentation.width)
+        console.log("The width is: " + width);
 
         return (
-            <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflow: "hidden", opacity: this.opacity, transition: "0.7s opacity ease" }}>
+            <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflow: "hidden", opacity: this.opacity, transition: "0.7s opacity ease", pointerEvents: "all" }}>
                 <div className="presentationView-heading">
                     {this.renderSelectOrPresSelection()}
                     <button title="Close Presentation" className='presentation-icon' onClick={this.closePresentation}><FontAwesomeIcon icon={"times"} /></button> {/**this.closePresentation  CLICK does not work?! Also without the*/}
-- 
cgit v1.2.3-70-g09d2


From d271205a94bcb2ebc524b70f8a3ff98398e69252 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Mon, 12 Aug 2019 21:39:16 -0400
Subject: exporting text to google docs almost supported

---
 package.json                                       |   1 +
 src/client/DocServer.ts                            |   1 +
 .../apis/google_docs/GoogleApiClientUtils.ts       |  85 ++++++++++
 src/client/views/MainView.tsx                      |  11 +-
 src/server/Message.ts                              |   1 +
 src/server/RouteStore.ts                           |   3 +-
 src/server/apis/google/GoogleApiServerUtils.ts     | 109 +++++++++++++
 src/server/apis/youtube/youtubeApiSample.d.ts      |   2 +
 src/server/apis/youtube/youtubeApiSample.js        | 179 +++++++++++++++++++++
 .../credentials/google_docs_credentials.json       |   1 +
 src/server/credentials/google_docs_token.json      |   1 +
 src/server/index.ts                                |  33 +++-
 src/server/youtubeApi/youtubeApiSample.d.ts        |   2 -
 src/server/youtubeApi/youtubeApiSample.js          | 179 ---------------------
 14 files changed, 418 insertions(+), 190 deletions(-)
 create mode 100644 src/client/apis/google_docs/GoogleApiClientUtils.ts
 create mode 100644 src/server/apis/google/GoogleApiServerUtils.ts
 create mode 100644 src/server/apis/youtube/youtubeApiSample.d.ts
 create mode 100644 src/server/apis/youtube/youtubeApiSample.js
 create mode 100644 src/server/credentials/google_docs_credentials.json
 create mode 100644 src/server/credentials/google_docs_token.json
 delete mode 100644 src/server/youtubeApi/youtubeApiSample.d.ts
 delete mode 100644 src/server/youtubeApi/youtubeApiSample.js

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

diff --git a/package.json b/package.json
index d699d1e6f..326468ab9 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,7 @@
         "@types/express-session": "^1.15.12",
         "@types/express-validator": "^3.0.0",
         "@types/formidable": "^1.0.31",
+        "@types/gapi": "0.0.39",
         "@types/jquery": "^3.3.29",
         "@types/jquery-awesome-cursor": "^0.3.0",
         "@types/jsonwebtoken": "^8.3.2",
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index bf5168c22..47aff2bb6 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -5,6 +5,7 @@ import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
 import { RefField } from '../new_fields/RefField';
 import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
+import { docs_v1 } from 'googleapis';
 
 /**
  * This class encapsulates the transfer and cross-client synchronization of
diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
new file mode 100644
index 000000000..42e621f6b
--- /dev/null
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -0,0 +1,85 @@
+import { docs_v1 } from "googleapis";
+import { PostToServer } from "../../../Utils";
+import { RouteStore } from "../../../server/RouteStore";
+
+export namespace GoogleApiClientUtils {
+
+    export namespace Docs {
+
+        export enum Actions {
+            Create = "create",
+            Retrieve = "retrieve"
+        }
+
+        export namespace Helpers {
+
+            export const fromRgb = (red: number, green: number, blue: number) => {
+                return { color: { rgbColor: { red, green, blue } } };
+            };
+
+        }
+
+        export const ExampleDocumentSchema = {
+            title: "This is a Google Doc Created From Dash Web",
+            body: {
+                content: [
+                    {
+                        paragraph: {
+                            elements: [
+                                {
+                                    textRun: {
+                                        content: "And this is its bold, blue text!!!",
+                                        textStyle: {
+                                            bold: true,
+                                            backgroundColor: Helpers.fromRgb(0, 0, 1)
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                ] as docs_v1.Schema$StructuralElement[]
+            }
+        } as docs_v1.Schema$Document;
+
+        /**
+         * After following the authentication routine, which connects this API call to the current signed in account
+         * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which
+         * should appear in the user's Google Doc library instantaneously.
+         * 
+         * @param schema whatever subset of a docs_v1.Schema$Document is required to properly initialize your
+         * Google Doc. This schema defines all aspects of a Google Doc, from the title to headers / footers to the
+         * actual document body and its styling!
+         * @returns the documentId of the newly generated document, or undefined if the creation process fails.
+         */
+        export const Create = async (schema?: docs_v1.Schema$Document): Promise<string | undefined> => {
+            let path = RouteStore.googleDocs + Actions.Create;
+            let parameters = { requestBody: schema || ExampleDocumentSchema };
+            let generatedId: string | undefined;
+            try {
+                generatedId = await PostToServer(path, parameters);
+            } catch (e) {
+                console.error(e);
+                generatedId = undefined;
+            } finally {
+                return generatedId;
+            }
+        };
+
+        let path = RouteStore.googleDocs + Actions.Retrieve;
+        export const Retrieve = async (documentId: string): Promise<any> => {
+            let parameters = { documentId };
+            let documentContents: any;
+            try {
+                documentContents = await PostToServer(path, parameters);
+            } catch (e) {
+                console.error(e);
+                documentContents = undefined;
+            } finally {
+                return documentContents;
+            }
+        };
+
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 031478477..0a987de4a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -15,7 +15,7 @@ import { listSpec } from '../../new_fields/Schema';
 import { Cast, FieldValue, NumCast, BoolCast, StrCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
-import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils';
+import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString, PostToServer } from '../../Utils';
 import { DocServer } from '../DocServer';
 import { Docs } from '../documents/Documents';
 import { SetupDrag } from '../util/DragManager';
@@ -40,6 +40,8 @@ import { CollectionTreeView } from './collections/CollectionTreeView';
 import { ClientUtils } from '../util/ClientUtils';
 import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField';
 import { DictationManager } from '../util/DictationManager';
+import { docs_v1 } from 'googleapis';
+import { GoogleApiClientUtils } from '../apis/google_docs/GoogleApiClientUtils';
 
 @observer
 export class MainView extends React.Component {
@@ -130,6 +132,8 @@ export class MainView extends React.Component {
         window.removeEventListener("keydown", KeyManager.Instance.handle);
         window.addEventListener("keydown", KeyManager.Instance.handle);
 
+        GoogleApiClientUtils.Docs.Create().then(id => console.log(id));
+
         reaction(() => {
             let workspaces = CurrentUserUtils.UserDocument.workspaces;
             let recent = CurrentUserUtils.UserDocument.recentlyClosed;
@@ -569,6 +573,11 @@ export class MainView extends React.Component {
     render() {
         return (
             <div id="main-div">
+                <input style={{ position: "absolute", zIndex: 100000 }} onKeyPress={e => {
+                    if (e.which === 13) {
+                        GoogleApiClientUtils.Docs.Retrieve(e.currentTarget.value.trim()).then((res: any) => console.log(res));
+                    }
+                }} />
                 {this.dictationOverlay}
                 <DocumentDecorations />
                 {this.mainContent}
diff --git a/src/server/Message.ts b/src/server/Message.ts
index aaee143e8..4ec390ade 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,4 +1,5 @@
 import { Utils } from "../Utils";
+import { google, docs_v1 } from "googleapis";
 
 export class Message<T> {
     private _name: string;
diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts
index e30015e39..5d977006a 100644
--- a/src/server/RouteStore.ts
+++ b/src/server/RouteStore.ts
@@ -30,6 +30,7 @@ export enum RouteStore {
     reset = "/reset/:token",
 
     // APIS
-    cognitiveServices = "/cognitiveservices"
+    cognitiveServices = "/cognitiveservices",
+    googleDocs = "/googleDocs/"
 
 }
\ No newline at end of file
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
new file mode 100644
index 000000000..817b2b696
--- /dev/null
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -0,0 +1,109 @@
+import { google, docs_v1 } from "googleapis";
+import { createInterface } from "readline";
+import { readFile, writeFile } from "fs";
+import { OAuth2Client } from "google-auth-library";
+
+/**
+ * Server side authentication for Google Api queries.
+ */
+export namespace GoogleApiServerUtils {
+
+    // If modifying these scopes, delete token.json.
+    const prefix = 'https://www.googleapis.com/auth/';
+    const SCOPES = [
+        'documents.readonly',
+        'documents',
+        'drive',
+        'drive.file',
+    ];
+    // The file token.json stores the user's access and refresh tokens, and is
+    // created automatically when the authorization flow completes for the first
+    // time.
+    export const parseBuffer = (data: Buffer) => JSON.parse(data.toString());
+
+    export namespace Docs {
+
+        export interface CredentialPaths {
+            credentials: string;
+            token: string;
+        }
+
+        export type Endpoint = docs_v1.Docs;
+
+        export const GetEndpoint = async (paths: CredentialPaths) => {
+            return new Promise<Endpoint>((resolve, reject) => {
+                readFile(paths.credentials, (err, credentials) => {
+                    if (err) {
+                        reject(err);
+                        return console.log('Error loading client secret file:', err);
+                    }
+                    return authorize(parseBuffer(credentials), paths.token).then(auth => {
+                        resolve(google.docs({ version: "v1", auth }));
+                    });
+                });
+            });
+        };
+
+    }
+
+    /**
+     * Create an OAuth2 client with the given credentials, and returns the promise resolving to the authenticated client
+     * @param {Object} credentials The authorization client credentials.
+     */
+    export function authorize(credentials: any, token_path: string): Promise<OAuth2Client> {
+        const { client_secret, client_id, redirect_uris } = credentials.installed;
+        const oAuth2Client = new google.auth.OAuth2(
+            client_id, client_secret, redirect_uris[0]);
+
+        return new Promise<OAuth2Client>((resolve, reject) => {
+            readFile(token_path, (err, token) => {
+                // Check if we have previously stored a token.
+                if (err) {
+                    return getNewToken(oAuth2Client, token_path).then(resolve, reject);
+                }
+                oAuth2Client.setCredentials(parseBuffer(token));
+                resolve(oAuth2Client);
+            });
+        });
+    }
+
+    /**
+     * Get and store new token after prompting for user authorization, and then
+     * execute the given callback with the authorized OAuth2 client.
+     * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
+     * @param {getEventsCallback} callback The callback for the authorized client.
+     */
+    function getNewToken(oAuth2Client: OAuth2Client, token_path: string) {
+        return new Promise<OAuth2Client>((resolve, reject) => {
+            const authUrl = oAuth2Client.generateAuthUrl({
+                access_type: 'offline',
+                scope: SCOPES.map(relative => prefix + relative),
+            });
+            console.log('Authorize this app by visiting this url:', authUrl);
+            const rl = createInterface({
+                input: process.stdin,
+                output: process.stdout,
+            });
+            rl.question('Enter the code from that page here: ', (code) => {
+                rl.close();
+                oAuth2Client.getToken(code, (err, token) => {
+                    if (err || !token) {
+                        reject(err);
+                        return console.error('Error retrieving access token', err);
+                    }
+                    oAuth2Client.setCredentials(token);
+                    // Store the token to disk for later program executions
+                    writeFile(token_path, JSON.stringify(token), (err) => {
+                        if (err) {
+                            console.error(err);
+                            reject(err);
+                        }
+                        console.log('Token stored to', token_path);
+                    });
+                    resolve(oAuth2Client);
+                });
+            });
+        });
+    }
+
+}
\ No newline at end of file
diff --git a/src/server/apis/youtube/youtubeApiSample.d.ts b/src/server/apis/youtube/youtubeApiSample.d.ts
new file mode 100644
index 000000000..427f54608
--- /dev/null
+++ b/src/server/apis/youtube/youtubeApiSample.d.ts
@@ -0,0 +1,2 @@
+declare const YoutubeApi: any;
+export = YoutubeApi;
\ No newline at end of file
diff --git a/src/server/apis/youtube/youtubeApiSample.js b/src/server/apis/youtube/youtubeApiSample.js
new file mode 100644
index 000000000..50b3c7b38
--- /dev/null
+++ b/src/server/apis/youtube/youtubeApiSample.js
@@ -0,0 +1,179 @@
+const fs = require('fs');
+const readline = require('readline');
+const { google } = require('googleapis');
+const OAuth2 = google.auth.OAuth2;
+
+
+// If modifying these scopes, delete your previously saved credentials
+// at ~/.credentials/youtube-nodejs-quickstart.json
+let SCOPES = ['https://www.googleapis.com/auth/youtube.readonly'];
+let TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
+    process.env.USERPROFILE) + '/.credentials/';
+let TOKEN_PATH = TOKEN_DIR + 'youtube-nodejs-quickstart.json';
+
+module.exports.readApiKey = (callback) => {
+    fs.readFile('client_secret.json', function processClientSecrets(err, content) {
+        if (err) {
+            console.log('Error loading client secret file: ' + err);
+            return;
+        }
+        callback(content);
+    });
+}
+
+module.exports.authorizedGetChannel = (apiKey) => {
+    //this didnt get called
+    // Authorize a client with the loaded credentials, then call the YouTube API.
+    authorize(JSON.parse(apiKey), getChannel);
+}
+
+module.exports.authorizedGetVideos = (apiKey, userInput, callBack) => {
+    authorize(JSON.parse(apiKey), getVideos, { userInput: userInput, callBack: callBack });
+}
+
+module.exports.authorizedGetVideoDetails = (apiKey, videoIds, callBack) => {
+    authorize(JSON.parse(apiKey), getVideoDetails, { videoIds: videoIds, callBack: callBack });
+}
+
+
+/**
+ * Create an OAuth2 client with the given credentials, and then execute the
+ * given callback function.
+ *
+ * @param {Object} credentials The authorization client credentials.
+ * @param {function} callback The callback to call with the authorized client.
+ */
+function authorize(credentials, callback, args = {}) {
+    let clientSecret = credentials.installed.client_secret;
+    let clientId = credentials.installed.client_id;
+    let redirectUrl = credentials.installed.redirect_uris[0];
+    let oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl);
+
+    // Check if we have previously stored a token.
+    fs.readFile(TOKEN_PATH, function (err, token) {
+        if (err) {
+            getNewToken(oauth2Client, callback);
+        } else {
+            oauth2Client.credentials = JSON.parse(token);
+            callback(oauth2Client, args);
+        }
+    });
+}
+
+/**
+ * Get and store new token after prompting for user authorization, and then
+ * execute the given callback with the authorized OAuth2 client.
+ *
+ * @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
+ * @param {getEventsCallback} callback The callback to call with the authorized
+ *     client.
+ */
+function getNewToken(oauth2Client, callback) {
+    var authUrl = oauth2Client.generateAuthUrl({
+        access_type: 'offline',
+        scope: SCOPES
+    });
+    console.log('Authorize this app by visiting this url: ', authUrl);
+    var rl = readline.createInterface({
+        input: process.stdin,
+        output: process.stdout
+    });
+    rl.question('Enter the code from that page here: ', function (code) {
+        rl.close();
+        oauth2Client.getToken(code, function (err, token) {
+            if (err) {
+                console.log('Error while trying to retrieve access token', err);
+                return;
+            }
+            oauth2Client.credentials = token;
+            storeToken(token);
+            callback(oauth2Client);
+        });
+    });
+}
+
+/**
+ * Store token to disk be used in later program executions.
+ *
+ * @param {Object} token The token to store to disk.
+ */
+function storeToken(token) {
+    try {
+        fs.mkdirSync(TOKEN_DIR);
+    } catch (err) {
+        if (err.code != 'EEXIST') {
+            throw err;
+        }
+    }
+    fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
+        if (err) throw err;
+        console.log('Token stored to ' + TOKEN_PATH);
+    });
+    console.log('Token stored to ' + TOKEN_PATH);
+}
+
+/**
+ * Lists the names and IDs of up to 10 files.
+ *
+ * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
+ */
+function getChannel(auth) {
+    var service = google.youtube('v3');
+    service.channels.list({
+        auth: auth,
+        part: 'snippet,contentDetails,statistics',
+        forUsername: 'GoogleDevelopers'
+    }, function (err, response) {
+        if (err) {
+            console.log('The API returned an error: ' + err);
+            return;
+        }
+        var channels = response.data.items;
+        if (channels.length == 0) {
+            console.log('No channel found.');
+        } else {
+            console.log('This channel\'s ID is %s. Its title is \'%s\', and ' +
+                'it has %s views.',
+                channels[0].id,
+                channels[0].snippet.title,
+                channels[0].statistics.viewCount);
+        }
+    });
+}
+
+function getVideos(auth, args) {
+    let service = google.youtube('v3');
+    service.search.list({
+        auth: auth,
+        part: 'id, snippet',
+        type: 'video',
+        q: args.userInput,
+        maxResults: 10
+    }, function (err, response) {
+        if (err) {
+            console.log('The API returned an error: ' + err);
+            return;
+        }
+        let videos = response.data.items;
+        args.callBack(videos);
+    });
+}
+
+function getVideoDetails(auth, args) {
+    if (args.videoIds === undefined) {
+        return;
+    }
+    let service = google.youtube('v3');
+    service.videos.list({
+        auth: auth,
+        part: 'contentDetails, statistics',
+        id: args.videoIds
+    }, function (err, response) {
+        if (err) {
+            console.log('The API returned an error from details: ' + err);
+            return;
+        }
+        let videoDetails = response.data.items;
+        args.callBack(videoDetails);
+    });
+}
\ No newline at end of file
diff --git a/src/server/credentials/google_docs_credentials.json b/src/server/credentials/google_docs_credentials.json
new file mode 100644
index 000000000..8d097d363
--- /dev/null
+++ b/src/server/credentials/google_docs_credentials.json
@@ -0,0 +1 @@
+{"installed":{"client_id":"343179513178-ud6tvmh275r2fq93u9eesrnc66t6akh9.apps.googleusercontent.com","project_id":"quickstart-1565056383187","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"w8KIFSc0MQpmUYHed4qEzn8b","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}}
\ No newline at end of file
diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json
new file mode 100644
index 000000000..07c02d56c
--- /dev/null
+++ b/src/server/credentials/google_docs_token.json
@@ -0,0 +1 @@
+{"access_token":"ya29.GltjB4-x03xFpd2NY2555cxg1xlT_ajqRi78M9osOfdOF2jTIjlPkn_UZL8cUwVP0DPC8rH3vhhg8RpspFe8Vewx92shAO3RPos_uMH0CUqEiCiZlaaB5I3Jq3Mv","refresh_token":"1/teUKUqGKMLjVqs-eed0L8omI02pzSxMUYaxGc2QxBw0","scope":"https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents.readonly","token_type":"Bearer","expiry_date":1565654175862}
\ No newline at end of file
diff --git a/src/server/index.ts b/src/server/index.ts
index eae018f13..9986e62d6 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -14,7 +14,6 @@ import * as mobileDetect from 'mobile-detect';
 import * as passport from 'passport';
 import * as path from 'path';
 import * as request from 'request';
-import * as rp from 'request-promise';
 import * as io from 'socket.io';
 import { Socket } from 'socket.io';
 import * as webpack from 'webpack';
@@ -36,19 +35,16 @@ const port = 1050; // default port to listen
 const serverPort = 4321;
 import expressFlash = require('express-flash');
 import flash = require('connect-flash');
-import c = require("crypto");
 import { Search } from './Search';
-import { debug } from 'util';
 import _ = require('lodash');
 import * as Archiver from 'archiver';
-import * as AdmZip from 'adm-zip';
-import * as YoutubeApi from './youtubeApi/youtubeApiSample.js';
+import AdmZip from 'adm-zip';
+import * as YoutubeApi from "./apis/youtube/youtubeApiSample";
 import { Response } from 'express-serve-static-core';
+import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
 const MongoStore = require('connect-mongo')(session);
 const mongoose = require('mongoose');
 const probe = require("probe-image-size");
-var SolrNode = require('solr-node');
-var shell = require('shelljs');
 
 const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest));
 let youtubeApiKey: string;
@@ -790,6 +786,29 @@ function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any
     }
 }
 
+const credentials = path.join(__dirname, "./credentials/google_docs_credentials.json");
+const token = path.join(__dirname, "./credentials/google_docs_token.json");
+
+app.post(RouteStore.googleDocs + ":action", (req, res) => {
+    let parameters = req.body;
+    console.log(parameters, req.params.action);
+
+    GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => {
+        let results: Promise<any> | undefined;
+        switch (req.params.action) {
+            case "create":
+                results = endpoint.documents.create(parameters).then(generated => generated.data.documentId);
+                break;
+            case "retrieve":
+                results = endpoint.documents.get(parameters).then(response => response.data);
+                break;
+            default:
+                results = undefined;
+        }
+        results && results.then(final => res.send(final));
+    });
+});
+
 const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
     "number": "_n",
     "string": "_t",
diff --git a/src/server/youtubeApi/youtubeApiSample.d.ts b/src/server/youtubeApi/youtubeApiSample.d.ts
deleted file mode 100644
index 427f54608..000000000
--- a/src/server/youtubeApi/youtubeApiSample.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-declare const YoutubeApi: any;
-export = YoutubeApi;
\ No newline at end of file
diff --git a/src/server/youtubeApi/youtubeApiSample.js b/src/server/youtubeApi/youtubeApiSample.js
deleted file mode 100644
index 50b3c7b38..000000000
--- a/src/server/youtubeApi/youtubeApiSample.js
+++ /dev/null
@@ -1,179 +0,0 @@
-const fs = require('fs');
-const readline = require('readline');
-const { google } = require('googleapis');
-const OAuth2 = google.auth.OAuth2;
-
-
-// If modifying these scopes, delete your previously saved credentials
-// at ~/.credentials/youtube-nodejs-quickstart.json
-let SCOPES = ['https://www.googleapis.com/auth/youtube.readonly'];
-let TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
-    process.env.USERPROFILE) + '/.credentials/';
-let TOKEN_PATH = TOKEN_DIR + 'youtube-nodejs-quickstart.json';
-
-module.exports.readApiKey = (callback) => {
-    fs.readFile('client_secret.json', function processClientSecrets(err, content) {
-        if (err) {
-            console.log('Error loading client secret file: ' + err);
-            return;
-        }
-        callback(content);
-    });
-}
-
-module.exports.authorizedGetChannel = (apiKey) => {
-    //this didnt get called
-    // Authorize a client with the loaded credentials, then call the YouTube API.
-    authorize(JSON.parse(apiKey), getChannel);
-}
-
-module.exports.authorizedGetVideos = (apiKey, userInput, callBack) => {
-    authorize(JSON.parse(apiKey), getVideos, { userInput: userInput, callBack: callBack });
-}
-
-module.exports.authorizedGetVideoDetails = (apiKey, videoIds, callBack) => {
-    authorize(JSON.parse(apiKey), getVideoDetails, { videoIds: videoIds, callBack: callBack });
-}
-
-
-/**
- * Create an OAuth2 client with the given credentials, and then execute the
- * given callback function.
- *
- * @param {Object} credentials The authorization client credentials.
- * @param {function} callback The callback to call with the authorized client.
- */
-function authorize(credentials, callback, args = {}) {
-    let clientSecret = credentials.installed.client_secret;
-    let clientId = credentials.installed.client_id;
-    let redirectUrl = credentials.installed.redirect_uris[0];
-    let oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl);
-
-    // Check if we have previously stored a token.
-    fs.readFile(TOKEN_PATH, function (err, token) {
-        if (err) {
-            getNewToken(oauth2Client, callback);
-        } else {
-            oauth2Client.credentials = JSON.parse(token);
-            callback(oauth2Client, args);
-        }
-    });
-}
-
-/**
- * Get and store new token after prompting for user authorization, and then
- * execute the given callback with the authorized OAuth2 client.
- *
- * @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
- * @param {getEventsCallback} callback The callback to call with the authorized
- *     client.
- */
-function getNewToken(oauth2Client, callback) {
-    var authUrl = oauth2Client.generateAuthUrl({
-        access_type: 'offline',
-        scope: SCOPES
-    });
-    console.log('Authorize this app by visiting this url: ', authUrl);
-    var rl = readline.createInterface({
-        input: process.stdin,
-        output: process.stdout
-    });
-    rl.question('Enter the code from that page here: ', function (code) {
-        rl.close();
-        oauth2Client.getToken(code, function (err, token) {
-            if (err) {
-                console.log('Error while trying to retrieve access token', err);
-                return;
-            }
-            oauth2Client.credentials = token;
-            storeToken(token);
-            callback(oauth2Client);
-        });
-    });
-}
-
-/**
- * Store token to disk be used in later program executions.
- *
- * @param {Object} token The token to store to disk.
- */
-function storeToken(token) {
-    try {
-        fs.mkdirSync(TOKEN_DIR);
-    } catch (err) {
-        if (err.code != 'EEXIST') {
-            throw err;
-        }
-    }
-    fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
-        if (err) throw err;
-        console.log('Token stored to ' + TOKEN_PATH);
-    });
-    console.log('Token stored to ' + TOKEN_PATH);
-}
-
-/**
- * Lists the names and IDs of up to 10 files.
- *
- * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
- */
-function getChannel(auth) {
-    var service = google.youtube('v3');
-    service.channels.list({
-        auth: auth,
-        part: 'snippet,contentDetails,statistics',
-        forUsername: 'GoogleDevelopers'
-    }, function (err, response) {
-        if (err) {
-            console.log('The API returned an error: ' + err);
-            return;
-        }
-        var channels = response.data.items;
-        if (channels.length == 0) {
-            console.log('No channel found.');
-        } else {
-            console.log('This channel\'s ID is %s. Its title is \'%s\', and ' +
-                'it has %s views.',
-                channels[0].id,
-                channels[0].snippet.title,
-                channels[0].statistics.viewCount);
-        }
-    });
-}
-
-function getVideos(auth, args) {
-    let service = google.youtube('v3');
-    service.search.list({
-        auth: auth,
-        part: 'id, snippet',
-        type: 'video',
-        q: args.userInput,
-        maxResults: 10
-    }, function (err, response) {
-        if (err) {
-            console.log('The API returned an error: ' + err);
-            return;
-        }
-        let videos = response.data.items;
-        args.callBack(videos);
-    });
-}
-
-function getVideoDetails(auth, args) {
-    if (args.videoIds === undefined) {
-        return;
-    }
-    let service = google.youtube('v3');
-    service.videos.list({
-        auth: auth,
-        part: 'contentDetails, statistics',
-        id: args.videoIds
-    }, function (err, response) {
-        if (err) {
-            console.log('The API returned an error from details: ' + err);
-            return;
-        }
-        let videoDetails = response.data.items;
-        args.callBack(videoDetails);
-    });
-}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 278cabf67a77edb3dd3bd9bb392550eeb08ab910 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 13 Aug 2019 02:06:32 -0400
Subject: more util functions and clean up

---
 .../apis/google_docs/GoogleApiClientUtils.ts       | 71 +++++++++++++++++++---
 src/client/views/MainView.tsx                      | 24 +++++---
 src/server/index.ts                                |  1 -
 3 files changed, 78 insertions(+), 18 deletions(-)

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

diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index 42e621f6b..71e5e1073 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -1,6 +1,7 @@
 import { docs_v1 } from "googleapis";
 import { PostToServer } from "../../../Utils";
 import { RouteStore } from "../../../server/RouteStore";
+import { Opt } from "../../../new_fields/Doc";
 
 export namespace GoogleApiClientUtils {
 
@@ -11,33 +12,76 @@ export namespace GoogleApiClientUtils {
             Retrieve = "retrieve"
         }
 
-        export namespace Helpers {
+        export namespace Utils {
 
             export const fromRgb = (red: number, green: number, blue: number) => {
                 return { color: { rgbColor: { red, green, blue } } };
             };
 
+            export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false) => {
+                let fragments: string[] = [];
+                if (document.body && document.body.content) {
+                    for (let element of document.body.content) {
+                        if (element.paragraph && element.paragraph.elements) {
+                            for (let inner of element.paragraph.elements) {
+                                if (inner && inner.textRun) {
+                                    let fragment = inner.textRun.content;
+                                    fragment && fragments.push(fragment);
+                                }
+                            }
+                        }
+                    }
+                }
+                let text = fragments.join("");
+                return removeNewlines ? text.ReplaceAll("\n", "") : text;
+            };
+
         }
 
         export const ExampleDocumentSchema = {
             title: "This is a Google Doc Created From Dash Web",
             body: {
                 content: [
+                    {
+                        endIndex: 1,
+                        sectionBreak: {
+                            sectionStyle: {
+                                columnSeparatorStyle: "NONE",
+                                contentDirection: "LEFT_TO_RIGHT"
+                            }
+                        }
+                    },
                     {
                         paragraph: {
                             elements: [
                                 {
                                     textRun: {
-                                        content: "And this is its bold, blue text!!!",
+                                        content: "And this is its bold, blue text!!!\n",
                                         textStyle: {
                                             bold: true,
-                                            backgroundColor: Helpers.fromRgb(0, 0, 1)
+                                            backgroundColor: Utils.fromRgb(0, 0, 1)
                                         }
                                     }
                                 }
                             ]
                         }
-                    }
+                    },
+                    {
+                        paragraph: {
+                            elements: [
+                                {
+                                    textRun: {
+                                        content: "And this is its bold, blue text!!!\n",
+                                        textStyle: {
+                                            bold: true,
+                                            backgroundColor: Utils.fromRgb(0, 0, 1)
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    },
+
                 ] as docs_v1.Schema$StructuralElement[]
             }
         } as docs_v1.Schema$Document;
@@ -66,20 +110,27 @@ export namespace GoogleApiClientUtils {
             }
         };
 
-        let path = RouteStore.googleDocs + Actions.Retrieve;
-        export const Retrieve = async (documentId: string): Promise<any> => {
+        export const Read = async (documentId: string, removeNewlines = false): Promise<Opt<string>> => {
+            return Retrieve(documentId).then(schema => {
+                return schema ? Utils.extractText(schema, removeNewlines) : undefined;
+            });
+        };
+
+        export const Retrieve = async (documentId: string): Promise<Opt<docs_v1.Schema$Document>> => {
+            let path = RouteStore.googleDocs + Actions.Retrieve;
             let parameters = { documentId };
-            let documentContents: any;
+            let schema: Opt<docs_v1.Schema$Document>;
             try {
-                documentContents = await PostToServer(path, parameters);
+                schema = await PostToServer(path, parameters);
             } catch (e) {
                 console.error(e);
-                documentContents = undefined;
+                schema = undefined;
             } finally {
-                return documentContents;
+                return schema;
             }
         };
 
+
     }
 
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0a987de4a..3a5285a66 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -87,6 +87,11 @@ export class MainView extends React.Component {
             if (!("presentationView" in doc)) {
                 doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })]);
             }
+            if (!("googleDocId" in doc)) {
+                GoogleApiClientUtils.Docs.Create().then(id => {
+                    id && (doc.googleDocId = id);
+                });
+            }
             CurrentUserUtils.UserDocument.activeWorkspace = doc;
         }
     }
@@ -132,8 +137,6 @@ export class MainView extends React.Component {
         window.removeEventListener("keydown", KeyManager.Instance.handle);
         window.addEventListener("keydown", KeyManager.Instance.handle);
 
-        GoogleApiClientUtils.Docs.Create().then(id => console.log(id));
-
         reaction(() => {
             let workspaces = CurrentUserUtils.UserDocument.workspaces;
             let recent = CurrentUserUtils.UserDocument.recentlyClosed;
@@ -151,6 +154,18 @@ export class MainView extends React.Component {
         }, { fireImmediately: true });
     }
 
+    componentDidMount() {
+        reaction(() => this.mainContainer, () => {
+            let main = this.mainContainer, googleDocId;
+            if (main && (googleDocId = StrCast(main.googleDocId))) {
+                GoogleApiClientUtils.Docs.Read(googleDocId, true).then(text => {
+                    text && Utils.CopyText(text);
+                    console.log(text);
+                });
+            }
+        });
+    }
+
     componentWillUnMount() {
         window.removeEventListener("keydown", KeyManager.Instance.handle);
         window.removeEventListener("pointerdown", this.globalPointerDown);
@@ -573,11 +588,6 @@ export class MainView extends React.Component {
     render() {
         return (
             <div id="main-div">
-                <input style={{ position: "absolute", zIndex: 100000 }} onKeyPress={e => {
-                    if (e.which === 13) {
-                        GoogleApiClientUtils.Docs.Retrieve(e.currentTarget.value.trim()).then((res: any) => console.log(res));
-                    }
-                }} />
                 {this.dictationOverlay}
                 <DocumentDecorations />
                 {this.mainContent}
diff --git a/src/server/index.ts b/src/server/index.ts
index 9986e62d6..4ccb61681 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -791,7 +791,6 @@ const token = path.join(__dirname, "./credentials/google_docs_token.json");
 
 app.post(RouteStore.googleDocs + ":action", (req, res) => {
     let parameters = req.body;
-    console.log(parameters, req.params.action);
 
     GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => {
         let results: Promise<any> | undefined;
-- 
cgit v1.2.3-70-g09d2


From 2ed0c3e1203794ddaa0b17ed908432582d0198d1 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 13 Aug 2019 02:11:38 -0400
Subject: reading options

---
 src/client/apis/google_docs/GoogleApiClientUtils.ts | 11 ++++++++---
 src/client/views/MainView.tsx                       |  6 +++---
 2 files changed, 11 insertions(+), 6 deletions(-)

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

diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index 71e5e1073..5e974b2e7 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -110,9 +110,14 @@ export namespace GoogleApiClientUtils {
             }
         };
 
-        export const Read = async (documentId: string, removeNewlines = false): Promise<Opt<string>> => {
-            return Retrieve(documentId).then(schema => {
-                return schema ? Utils.extractText(schema, removeNewlines) : undefined;
+        export interface ReadOptions {
+            documentId: string;
+            removeNewlines?: boolean;
+        }
+
+        export const Read = async (options: ReadOptions): Promise<Opt<string>> => {
+            return Retrieve(options.documentId).then(schema => {
+                return schema ? Utils.extractText(schema, options.removeNewlines) : undefined;
             });
         };
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 3a5285a66..705eacc0c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -156,9 +156,9 @@ export class MainView extends React.Component {
 
     componentDidMount() {
         reaction(() => this.mainContainer, () => {
-            let main = this.mainContainer, googleDocId;
-            if (main && (googleDocId = StrCast(main.googleDocId))) {
-                GoogleApiClientUtils.Docs.Read(googleDocId, true).then(text => {
+            let main = this.mainContainer, documentId;
+            if (main && (documentId = StrCast(main.googleDocId))) {
+                GoogleApiClientUtils.Docs.Read({ documentId }).then(text => {
                     text && Utils.CopyText(text);
                     console.log(text);
                 });
-- 
cgit v1.2.3-70-g09d2


From 2b829d1028a61869858ecd48e2f1801819e17488 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 13 Aug 2019 22:15:47 -0400
Subject: robust google doc export api and cleanup

---
 .../apis/google_docs/GoogleApiClientUtils.ts       | 185 ++++++++++++---------
 src/client/views/MainView.tsx                      |  11 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  24 ++-
 src/new_fields/RichTextField.ts                    |  13 ++
 src/server/index.ts                                |  25 ++-
 5 files changed, 162 insertions(+), 96 deletions(-)

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

diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index 5e974b2e7..dda36f05a 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -1,7 +1,8 @@
 import { docs_v1 } from "googleapis";
 import { PostToServer } from "../../../Utils";
 import { RouteStore } from "../../../server/RouteStore";
-import { Opt } from "../../../new_fields/Doc";
+import { Opt, Doc } from "../../../new_fields/Doc";
+import { isArray } from "util";
 
 export namespace GoogleApiClientUtils {
 
@@ -9,15 +10,12 @@ export namespace GoogleApiClientUtils {
 
         export enum Actions {
             Create = "create",
-            Retrieve = "retrieve"
+            Retrieve = "retrieve",
+            Update = "update"
         }
 
         export namespace Utils {
 
-            export const fromRgb = (red: number, green: number, blue: number) => {
-                return { color: { rgbColor: { red, green, blue } } };
-            };
-
             export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false) => {
                 let fragments: string[] = [];
                 if (document.body && document.body.content) {
@@ -36,55 +34,36 @@ export namespace GoogleApiClientUtils {
                 return removeNewlines ? text.ReplaceAll("\n", "") : text;
             };
 
-        }
-
-        export const ExampleDocumentSchema = {
-            title: "This is a Google Doc Created From Dash Web",
-            body: {
-                content: [
-                    {
-                        endIndex: 1,
-                        sectionBreak: {
-                            sectionStyle: {
-                                columnSeparatorStyle: "NONE",
-                                contentDirection: "LEFT_TO_RIGHT"
+            export const EndOf = (schema: docs_v1.Schema$Document): Opt<number> => {
+                if (schema.body && schema.body.content) {
+                    let paragraphs = schema.body.content.filter(el => el.paragraph);
+                    if (paragraphs.length) {
+                        let target = paragraphs[paragraphs.length - 1];
+                        if (target.paragraph && target.paragraph.elements) {
+                            length = target.paragraph.elements.length;
+                            if (length) {
+                                let final = target.paragraph.elements[length - 1];
+                                return final.endIndex ? final.endIndex - 1 : undefined;
                             }
                         }
-                    },
-                    {
-                        paragraph: {
-                            elements: [
-                                {
-                                    textRun: {
-                                        content: "And this is its bold, blue text!!!\n",
-                                        textStyle: {
-                                            bold: true,
-                                            backgroundColor: Utils.fromRgb(0, 0, 1)
-                                        }
-                                    }
-                                }
-                            ]
-                        }
-                    },
-                    {
-                        paragraph: {
-                            elements: [
-                                {
-                                    textRun: {
-                                        content: "And this is its bold, blue text!!!\n",
-                                        textStyle: {
-                                            bold: true,
-                                            backgroundColor: Utils.fromRgb(0, 0, 1)
-                                        }
-                                    }
-                                }
-                            ]
-                        }
-                    },
+                    }
+                }
+            };
 
-                ] as docs_v1.Schema$StructuralElement[]
-            }
-        } as docs_v1.Schema$Document;
+        }
+
+        export interface ReadOptions {
+            documentId: string;
+            removeNewlines?: boolean;
+        }
+
+        export interface WriteOptions {
+            documentId?: string;
+            title?: string;
+            content: string | string[];
+            index?: number;
+            store?: { receiver: Doc, key: string };
+        }
 
         /**
          * After following the authentication routine, which connects this API call to the current signed in account
@@ -96,45 +75,95 @@ export namespace GoogleApiClientUtils {
          * actual document body and its styling!
          * @returns the documentId of the newly generated document, or undefined if the creation process fails.
          */
-        export const Create = async (schema?: docs_v1.Schema$Document): Promise<string | undefined> => {
+        const Create = async (title?: string): Promise<string | undefined> => {
             let path = RouteStore.googleDocs + Actions.Create;
-            let parameters = { requestBody: schema || ExampleDocumentSchema };
-            let generatedId: string | undefined;
+            let parameters = {
+                requestBody: {
+                    title: title || `Dash Export (${new Date().toDateString()})`
+                }
+            };
             try {
-                generatedId = await PostToServer(path, parameters);
-            } catch (e) {
-                console.error(e);
-                generatedId = undefined;
-            } finally {
-                return generatedId;
+                let schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
+                return schema.documentId;
+            } catch {
+                return undefined;
             }
         };
 
-        export interface ReadOptions {
-            documentId: string;
-            removeNewlines?: boolean;
-        }
+        const Retrieve = async (documentId: string): Promise<docs_v1.Schema$Document | undefined> => {
+            let path = RouteStore.googleDocs + Actions.Retrieve;
+            let parameters = {
+                documentId
+            };
+            try {
+                let schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
+                return schema;
+            } catch {
+                return undefined;
+            }
+        };
+
+        const Update = async (documentId: string, requests: docs_v1.Schema$Request[]): Promise<docs_v1.Schema$BatchUpdateDocumentResponse | undefined> => {
+            let path = RouteStore.googleDocs + Actions.Update;
+            let parameters = {
+                documentId,
+                requestBody: {
+                    requests
+                }
+            };
+            try {
+                let replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters);
+                console.log(replies);
+                return replies;
+            } catch {
+                return undefined;
+            }
+        };
 
-        export const Read = async (options: ReadOptions): Promise<Opt<string>> => {
+        export const Read = async (options: ReadOptions): Promise<string | undefined> => {
             return Retrieve(options.documentId).then(schema => {
                 return schema ? Utils.extractText(schema, options.removeNewlines) : undefined;
             });
         };
 
-        export const Retrieve = async (documentId: string): Promise<Opt<docs_v1.Schema$Document>> => {
-            let path = RouteStore.googleDocs + Actions.Retrieve;
-            let parameters = { documentId };
-            let schema: Opt<docs_v1.Schema$Document>;
-            try {
-                schema = await PostToServer(path, parameters);
-            } catch (e) {
-                console.error(e);
-                schema = undefined;
-            } finally {
-                return schema;
-            }
+        export const ReadLines = async (options: ReadOptions) => {
+            return Retrieve(options.documentId).then(schema => {
+                if (!schema) {
+                    return undefined;
+                }
+                let lines = Utils.extractText(schema).split("\n");
+                return options.removeNewlines ? lines.filter(line => line.length) : lines;
+            });
         };
 
+        export const Write = async (options: WriteOptions): Promise<docs_v1.Schema$BatchUpdateDocumentResponse | undefined> => {
+            let target = options.documentId;
+            if (!target) {
+                if (!(target = await Create(options.title))) {
+                    return undefined;
+                }
+            }
+            let index = options.index;
+            if (!index) {
+                let schema = await Retrieve(target);
+                if (!schema || !(index = Utils.EndOf(schema))) {
+                    return undefined;
+                }
+            }
+            let text = options.content;
+            let request = {
+                insertText: {
+                    text: isArray(text) ? text.join("\n") : text,
+                    location: { index }
+                }
+            };
+            return Update(target, [request]).then(res => {
+                if (res && options.store) {
+                    options.store.receiver[options.store.key] = res.documentId;
+                }
+                return res;
+            });
+        };
 
     }
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 705eacc0c..7b15e9624 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -87,11 +87,6 @@ export class MainView extends React.Component {
             if (!("presentationView" in doc)) {
                 doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })]);
             }
-            if (!("googleDocId" in doc)) {
-                GoogleApiClientUtils.Docs.Create().then(id => {
-                    id && (doc.googleDocId = id);
-                });
-            }
             CurrentUserUtils.UserDocument.activeWorkspace = doc;
         }
     }
@@ -158,9 +153,9 @@ export class MainView extends React.Component {
         reaction(() => this.mainContainer, () => {
             let main = this.mainContainer, documentId;
             if (main && (documentId = StrCast(main.googleDocId))) {
-                GoogleApiClientUtils.Docs.Read({ documentId }).then(text => {
-                    text && Utils.CopyText(text);
-                    console.log(text);
+                let options = { documentId, removeNewlines: true };
+                GoogleApiClientUtils.Docs.ReadLines(options).then(lines => {
+                    console.log(lines);
                 });
             }
         });
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 44b5d2c21..8c2af7c9e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faSmile, faTextHeight } from '@fortawesome/free-solid-svg-icons';
+import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
 import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
@@ -38,9 +38,10 @@ import { For } from 'babel-types';
 import { DateField } from '../../../new_fields/DateField';
 import { Utils } from '../../../Utils';
 import { MainOverlayTextBox } from '../MainOverlayTextBox';
+import { GoogleApiClientUtils } from '../../apis/google_docs/GoogleApiClientUtils';
 
 library.add(faEdit);
-library.add(faSmile, faTextHeight);
+library.add(faSmile, faTextHeight, faUpload);
 
 // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
 //
@@ -58,6 +59,8 @@ const richTextSchema = createSchema({
     documentText: "string"
 });
 
+const googleDocKey = "googleDocId";
+
 type RichTextDocument = makeInterface<[typeof richTextSchema]>;
 const RichTextDocument = makeInterface(richTextSchema);
 
@@ -661,7 +664,24 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt"
         });
         ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" });
+        if (!(googleDocKey in Doc.GetProto(this.props.Document))) {
+            ContextMenu.Instance.addItem({ description: "Export to Google Doc...", event: this.exportToGoogleDoc, icon: "upload" });
+        }
     }
+
+    exportToGoogleDoc = () => {
+        let dataDoc = Doc.GetProto(this.props.Document);
+        let data = Cast(dataDoc.data, RichTextField);
+        let content: string | undefined;
+        if (data && (content = data.plainText())) {
+            GoogleApiClientUtils.Docs.Write({
+                title: StrCast(dataDoc.title),
+                store: { receiver: dataDoc, key: googleDocKey },
+                content
+            });
+        }
+    }
+
     render() {
         let self = this;
         let style = this.props.isOverlay ? "scroll" : "hidden";
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index 89799b2af..dc66813e0 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -9,6 +9,7 @@ import { scriptingGlobal } from "../client/util/Scripting";
 export class RichTextField extends ObjectField {
     @serializable(true)
     readonly Data: string;
+    private Extractor = /,\"text\":\"([^\"\}]*)\"\}/g;
 
     constructor(data: string) {
         super();
@@ -22,4 +23,16 @@ export class RichTextField extends ObjectField {
     [ToScriptString]() {
         return `new RichTextField("${this.Data}")`;
     }
+
+    plainText = () => {
+        let contents = "";
+        let matches: RegExpExecArray | null;
+        let considering = this.Data;
+        while ((matches = this.Extractor.exec(considering)) !== null) {
+            contents += matches[1];
+            considering = considering.substring(matches.index + matches[0].length);
+            this.Extractor.lastIndex = 0;
+        }
+        return contents.length ? contents : undefined;
+    }
 }
\ No newline at end of file
diff --git a/src/server/index.ts b/src/server/index.ts
index 4ccb61681..abaa29658 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -42,6 +42,7 @@ import AdmZip from 'adm-zip';
 import * as YoutubeApi from "./apis/youtube/youtubeApiSample";
 import { Response } from 'express-serve-static-core';
 import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
+import { GaxiosResponse } from 'gaxios';
 const MongoStore = require('connect-mongo')(session);
 const mongoose = require('mongoose');
 const probe = require("probe-image-size");
@@ -170,6 +171,13 @@ const read_text_file = (relativePath: string) => {
     });
 };
 
+const write_text_file = (relativePath: string, contents: any) => {
+    let target = path.join(__dirname, relativePath);
+    return new Promise<void>((resolve, reject) => {
+        fs.writeFile(target, contents, (err) => err ? reject(err) : resolve());
+    });
+};
+
 app.get("/version", (req, res) => {
     exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout, stderr) => {
         if (err) {
@@ -790,21 +798,22 @@ const credentials = path.join(__dirname, "./credentials/google_docs_credentials.
 const token = path.join(__dirname, "./credentials/google_docs_token.json");
 
 app.post(RouteStore.googleDocs + ":action", (req, res) => {
-    let parameters = req.body;
-
     GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => {
-        let results: Promise<any> | undefined;
+        let results: Promise<GaxiosResponse> | undefined;
+        let documents = endpoint.documents;
+        let parameters = req.body;
         switch (req.params.action) {
             case "create":
-                results = endpoint.documents.create(parameters).then(generated => generated.data.documentId);
+                results = documents.create(parameters);
                 break;
             case "retrieve":
-                results = endpoint.documents.get(parameters).then(response => response.data);
+                results = documents.get(parameters);
+                break;
+            case "update":
+                results = documents.batchUpdate(parameters);
                 break;
-            default:
-                results = undefined;
         }
-        results && results.then(final => res.send(final));
+        !results ? res.send(undefined) : results.then(response => res.send(response.data));
     });
 });
 
-- 
cgit v1.2.3-70-g09d2


From dc766185075b5861686c68a704a8e49213eacbc6 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 14 Aug 2019 01:00:29 -0400
Subject: cleanup and streamlined / robust type requirements

---
 .../apis/google_docs/GoogleApiClientUtils.ts       | 88 +++++++++++-----------
 src/client/views/MainView.tsx                      | 12 ---
 src/client/views/nodes/FormattedTextBox.tsx        | 20 ++---
 src/new_fields/RichTextField.ts                    |  2 +-
 4 files changed, 58 insertions(+), 64 deletions(-)

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

diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index dda36f05a..f4fb87e0b 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -17,32 +17,32 @@ export namespace GoogleApiClientUtils {
         export namespace Utils {
 
             export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false) => {
-                let fragments: string[] = [];
+                const fragments: string[] = [];
                 if (document.body && document.body.content) {
-                    for (let element of document.body.content) {
+                    for (const element of document.body.content) {
                         if (element.paragraph && element.paragraph.elements) {
-                            for (let inner of element.paragraph.elements) {
+                            for (const inner of element.paragraph.elements) {
                                 if (inner && inner.textRun) {
-                                    let fragment = inner.textRun.content;
+                                    const fragment = inner.textRun.content;
                                     fragment && fragments.push(fragment);
                                 }
                             }
                         }
                     }
                 }
-                let text = fragments.join("");
+                const text = fragments.join("");
                 return removeNewlines ? text.ReplaceAll("\n", "") : text;
             };
 
             export const EndOf = (schema: docs_v1.Schema$Document): Opt<number> => {
                 if (schema.body && schema.body.content) {
-                    let paragraphs = schema.body.content.filter(el => el.paragraph);
+                    const paragraphs = schema.body.content.filter(el => el.paragraph);
                     if (paragraphs.length) {
-                        let target = paragraphs[paragraphs.length - 1];
+                        const target = paragraphs[paragraphs.length - 1];
                         if (target.paragraph && target.paragraph.elements) {
                             length = target.paragraph.elements.length;
                             if (length) {
-                                let final = target.paragraph.elements[length - 1];
+                                const final = target.paragraph.elements[length - 1];
                                 return final.endIndex ? final.endIndex - 1 : undefined;
                             }
                         }
@@ -52,17 +52,25 @@ export namespace GoogleApiClientUtils {
 
         }
 
+        export type IdHandler = (id: DocumentId) => any;
+        export interface CreateOptions {
+            handler: IdHandler;
+            // if excluded, will use a default title annotated with the current date
+            title?: string;
+        }
+
         export interface ReadOptions {
             documentId: string;
+            // if exluded, will preserve newlines
             removeNewlines?: boolean;
         }
 
+        export type DocumentId = string;
         export interface WriteOptions {
-            documentId?: string;
-            title?: string;
             content: string | string[];
+            reference: DocumentId | CreateOptions;
+            // if excluded, will compute the last index of the document and append the content there
             index?: number;
-            store?: { receiver: Doc, key: string };
         }
 
         /**
@@ -70,33 +78,36 @@ export namespace GoogleApiClientUtils {
          * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which
          * should appear in the user's Google Doc library instantaneously.
          * 
-         * @param schema whatever subset of a docs_v1.Schema$Document is required to properly initialize your
-         * Google Doc. This schema defines all aspects of a Google Doc, from the title to headers / footers to the
-         * actual document body and its styling!
+         * @param options the title to assign to the new document, and the information necessary
+         * to store the new documentId returned from the creation process
          * @returns the documentId of the newly generated document, or undefined if the creation process fails.
          */
-        const Create = async (title?: string): Promise<string | undefined> => {
-            let path = RouteStore.googleDocs + Actions.Create;
-            let parameters = {
+        const Create = async (options: CreateOptions): Promise<string | undefined> => {
+            const path = RouteStore.googleDocs + Actions.Create;
+            const parameters = {
                 requestBody: {
-                    title: title || `Dash Export (${new Date().toDateString()})`
+                    title: options.title || `Dash Export (${new Date().toDateString()})`
                 }
             };
             try {
-                let schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
-                return schema.documentId;
+                const schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
+                const generatedId = schema.documentId;
+                if (generatedId) {
+                    options.handler(generatedId);
+                    return generatedId;
+                }
             } catch {
                 return undefined;
             }
         };
 
         const Retrieve = async (documentId: string): Promise<docs_v1.Schema$Document | undefined> => {
-            let path = RouteStore.googleDocs + Actions.Retrieve;
-            let parameters = {
+            const path = RouteStore.googleDocs + Actions.Retrieve;
+            const parameters = {
                 documentId
             };
             try {
-                let schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
+                const schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
                 return schema;
             } catch {
                 return undefined;
@@ -104,16 +115,15 @@ export namespace GoogleApiClientUtils {
         };
 
         const Update = async (documentId: string, requests: docs_v1.Schema$Request[]): Promise<docs_v1.Schema$BatchUpdateDocumentResponse | undefined> => {
-            let path = RouteStore.googleDocs + Actions.Update;
-            let parameters = {
+            const path = RouteStore.googleDocs + Actions.Update;
+            const parameters = {
                 documentId,
                 requestBody: {
                     requests
                 }
             };
             try {
-                let replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters);
-                console.log(replies);
+                const replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters);
                 return replies;
             } catch {
                 return undefined;
@@ -131,38 +141,32 @@ export namespace GoogleApiClientUtils {
                 if (!schema) {
                     return undefined;
                 }
-                let lines = Utils.extractText(schema).split("\n");
+                const lines = Utils.extractText(schema).split("\n");
                 return options.removeNewlines ? lines.filter(line => line.length) : lines;
             });
         };
 
         export const Write = async (options: WriteOptions): Promise<docs_v1.Schema$BatchUpdateDocumentResponse | undefined> => {
-            let target = options.documentId;
-            if (!target) {
-                if (!(target = await Create(options.title))) {
-                    return undefined;
-                }
+            let documentId: string | undefined;
+            const ref = options.reference;
+            if (!(documentId = typeof ref === "string" ? ref : await Create(ref))) {
+                return undefined;
             }
             let index = options.index;
             if (!index) {
-                let schema = await Retrieve(target);
+                let schema = await Retrieve(documentId);
                 if (!schema || !(index = Utils.EndOf(schema))) {
                     return undefined;
                 }
             }
-            let text = options.content;
-            let request = {
+            const text = options.content;
+            const request = {
                 insertText: {
                     text: isArray(text) ? text.join("\n") : text,
                     location: { index }
                 }
             };
-            return Update(target, [request]).then(res => {
-                if (res && options.store) {
-                    options.store.receiver[options.store.key] = res.documentId;
-                }
-                return res;
-            });
+            return Update(documentId, [request]);
         };
 
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7b15e9624..77f0e3d60 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -149,18 +149,6 @@ export class MainView extends React.Component {
         }, { fireImmediately: true });
     }
 
-    componentDidMount() {
-        reaction(() => this.mainContainer, () => {
-            let main = this.mainContainer, documentId;
-            if (main && (documentId = StrCast(main.googleDocId))) {
-                let options = { documentId, removeNewlines: true };
-                GoogleApiClientUtils.Docs.ReadLines(options).then(lines => {
-                    console.log(lines);
-                });
-            }
-        });
-    }
-
     componentWillUnMount() {
         window.removeEventListener("keydown", KeyManager.Instance.handle);
         window.removeEventListener("pointerdown", this.globalPointerDown);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 8c2af7c9e..50ec27259 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -670,16 +670,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     exportToGoogleDoc = () => {
-        let dataDoc = Doc.GetProto(this.props.Document);
-        let data = Cast(dataDoc.data, RichTextField);
-        let content: string | undefined;
-        if (data && (content = data.plainText())) {
-            GoogleApiClientUtils.Docs.Write({
-                title: StrCast(dataDoc.title),
-                store: { receiver: dataDoc, key: googleDocKey },
-                content
-            });
+        const dataDoc = Doc.GetProto(this.props.Document);
+        const data = Cast(dataDoc.data, RichTextField);
+        if (!data) {
+            return;
         }
+        GoogleApiClientUtils.Docs.Write({
+            reference: {
+                title: StrCast(dataDoc.title),
+                handler: id => dataDoc[googleDocKey] = id
+            },
+            content: data.plainText()
+        });
     }
 
     render() {
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index dc66813e0..3e8803a34 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -33,6 +33,6 @@ export class RichTextField extends ObjectField {
             considering = considering.substring(matches.index + matches[0].length);
             this.Extractor.lastIndex = 0;
         }
-        return contents.length ? contents : undefined;
+        return contents;
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 07482c3bf435748140addfd4fd338fc668657798 Mon Sep 17 00:00:00 2001
From: HJF Bulterman <henri_bulterman@brown.edu>
Date: Fri, 16 Aug 2019 14:50:11 -0400
Subject: initial commit, mostly functional pending circular import issue

---
 src/client/views/MainView.tsx                         |   8 ++++++--
 src/client/views/nodes/DocumentView.tsx               |   2 +-
 src/client/views/nodes/PresBox.tsx                    |   1 -
 src/new_fields/ListSpec.ts                            |   0
 .../buxton/source/.Bill_Notes_NewO.docx.icloud        | Bin 0 -> 172 bytes
 .../buxton/source/.Bill_Notes_OLPC.docx.icloud        | Bin 0 -> 172 bytes
 src/scraping/buxton/source/Bill_Notes_NewO.docx       | Bin 2150143 -> 0 bytes
 src/scraping/buxton/source/Bill_Notes_OLPC.docx       | Bin 6721592 -> 0 bytes
 8 files changed, 7 insertions(+), 4 deletions(-)
 create mode 100644 src/new_fields/ListSpec.ts
 create mode 100644 src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud
 create mode 100644 src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud
 delete mode 100644 src/scraping/buxton/source/Bill_Notes_NewO.docx
 delete mode 100644 src/scraping/buxton/source/Bill_Notes_OLPC.docx

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cb81f9aad..de312318b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -8,10 +8,10 @@ import * as React from 'react';
 import { SketchPicker } from 'react-color';
 import Measure from 'react-measure';
 import { Doc, DocListCast, Opt, HeightSym } from '../../new_fields/Doc';
+import { listSpec } from "../../new_fields/Schema";
 import { Id } from '../../new_fields/FieldSymbols';
 import { InkTool } from '../../new_fields/InkField';
 import { List } from '../../new_fields/List';
-import { listSpec } from '../../new_fields/Schema';
 import { Cast, FieldValue, NumCast, BoolCast, StrCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
@@ -93,6 +93,7 @@ export class MainView extends React.Component {
 
     componentWillUnMount() {
         window.removeEventListener("keydown", KeyManager.Instance.handle);
+        //close presentation 
     }
 
     constructor(props: Readonly<{}>) {
@@ -457,7 +458,10 @@ export class MainView extends React.Component {
         }
         let isOpen = CollectionDockingView.Instance.Has(presDoc);
         if (isOpen) {
-            CollectionDockingView.Instance.CloseRightSplit(presDoc);
+            return;
+            // CollectionDockingView.Instance.CloseRightSplit(presDoc);
+            //why?? It's throwing an error that seems impossible to fix.
+            //ToDo: 
         } else {
             CollectionDockingView.Instance.AddRightSplit(presDoc, undefined);
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6d4c18050..228efdc87 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -554,7 +554,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
         cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
         cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
-        cm.addItem({ description: "Pin to Presentation", event: () => PresBox.Instance.PinDoc(this.props.Document), icon: "map-pin" }); //this should work, and it does! A miracle!
+        cm.addItem({ description: "Pin to Presentation", event: () => PresBox.Instance.PinDoc(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle!
         cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
         let makes: ContextMenuProps[] = [];
         makes.push({ description: "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 8316c4469..1bd2ad3c9 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -826,7 +826,6 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
     render() {
 
         let width = "100%"; //NumCast(this.curPresentation.width)
-        console.log("The width is: " + width);
 
         return (
             <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflow: "hidden", opacity: this.opacity, transition: "0.7s opacity ease", pointerEvents: "all" }}>
diff --git a/src/new_fields/ListSpec.ts b/src/new_fields/ListSpec.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud b/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud
new file mode 100644
index 000000000..f71886d8c
Binary files /dev/null and b/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud differ
diff --git a/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud b/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud
new file mode 100644
index 000000000..30ddb3091
Binary files /dev/null and b/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud differ
diff --git a/src/scraping/buxton/source/Bill_Notes_NewO.docx b/src/scraping/buxton/source/Bill_Notes_NewO.docx
deleted file mode 100644
index 2f4a04e81..000000000
Binary files a/src/scraping/buxton/source/Bill_Notes_NewO.docx and /dev/null differ
diff --git a/src/scraping/buxton/source/Bill_Notes_OLPC.docx b/src/scraping/buxton/source/Bill_Notes_OLPC.docx
deleted file mode 100644
index 7a636e2d6..000000000
Binary files a/src/scraping/buxton/source/Bill_Notes_OLPC.docx and /dev/null differ
-- 
cgit v1.2.3-70-g09d2


From 0e4729a8d634c67a3575761784b840a28694ba7a Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 19 Aug 2019 15:55:36 -0400
Subject: fixed treeview dragging.  got rid of extra stuff from
 presentationview

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/views/MainView.tsx                      |  62 +-
 .../views/collections/CollectionDockingView.tsx    |  27 +-
 .../views/collections/CollectionSchemaCells.tsx    |   2 +
 .../views/collections/CollectionSchemaView.tsx     |   4 +
 src/client/views/collections/CollectionSubView.tsx |   2 +-
 .../views/collections/CollectionTreeView.tsx       |  18 +-
 .../CollectionFreeFormRemoteCursors.tsx            |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +
 src/client/views/nodes/DocumentView.tsx            |   6 +-
 src/client/views/nodes/FieldView.tsx               |   1 +
 src/client/views/nodes/KeyValuePair.tsx            |   1 +
 src/client/views/nodes/LinkMenu.tsx                |   3 -
 src/client/views/nodes/PresBox.tsx                 | 249 +-----
 src/client/views/pdf/Annotation.tsx                |   6 +-
 src/client/views/pdf/PDFViewer.tsx                 |   1 +
 .../views/presentationview/PresentationElement.tsx |   3 +-
 .../views/presentationview/PresentationList.tsx    |   5 +-
 .../views/presentationview/PresentationView.scss   |   4 +-
 .../views/presentationview/PresentationView.tsx    | 994 ---------------------
 src/client/views/search/SearchItem.tsx             |   1 +
 src/new_fields/Doc.ts                              |  55 +-
 src/new_fields/util.ts                             |   3 +-
 .../authentication/models/current_user_utils.ts    |  23 +-
 24 files changed, 172 insertions(+), 1304 deletions(-)
 delete mode 100644 src/client/views/presentationview/PresentationView.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c551fd2a3..47df17329 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -290,7 +290,7 @@ export namespace Docs {
             const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys);
 
             if (!("author" in protoProps)) {
-                protoProps.author = CurrentUserUtils.email;
+                protoProps.author = Doc.CurrentUserEmail;
             }
 
             if (!("creationDate" in protoProps)) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7119a4fc3..7b7a5542d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -12,7 +12,6 @@ import { List } from '../../new_fields/List';
 import { Id } from '../../new_fields/FieldSymbols';
 import { InkTool } from '../../new_fields/InkField';
 import { listSpec } from '../../new_fields/Schema';
-import { SchemaHeaderField } from '../../new_fields/SchemaHeaderField';
 import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
@@ -24,7 +23,7 @@ import { DictationManager } from '../util/DictationManager';
 import { SetupDrag } from '../util/DragManager';
 import { HistoryUtil } from '../util/History';
 import { Transform } from '../util/Transform';
-import { UndoManager } from '../util/UndoManager';
+import { UndoManager, undoBatch } from '../util/UndoManager';
 import { CollectionBaseView } from './collections/CollectionBaseView';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { CollectionTreeView } from './collections/CollectionTreeView';
@@ -37,15 +36,16 @@ import { MainOverlayTextBox } from './MainOverlayTextBox';
 import { DocumentView } from './nodes/DocumentView';
 import { OverlayView } from './OverlayView';
 import PDFMenu from './pdf/PDFMenu';
-import { PresentationView } from './presentationview/PresentationView';
 import { PreviewCursor } from './PreviewCursor';
 import { FilterBox } from './search/FilterBox';
+import { DocumentManager } from '../util/DocumentManager';
+import PresModeMenu from './presentationview/PresentationModeMenu';
+import { PresBox } from './nodes/PresBox';
 
 @observer
 export class MainView extends React.Component {
     public static Instance: MainView;
     @observable addMenuToggle = React.createRef<HTMLInputElement>();
-    @observable private _workspacesShown: boolean = false;
     @observable public pwidth: number = 0;
     @observable public pheight: number = 0;
 
@@ -82,10 +82,6 @@ export class MainView extends React.Component {
     public isPointerDown = false;
     private set mainContainer(doc: Opt<Doc>) {
         if (doc) {
-            if (!("presentationView" in doc)) {
-                let initialDoc = Docs.Create.TreeDocument([], { title: "Presentation" });
-                doc.presentationView = Docs.Create.PresDocument(new List<Doc>([initialDoc]));
-            }
             CurrentUserUtils.UserDocument.activeWorkspace = doc;
         }
     }
@@ -322,6 +318,7 @@ export class MainView extends React.Component {
                             DataDoc={undefined}
                             addDocument={undefined}
                             addDocTab={emptyFunction}
+                            pinToPres={emptyFunction}
                             onClick={undefined}
                             removeDocument={undefined}
                             ScreenToLocalTransform={Transform.Identity}
@@ -339,7 +336,6 @@ export class MainView extends React.Component {
                             zoomToScale={emptyFunction}
                             getScale={returnOne}
                         />}
-                    {/* {presentationDoc ? <PresentationView Documents={Cast(presentationDoc.data, listSpec(Doc))!} key="presentation" /> : null} */}
                 </div>
             }
         </Measure>;
@@ -386,6 +382,7 @@ export class MainView extends React.Component {
             DataDoc={undefined}
             addDocument={undefined}
             addDocTab={this.addDocTabFunc}
+            pinToPres={emptyFunction}
             removeDocument={undefined}
             onClick={undefined}
             ScreenToLocalTransform={Transform.Identity}
@@ -443,26 +440,24 @@ export class MainView extends React.Component {
 
         let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
 
-        // let addDockingNode = action(() => Docs.Create.StandardCollectionDockingDocument([{ doc: addColNode(), initialWidth: 200 }], { width: 200, height: 200, title: "a nested docking freeform collection" }));
-        let addSchemaNode = action(() => Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], [], { 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 addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
+        let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" }));
         let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" }));
         let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" }));
         let addImageNode = action(() => Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
         let addButtonDocument = action(() => Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" }));
         let addImportCollectionNode = action(() => Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 }));
-        let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw";
-        let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" }));
+        // let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw";
+        // let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" }));
 
         let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
             [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
+            [React.createRef<HTMLDivElement>(), "table", "Add Presentation Trail", addPresNode],
             [React.createRef<HTMLDivElement>(), "globe-asia", "Add Website", addWebNode],
             [React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
             // [React.createRef<HTMLDivElement>(), "clone", "Add Docking Frame", addDockingNode],
             [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode
-            [React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
+            //[React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
             [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode]
         ];
         if (!ClientUtils.RELEASE) btns.unshift([React.createRef<HTMLDivElement>(), "cat", "Add Cat Image", addImageNode]);
@@ -489,7 +484,7 @@ export class MainView extends React.Component {
             <div id="add-options-content">
                 <ul id="add-options-list">
                     <li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li>
-                    <li key="presentation"><button className="add-button round-button" title="Open Presentation View" onClick={this.togglePresentationView}><FontAwesomeIcon icon="table" size="sm" /></button></li>
+                    <li key="presentation"><button className="add-button round-button" title="Show Current Presentation Trail" onClick={this.togglePresentationView}><FontAwesomeIcon icon="table" size="sm" /></button></li>
                     <li key="undo"><button className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
                     <li key="redo"><button className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
                     {btns.map(btn =>
@@ -543,30 +538,20 @@ export class MainView extends React.Component {
     @observable isSearchVisible = false;
     @action.bound
     toggleSearch = () => {
-        // console.log("search toggling")
         this.isSearchVisible = !this.isSearchVisible;
     }
 
     togglePresentationView = () => {
-        let presDoc = this.presentationDoc;
-        if (!presDoc) {
-            return;
-        }
-        let isOpen = CollectionDockingView.Instance.Has(presDoc);
-        if (isOpen) {
-            return;
-            // CollectionDockingView.Instance.CloseRightSplit(presDoc);
-            //why?? It's throwing an error that seems impossible to fix.
-            //ToDo: 
-        } else {
-            CollectionDockingView.Instance.AddRightSplit(presDoc, undefined);
+        if (CurrentUserUtils.UserDocument.curPresentation) {
+            let isOpen = DocumentManager.Instance.getDocumentView(CurrentUserUtils.UserDocument.curPresentation as Doc);
+            if (isOpen) {
+                CollectionDockingView.Instance.CloseRightSplit(CurrentUserUtils.UserDocument.curPresentation as Doc);
+            } else {
+                CollectionDockingView.Instance.AddRightSplit(CurrentUserUtils.UserDocument.curPresentation as Doc, undefined);
+            }
         }
     }
 
-    private get presentationDoc() {
-        let mainCont = this.mainContainer;
-        return mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined;
-    }
     private get dictationOverlay() {
         let display = this.dictationOverlayVisible;
         let success = this.dictationSuccess;
@@ -592,12 +577,21 @@ export class MainView extends React.Component {
         );
     }
 
+    @computed get miniPresentation() {
+        let next = () => PresBox.CurrentPresentation.next();
+        let back = () => PresBox.CurrentPresentation.back();
+        let startOrResetPres = () => PresBox.CurrentPresentation.startOrResetPres();
+        let closePresMode = action(() => {PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document)});
+        return !PresBox.CurrentPresentation || !PresBox.CurrentPresentation.presMode ? (null) : <PresModeMenu next={next} back={back} presStatus={PresBox.CurrentPresentation.presStatus} startOrResetPres={startOrResetPres} closePresMode={closePresMode} > </PresModeMenu>
+    }
+
     render() {
         return (
             <div id="main-div">
                 {this.dictationOverlay}
                 <DocumentDecorations />
                 {this.mainContent}
+                {this.miniPresentation}
                 <PreviewCursor />
                 <ContextMenu />
                 {this.nodesMenu()}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 3fcc61b76..47dfeb169 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -29,6 +29,8 @@ import { faFile, faUnlockAlt } from '@fortawesome/free-solid-svg-icons';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { Docs } from '../../documents/Documents';
 import { DateField } from '../../../new_fields/DateField';
+import { List } from '../../../new_fields/List';
+import { DocumentType } from '../../documents/DocumentTypes';
 library.add(faFile);
 
 @observer
@@ -543,11 +545,31 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }));
     }
 
+    /**
+     * Adds a document to the presentation view
+     **/
+    @undoBatch
+    @action
+    public PinDoc(doc: Doc) {
+        if (doc.type === DocumentType.PRES) {
+            MainView.Instance.toggleMiniPresentation()
+        }
+        //add this new doc to props.Document
+        let curPres = Cast(CurrentUserUtils.UserDocument.curPresentation, Doc) as Doc;
+        if (curPres) {
+            const data = Cast(curPres.data, listSpec(Doc));
+            if (data) {
+                data.push(doc);
+            } else {
+                curPres.data = new List([doc]);
+            }
+        }
+    }
+
     componentDidMount() {
         this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
         this.props.glContainer.on("tab", this.onActiveContentItemChanged);
         this.onActiveContentItemChanged();
-        // setTimeout(() => MainView.Instance.openPresentationView(), 2000);
     }
 
     componentWillUnmount() {
@@ -592,6 +614,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             MainView.Instance.openWorkspace(doc);
         } else if (location === "onRight") {
             CollectionDockingView.Instance.AddRightSplit(doc, dataDoc);
+        } else if (location === "close") {
+            CollectionDockingView.Instance.CloseRightSplit(doc);
         } else {
             CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc);
         }
@@ -618,6 +642,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             focus={emptyFunction}
             backgroundColor={returnEmptyString}
             addDocTab={this.addDocTab}
+            pinToPres={this.PinDoc}
             ContainingCollectionView={undefined}
             zoomToScale={emptyFunction}
             getScale={returnOne} />;
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 7e3061354..551b485e7 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -40,6 +40,7 @@ export interface CellProps {
     fieldKey: string;
     renderDepth: number;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+    pinToPres: (document: Doc) => void;
     moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     isFocused: boolean;
     changeFocusedCellByIndex: (row: number, col: number) => void;
@@ -160,6 +161,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             PanelHeight: returnZero,
             PanelWidth: returnZero,
             addDocTab: this.props.addDocTab,
+            pinToPres: this.props.pinToPres,
             ContentScaling: returnOne
         };
 
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 4537dcc85..221908dd2 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -251,6 +251,7 @@ export interface SchemaTableProps {
     active: () => boolean;
     onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+    pinToPres: (document: Doc) => void;
     isSelected: () => boolean;
     isFocused: (document: Doc) => boolean;
     setFocused: (document: Doc) => void;
@@ -377,6 +378,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                         fieldKey: this.props.fieldKey,
                         renderDepth: this.props.renderDepth,
                         addDocTab: this.props.addDocTab,
+                        pinToPres: this.props.pinToPres,
                         moveDocument: this.props.moveDocument,
                         setIsEditing: this.setCellIsEditing,
                         isEditable: isEditable,
@@ -907,6 +909,7 @@ interface CollectionSchemaPreviewProps {
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+    pinToPres: (document: Doc) => void;
     setPreviewScript: (script: string) => void;
     previewScript?: string;
 }
@@ -997,6 +1000,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         whenActiveChanged={this.props.whenActiveChanged}
                         ContainingCollectionView={this.props.CollectionView}
                         addDocTab={this.props.addDocTab}
+                        pinToPres={this.props.pinToPres}
                         parentActive={this.props.active}
                         ScreenToLocalTransform={this.getTransform}
                         renderDepth={this.props.renderDepth + 1}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d192721a7..cfe3fefb3 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -83,7 +83,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             let ind;
             let doc = this.props.Document;
             let id = CurrentUserUtils.id;
-            let email = CurrentUserUtils.email;
+            let email = Doc.CurrentUserEmail;
             let pos = { x: position[0], y: position[1] };
             if (id && email) {
                 const proto = Doc.GetProto(doc);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 7424cc186..6b9cd57b3 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -39,6 +39,7 @@ export interface TreeViewProps {
     moveDocument: DragManager.MoveFunction;
     dropAction: "alias" | "copy" | undefined;
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
+    pinToPres: (document: Doc) => void;
     panelWidth: () => number;
     panelHeight: () => number;
     addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
@@ -259,10 +260,10 @@ class TreeView extends React.Component<TreeViewProps> {
 
             if (contents instanceof Doc || Cast(contents, listSpec(Doc))) {
                 let remDoc = (doc: Doc) => this.remove(doc, key);
-                let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before);
+                let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending));
                 contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
                     DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move,
-                    this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth);
+                    this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth);
             } else {
                 contentElement = <EditableView
                     key="editableView"
@@ -287,13 +288,13 @@ class TreeView extends React.Component<TreeViewProps> {
         const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
         if (expandKey !== undefined) {
             let remDoc = (doc: Doc) => this.remove(doc, expandKey);
-            let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before);
+            let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending));
             let docs = expandKey === "links" ? this.childLinks : this.childDocs;
             return <ul key={expandKey + "more"}>
                 {!docs ? (null) :
                     TreeView.GetChildElements(docs as Doc[], this.props.treeViewId, this.props.document.layout as Doc,
                         this.resolvedDataDoc, expandKey, addDoc, remDoc, this.move,
-                        this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform,
+                        this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
                         this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
             </ul >;
         } else if (this.treeViewExpandedView === "fields") {
@@ -319,6 +320,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     active={this.props.active}
                     whenActiveChanged={emptyFunction as any}
                     addDocTab={this.props.addDocTab}
+                    pinToPres={this.props.pinToPres}
                     setPreviewScript={emptyFunction}>
                 </CollectionSchemaPreview>
             </div>;
@@ -395,6 +397,7 @@ class TreeView extends React.Component<TreeViewProps> {
         move: DragManager.MoveFunction,
         dropAction: dropActionType,
         addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void,
+        pinToPres: (document: Doc) => void,
         screenToLocalXf: () => Transform,
         outerXf: () => { translateX: number, translateY: number },
         active: () => boolean,
@@ -471,6 +474,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 moveDocument={move}
                 dropAction={dropAction}
                 addDocTab={addDocTab}
+                pinToPres={pinToPres}
                 ScreenToLocalTransform={screenToLocalXf}
                 outerXf={outerXf}
                 parentKey={key}
@@ -554,7 +558,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     render() {
         Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
         let dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
-        let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
+        let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, !BoolCast(this.props.Document.stackingHeadersSortDescending));
         let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
         return !this.childDocs ? (null) : (
             <div id="body" className="collectionTreeView-dropTarget"
@@ -574,14 +578,14 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                         let doc = this.props.Document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.detailedLayout)) : undefined;
                         if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
                         TreeView.loadId = doc[Id];
-                        Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
+                        Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, !BoolCast(this.props.Document.stackingHeadersSortDescending));
                     }} />
                 {this.props.Document.workspaceLibrary ? this.renderNotifsButton : (null)}
                 {this.props.Document.allowClear ? this.renderClearButton : (null)}
                 <ul className="no-indent" style={{ width: "max-content" }} >
                     {
                         TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove,
-                            moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth)
+                            moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth)
                     }
                 </ul>
             </div >
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 3193f5624..b8148852d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -55,7 +55,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
                 ctx.stroke();
 
                 // ctx.font = "10px Arial";
-                // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10);
+                // ctx.fillText(Doc.CurrentUserEmail[0].toUpperCase(), 10, 10);
             }
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dbe5272dc..d69ece421 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -644,6 +644,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             whenActiveChanged: this.props.whenActiveChanged,
             bringToFront: this.bringToFront,
             addDocTab: this.props.addDocTab,
+            pinToPres: this.props.pinToPres,
             zoomToScale: this.zoomToScale,
             getScale: this.getScale
         };
@@ -669,6 +670,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             whenActiveChanged: this.props.whenActiveChanged,
             bringToFront: this.bringToFront,
             addDocTab: this.props.addDocTab,
+            pinToPres: this.props.pinToPres,
             zoomToScale: this.zoomToScale,
             getScale: this.getScale
         };
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 08d17e559..4378a51fb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -33,7 +33,6 @@ import { DocComponent } from "../DocComponent";
 import { EditableView } from '../EditableView';
 import { MainView } from '../MainView';
 import { OverlayView } from '../OverlayView';
-import { PresentationView } from "../presentationview/PresentationView";
 import { ScriptBox } from '../ScriptBox';
 import { ScriptingRepl } from '../ScriptingRepl';
 import { Template } from "./../Templates";
@@ -98,6 +97,7 @@ export interface DocumentViewProps {
     whenActiveChanged: (isActive: boolean) => void;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
+    pinToPres: (document: Doc) => void;
     collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
     zoomToScale: (scale: number) => void;
     backgroundColor: (doc: Doc) => string | undefined;
@@ -614,7 +614,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let analyzers: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
         analyzers.push({ description: "Transcribe Speech", event: this.listen, icon: "microphone" });
         !existingAnalyze && cm.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" });
-        cm.addItem({ description: "Pin to Presentation", event: () => PresBox.Instance.PinDoc(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle!
+        cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle!
         cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
         cm.addItem({
             description: "Download document", icon: "download", event: () => {
@@ -661,7 +661,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         try {
             let stuff = await rp.get(Utils.prepend(RouteStore.getUsers));
             const users: User[] = JSON.parse(stuff);
-            usersMenu = users.filter(({ email }) => email !== CurrentUserUtils.email).map(({ email, userDocumentId }) => ({
+            usersMenu = users.filter(({ email }) => email !== Doc.CurrentUserEmail).map(({ email, userDocumentId }) => ({
                 description: email, event: async () => {
                     const userDocument = await Cast(DocServer.GetRefField(userDocumentId), Doc);
                     if (!userDocument) {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index cae975f30..f0f1b3b73 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -39,6 +39,7 @@ export interface FieldViewProps {
     selectOnLoad: boolean;
     addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+    pinToPres: (document: Doc) => void;
     removeDocument?: (document: Doc) => boolean;
     moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 534a42efc..8001b24a7 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -68,6 +68,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             PanelWidth: returnZero,
             PanelHeight: returnZero,
             addDocTab: returnZero,
+            pinToPres: returnZero,
             ContentScaling: returnOne
         };
         let contents = <FieldView {...props} />;
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 1a4af04f8..1908889e9 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -12,9 +12,6 @@ import { library } from "@fortawesome/fontawesome-svg-core";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 
 library.add(faTrash);
-import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { DocumentType } from "../../documents/Documents";
 
 interface Props {
     docView: DocumentView;
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 91c141540..cc042e008 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -1,22 +1,22 @@
 import React = require("react");
-import { FieldViewProps, FieldView } from './FieldView';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { observable, action, runInAction, reaction, autorun, computed } from "mobx";
-import "../presentationview/PresentationView.scss";
-import { DocumentManager } from "../../util/DocumentManager";
-import { Utils } from "../../../Utils";
 import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
-import { listSpec } from "../../../new_fields/Schema";
-import { Cast, NumCast, FieldValue, PromiseValue, StrCast, BoolCast } from "../../../new_fields/Types";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { Utils } from "../../../Utils";
+import { DocumentManager } from "../../util/DocumentManager";
+import { undoBatch } from "../../util/UndoManager";
 import PresentationElement, { buttonIndex } from "../presentationview/PresentationElement";
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faArrowRight, faArrowLeft, faPlay, faStop, faPlus, faTimes, faMinus, faEdit } from '@fortawesome/free-solid-svg-icons';
-import { Docs } from "../../documents/Documents";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
 import PresentationViewList from "../presentationview/PresentationList";
+import "../presentationview/PresentationView.scss";
+import { FieldView, FieldViewProps } from './FieldView';
+import { ContextMenu } from "../ContextMenu";
 
 library.add(faArrowLeft);
 library.add(faArrowRight);
@@ -37,17 +37,14 @@ const expandedWidth = 450;
 @observer
 export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
 
-    @computed
-    private get presentationDocs() {
-        let source = Doc.GetProto(this.props.Document);
-        return DocListCast(source.data);
-    }
 
     public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(PresBox, fieldKey); }
 
-    //public static Instance: PresentationView;
     public static Instance: PresBox;
 
+    //Keeping track of the doc for the current presentation -- bcz: keeping a list of current presentations shouldn't be needed.  Let users create them, store them, as they see fit.
+    @computed get curPresentation() { return this.props.Document; }
+
     //Mapping from presentation ids to a list of doc that represent a group
     @observable groupMappings: Map<String, Doc[]> = new Map();
     //mapping from docs to their rendered component
@@ -59,9 +56,6 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
     //back-up so that presentation stays the way it's when refreshed
     @observable presGroupBackUp: Doc = new Doc();
     @observable presButtonBackUp: Doc = new Doc();
-
-    //Keeping track of the doc for the current presentation
-    @observable curPresentation: Doc = new Doc();
     //Mapping of guids to presentations.
     @observable presentationsMapping: Map<String, Doc> = new Map();
     //Mapping of presentations to guid, so that select option values can be given.
@@ -77,12 +71,14 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
     @observable opacity = 1;
     @observable persistOpacity = true;
     @observable labelOpacity = 0;
+    @observable presMode = false;
+
+    @observable public static CurrentPresentation: PresBox;
 
     //initilize class variables
-    constructor(props: FieldViewProps) { //FieldViewProps?
+    constructor(props: FieldViewProps) {
         super(props);
-        //PresentationView.Instance = this;
-        PresBox.Instance = this;
+        runInAction(() => PresBox.CurrentPresentation = this);
     }
 
     @action
@@ -94,32 +90,10 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
         }
     }
 
-    //The first lifecycle function that gets called to set up the current presentation.
-    async componentWillMount() {
-        this.presentationDocs.forEach(async (doc, index: number) => {
-
-            //For each presentation received from mainContainer, a mapping is created.
-            let curDoc: Doc = await doc;
-            let newGuid = Utils.GenerateGuid();
-            this.presentationsKeyMapping.set(curDoc, newGuid);
-            this.presentationsMapping.set(newGuid, curDoc);
-
-            //The Presentation at first index gets set as default start presentation
-            if (index === 0) {
-                runInAction(() => this.currentSelectedPresValue = newGuid);
-                runInAction(() => this.curPresentation = curDoc);
-            }
-        });
-    }
-
-    //Second lifecycle function that gets called when component mounts. It makes sure to
+    //Second lifecycle function that gets called when component mounts. It makes sure toS
     //get the back-up information from previous session for the current presentation.
     async componentDidMount() {
-        let docAtZero = await this.presentationDocs[0];
-        runInAction(() => this.curPresentation = docAtZero);
-
         this.setPresentationBackUps();
-
     }
 
 
@@ -212,7 +186,6 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
     //observable means render is re-called every time variable is changed
     @observable
     collapsed: boolean = false;
-    closePresentation = action(() => this.curPresentation.width = 0);
     next = async () => {
         const current = NumCast(this.curPresentation.selectedDoc);
         //asking to get document at current index
@@ -556,23 +529,6 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
         runInAction(() => this.groupMappings = new Map());
     }
 
-    /**
-     * Adds a document to the presentation view
-     **/
-    @undoBatch
-    @action
-    public PinDoc(doc: Doc) {
-        //add this new doc to props.Document
-        const data = Cast(this.curPresentation.data, listSpec(Doc));
-        if (data) {
-            data.push(doc);
-        } else {
-            this.curPresentation.data = new List([doc]);
-        }
-
-        this.toggle(true);
-    }
-
     //Function that sets the store of the children docs.
     @action
     setChildrenDocs = (docList: Doc[]) => {
@@ -648,72 +604,15 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
 
     }
 
-    /**
-     * The function that is called to add a new presentation to the presentationView.
-     * It sets up te mappings and local copies of it. Resets the groupings and presentation.
-     * Makes the new presentation current selected, and retrieve the back-Ups if present.
-     */
-    @action
-    addNewPresentation = (presTitle: string) => {
-        //creating a new presentation doc
-        let newPresentationDoc = Docs.Create.TreeDocument([], { title: presTitle });
-        let presDocs = Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc));
-        presDocs && presDocs.push(newPresentationDoc);
-
-        //setting that new doc as current
-        this.curPresentation = newPresentationDoc;
-
-        //storing the doc in local copies for easier access
-        let newGuid = Utils.GenerateGuid();
-        this.presentationsMapping.set(newGuid, newPresentationDoc);
-        this.presentationsKeyMapping.set(newPresentationDoc, newGuid);
-
-        //resetting the previous presentation's actions so that new presentation can be loaded.
-        this.resetGroupIds();
-        this.resetPresentation();
-        this.presElementsMappings = new Map();
-        this.currentSelectedPresValue = newGuid;
-        this.setPresentationBackUps();
-
-    }
-
-    /**
-     * The function that is called to change the current selected presentation.
-     * Changes the presentation, also resetting groupings and presentation in process.
-     * Plus retrieving the backUps for the newly selected presentation.
-     */
-    @action
-    getSelectedPresentation = (e: React.ChangeEvent<HTMLSelectElement>) => {
-        //get the guid of the selected presentation
-        let selectedGuid = e.target.value;
-        //set that as current presentation
-        this.curPresentation = this.presentationsMapping.get(selectedGuid)!;
-
-        //reset current Presentations local things so that new one can be loaded
-        this.resetGroupIds();
-        this.resetPresentation();
-        this.presElementsMappings = new Map();
-        this.currentSelectedPresValue = selectedGuid;
-        this.setPresentationBackUps();
-
-
-    }
 
     /**
      * The function that is called to render either select for presentations, or title inputting.
      */
     renderSelectOrPresSelection = () => {
-        let presentationList = this.presentationDocs;
         if (this.PresTitleInputOpen || this.PresTitleChangeOpen) {
             return <input ref={(e) => this.titleInputElement = e!} type="text" className="presentationView-title" placeholder="Enter Name!" onKeyDown={this.submitPresentationTitle} />;
         } else {
-            return <select value={this.currentSelectedPresValue} id="pres_selector" className="presentationView-title" onChange={this.getSelectedPresentation}>
-                {presentationList.map((doc: Doc, index: number) => {
-                    let mappedGuid = this.presentationsKeyMapping.get(doc);
-                    let docGuid: string = mappedGuid ? mappedGuid.toString() : "";
-                    return <option key={docGuid} value={docGuid}>{StrCast(doc.title)}</option>;
-                })}
-            </select>;
+            return (null);
         }
     }
 
@@ -726,87 +625,12 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
         if (e.keyCode === 13) {
             let presTitle = this.titleInputElement!.value;
             this.titleInputElement!.value = "";
-            if (this.PresTitleInputOpen) {
-                if (presTitle === "") {
-                    presTitle = "Presentation";
-                }
-                this.PresTitleInputOpen = false;
-                this.addNewPresentation(presTitle);
-            } else if (this.PresTitleChangeOpen) {
+            if (this.PresTitleChangeOpen) {
                 this.PresTitleChangeOpen = false;
                 this.changePresentationTitle(presTitle);
             }
         }
     }
-
-    /**
-     * The function that is called to remove a presentation from all its copies, and the main Container's
-     * list. Sets up the next presentation as current.
-     */
-    @action
-    removePresentation = async () => {
-        if (this.presentationsMapping.size !== 1) {
-            let presentationList = this.presentationDocs;
-            let batch = UndoManager.StartBatch("presRemoval");
-
-            //getting the presentation that will be removed
-            let removedDoc = this.presentationsMapping.get(this.currentSelectedPresValue!);
-            //that presentation is removed
-            presentationList!.splice(presentationList.indexOf(removedDoc!), 1);
-
-            //its mappings are removed from local copies
-            this.presentationsKeyMapping.delete(removedDoc!);
-            this.presentationsMapping.delete(this.currentSelectedPresValue!);
-
-            //the next presentation is set as current
-            let remainingPresentations = this.presentationsMapping.values();
-            let nextDoc = remainingPresentations.next().value;
-            this.curPresentation = nextDoc;
-
-
-            //Storing these for being able to undo changes
-            let curGuid = this.currentSelectedPresValue!;
-            let curPresStatus = this.presStatus;
-
-            //resetting the groups and presentation actions so that next presentation gets loaded
-            this.resetGroupIds();
-            this.resetPresentation();
-            this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString();
-            this.setPresentationBackUps();
-
-            //Storing for undo
-            let currentGroups = this.groupMappings;
-            let curPresElemMapping = this.presElementsMappings;
-
-            //Event to undo actions that are not related to doc directly, aka. local things
-            UndoManager.AddEvent({
-                undo: action(() => {
-                    this.curPresentation = removedDoc!;
-                    this.presentationsMapping.set(curGuid, removedDoc!);
-                    this.presentationsKeyMapping.set(removedDoc!, curGuid);
-                    this.currentSelectedPresValue = curGuid;
-
-                    this.presStatus = curPresStatus;
-                    this.groupMappings = currentGroups;
-                    this.presElementsMappings = curPresElemMapping;
-                    this.setPresentationBackUps();
-
-                }),
-                redo: action(() => {
-                    this.curPresentation = nextDoc;
-                    this.presStatus = false;
-                    this.presentationsKeyMapping.delete(removedDoc!);
-                    this.presentationsMapping.delete(curGuid);
-                    this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString();
-                    this.setPresentationBackUps();
-
-                }),
-            });
-
-            batch.end();
-        }
-    }
-
     /**
      * The function that is called to change title of presentation to what user entered.
      */
@@ -822,26 +646,23 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
         this.presElementsMappings.set(keyDoc, elem);
     }
 
+    specificContextMenu = (e: React.MouseEvent): void => {
+        ContextMenu.Instance.addItem({ description: "Make Current Presentation", event: action(() => Doc.UserDoc().curPresentation = this.props.Document), icon: "asterisk" });
+        ContextMenu.Instance.addItem({
+            description: "Toggle Minimized Mode", event: action(() => {
+                this.presMode = !this.presMode;
+                if (this.presMode) this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close");
+            }), icon: "asterisk"
+        });
+    }
 
     render() {
 
         let width = "100%"; //NumCast(this.curPresentation.width)
-
         return (
-            <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflow: "hidden", opacity: this.opacity, transition: "0.7s opacity ease", pointerEvents: "all" }}>
-                <div className="presentationView-heading">
-                    {this.renderSelectOrPresSelection()}
-                    <button title="Close Presentation" className='presentation-icon' onClick={this.closePresentation}><FontAwesomeIcon icon={"times"} /></button> {/**this.closePresentation  CLICK does not work?! Also without the*/}
-                    <button title="Add Presentation" className="presentation-icon" style={{ marginRight: 10 }} onClick={() => {
-                        runInAction(() => { if (this.PresTitleChangeOpen) { this.PresTitleChangeOpen = false; } });
-                        runInAction(() => this.PresTitleInputOpen ? this.PresTitleInputOpen = false : this.PresTitleInputOpen = true);
-                    }}><FontAwesomeIcon icon={"plus"} /></button>
-                    <button title="Remove Presentation" className='presentation-icon' style={{ marginRight: 10 }} onClick={this.removePresentation}><FontAwesomeIcon icon={"minus"} /></button>
-                    <button title="Change Presentation Title" className="presentation-icon" style={{ marginRight: 10 }} onClick={() => {
-                        runInAction(() => { if (this.PresTitleInputOpen) { this.PresTitleInputOpen = false; } });
-                        runInAction(() => this.PresTitleChangeOpen ? this.PresTitleChangeOpen = false : this.PresTitleChangeOpen = true);
-                    }}><FontAwesomeIcon icon={"edit"} /></button>
-                </div>
+            <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onContextMenu={this.specificContextMenu}
+                onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))}
+                style={{ width: width, opacity: this.opacity, }}>
                 <div className="presentation-buttons">
                     <button title="Back" className="presentation-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
                     {this.renderPlayPauseButton()}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 7ba7b6d14..6f77a0a5b 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -6,10 +6,10 @@ import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { DocumentManager } from "../../util/DocumentManager";
-import { PresentationView } from "../presentationview/PresentationView";
 import PDFMenu from "./PDFMenu";
 import "./Annotation.scss";
 import { scale } from "./PDFViewer";
+import { PresBox } from "../nodes/PresBox";
 
 interface IAnnotationProps {
     anno: Doc;
@@ -18,6 +18,7 @@ interface IAnnotationProps {
     fieldExtensionDoc: Doc;
     scrollTo?: (n: number) => void;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+    pinToPres: (document: Doc) => void;
 }
 
 export default class Annotation extends React.Component<IAnnotationProps> {
@@ -37,6 +38,7 @@ interface IRegionAnnotationProps {
     fieldExtensionDoc: Doc;
     scrollTo?: (n: number) => void;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+    pinToPres: (document: Doc) => void;
     document: Doc;
 }
 
@@ -81,7 +83,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
 
     pinToPres = () => {
         let group = FieldValue(Cast(this.props.document.group, Doc));
-        group && PresentationView.Instance.PinDoc(group);
+        group && this.props.pinToPres(group);
     }
 
     @action
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 08674720d..258e218f0 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -36,6 +36,7 @@ interface IViewerProps {
     active: () => boolean;
     setPanY?: (n: number) => void;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+    pinToPres: (document: Doc) => void;
     addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
 }
 
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index d98b66324..912970a50 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -15,7 +15,7 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { ContextMenu } from "../ContextMenu";
 import { Transform } from "../../util/Transform";
 import { DocumentView } from "../nodes/DocumentView";
-import { DocumentType } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
 import React = require("react");
 
 
@@ -839,6 +839,7 @@ export default class PresentationElement extends React.Component<PresentationEle
                     removeDocument={returnFalse}
                     ScreenToLocalTransform={Transform.Identity}
                     addDocTab={returnFalse}
+                    pinToPres={returnFalse}
                     renderDepth={1}
                     PanelWidth={() => 350}
                     PanelHeight={() => 90}
diff --git a/src/client/views/presentationview/PresentationList.tsx b/src/client/views/presentationview/PresentationList.tsx
index e853c4070..288ade042 100644
--- a/src/client/views/presentationview/PresentationList.tsx
+++ b/src/client/views/presentationview/PresentationList.tsx
@@ -6,10 +6,7 @@ import { Utils } from "../../../Utils";
 import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
 import { NumCast, StrCast } from "../../../new_fields/Types";
 import { Id } from "../../../new_fields/FieldSymbols";
-import PresentationElement, { buttonIndex } from "./PresentationElement";
-import "../../../new_fields/Doc";
-
-
+import PresentationElement from "./PresentationElement";
 
 
 interface PresListProps {
diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss
index 65b09c833..4f5858f20 100644
--- a/src/client/views/presentationview/PresentationView.scss
+++ b/src/client/views/presentationview/PresentationView.scss
@@ -7,7 +7,9 @@
     top: 0;
     bottom: 0;
     letter-spacing: 2px;
-    
+    overflow: hidden;
+    transition: 0.7s opacity ease;
+    pointer-events: all;
 }
 
 .presentationView-item {
diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx
deleted file mode 100644
index bea70f00b..000000000
--- a/src/client/views/presentationview/PresentationView.tsx
+++ /dev/null
@@ -1,994 +0,0 @@
-import { observer } from "mobx-react";
-import React = require("react");
-import { observable, action, runInAction, reaction, autorun } from "mobx";
-import "./PresentationView.scss";
-import { DocumentManager } from "../../util/DocumentManager";
-import { Utils } from "../../../Utils";
-import { Doc, DocListCast, DocListCastAsync, WidthSym } from "../../../new_fields/Doc";
-import { listSpec } from "../../../new_fields/Schema";
-import { Cast, NumCast, FieldValue, PromiseValue, StrCast, BoolCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { List } from "../../../new_fields/List";
-import PresentationElement, { buttonIndex } from "./PresentationElement";
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faArrowRight, faArrowLeft, faPlay, faStop, faPlus, faTimes, faMinus, faEdit, faEye } from '@fortawesome/free-solid-svg-icons';
-import { Docs } from "../../documents/Documents";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import PresentationViewList from "./PresentationList";
-import PresModeMenu from "./PresentationModeMenu";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-
-library.add(faArrowLeft);
-library.add(faArrowRight);
-library.add(faPlay);
-library.add(faStop);
-library.add(faPlus);
-library.add(faTimes);
-library.add(faMinus);
-library.add(faEdit);
-library.add(faEye);
-
-
-export interface PresViewProps {
-    Documents: List<Doc>;
-}
-
-const expandedWidth = 400;
-const presMinWidth = 300;
-
-@observer
-export class PresentationView extends React.Component<PresViewProps>  {
-    public static Instance: PresentationView;
-
-    //Mapping from presentation ids to a list of doc that represent a group
-    @observable groupMappings: Map<String, Doc[]> = new Map();
-    //mapping from docs to their rendered component
-    @observable presElementsMappings: Map<Doc, PresentationElement> = new Map();
-    //variable that holds all the docs in the presentation
-    @observable childrenDocs: Doc[] = [];
-    //variable to hold if presentation is started
-    @observable presStatus: boolean = false;
-    //back-up so that presentation stays the way it's when refreshed
-    @observable presGroupBackUp: Doc = new Doc();
-    @observable presButtonBackUp: Doc = new Doc();
-
-    //Keeping track of the doc for the current presentation
-    @observable curPresentation: Doc = new Doc();
-    //Mapping of guids to presentations.
-    @observable presentationsMapping: Map<String, Doc> = new Map();
-    //Mapping of presentations to guid, so that select option values can be given.
-    @observable presentationsKeyMapping: Map<Doc, String> = new Map();
-    //Variable to keep track of guid of the current presentation
-    @observable currentSelectedPresValue: string | undefined;
-    //A flag to keep track if title input is open, which is used in rendering.
-    @observable PresTitleInputOpen: boolean = false;
-    //Variable that holds reference to title input, so that new presentations get titles assigned.
-    @observable titleInputElement: HTMLInputElement | undefined;
-    @observable PresTitleChangeOpen: boolean = false;
-    @observable presMode: boolean = false;
-
-
-    @observable opacity = 1;
-    @observable persistOpacity = true;
-    @observable labelOpacity = 0;
-
-    //initilize class variables
-    constructor(props: PresViewProps) {
-        super(props);
-        PresentationView.Instance = this;
-    }
-
-    @action
-    toggle = (forcedValue: boolean | undefined) => {
-        if (forcedValue !== undefined) {
-            this.curPresentation.width = forcedValue ? expandedWidth : 0;
-        } else {
-            this.curPresentation.width = this.curPresentation.width === expandedWidth ? 0 : expandedWidth;
-        }
-    }
-
-    //The first lifecycle function that gets called to set up the current presentation.
-    async componentWillMount() {
-
-        this.props.Documents.forEach(async (doc, index: number) => {
-
-            //For each presentation received from mainContainer, a mapping is created.
-            let curDoc: Doc = await doc;
-            let newGuid = Utils.GenerateGuid();
-            this.presentationsKeyMapping.set(curDoc, newGuid);
-            this.presentationsMapping.set(newGuid, curDoc);
-
-            //The Presentation at first index gets set as default start presentation
-            if (index === 0) {
-                runInAction(() => this.currentSelectedPresValue = newGuid);
-                runInAction(() => this.curPresentation = curDoc);
-            }
-        });
-    }
-
-    //Second lifecycle function that gets called when component mounts. It makes sure to
-    //get the back-up information from previous session for the current presentation.
-    async componentDidMount() {
-        let docAtZero = await this.props.Documents[0];
-        runInAction(() => this.curPresentation = docAtZero);
-
-        this.setPresentationBackUps();
-
-    }
-
-
-    /**
-     * The function that retrieves the backUps for the current Presentation if present,
-     * otherwise initializes.
-     */
-    setPresentationBackUps = async () => {
-        //getting both backUp documents
-
-        let castedGroupBackUp = Cast(this.curPresentation.presGroupBackUp, Doc);
-        let castedButtonBackUp = Cast(this.curPresentation.presButtonBackUp, Doc);
-        //if instantiated before 
-        if (castedGroupBackUp instanceof Promise) {
-            castedGroupBackUp.then(doc => {
-                let toAssign = doc ? doc : new Doc();
-                this.curPresentation.presGroupBackUp = toAssign;
-                runInAction(() => this.presGroupBackUp = toAssign);
-                if (doc) {
-                    if (toAssign[Id] === doc[Id]) {
-                        this.retrieveGroupMappings();
-                    }
-                }
-            });
-
-            //if never instantiated a store doc yet
-        } else if (castedGroupBackUp instanceof Doc) {
-            let castedDoc: Doc = await castedGroupBackUp;
-            runInAction(() => this.presGroupBackUp = castedDoc);
-            this.retrieveGroupMappings();
-        } else {
-            runInAction(() => {
-                let toAssign = new Doc();
-                this.presGroupBackUp = toAssign;
-                this.curPresentation.presGroupBackUp = toAssign;
-
-            });
-
-        }
-        //if instantiated before 
-        if (castedButtonBackUp instanceof Promise) {
-            castedButtonBackUp.then(doc => {
-                let toAssign = doc ? doc : new Doc();
-                this.curPresentation.presButtonBackUp = toAssign;
-                runInAction(() => this.presButtonBackUp = toAssign);
-            });
-
-            //if never instantiated a store doc yet
-        } else if (castedButtonBackUp instanceof Doc) {
-            let castedDoc: Doc = await castedButtonBackUp;
-            runInAction(() => this.presButtonBackUp = castedDoc);
-
-        } else {
-            runInAction(() => {
-                let toAssign = new Doc();
-                this.presButtonBackUp = toAssign;
-                this.curPresentation.presButtonBackUp = toAssign;
-            });
-
-        }
-
-
-        //storing the presentation status,ie. whether it was stopped or playing
-        let presStatusBackUp = BoolCast(this.curPresentation.presStatus);
-        runInAction(() => this.presStatus = presStatusBackUp);
-    }
-
-    /**
-     * This is the function that is called to retrieve the groups that have been stored and
-     * push them to the groupMappings.
-     */
-    retrieveGroupMappings = async () => {
-        let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs);
-        if (castedGroupDocs !== undefined) {
-            castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => {
-                let castedGrouping = await DocListCastAsync(groupDoc.grouping);
-                let castedKey = StrCast(groupDoc.presentIdStore, null);
-                if (castedGrouping) {
-                    castedGrouping.forEach((doc: Doc) => {
-                        doc.presentId = castedKey;
-                    });
-                }
-                if (castedGrouping !== undefined && castedKey !== undefined) {
-                    this.groupMappings.set(castedKey, castedGrouping);
-                }
-            });
-        }
-    }
-
-    //observable means render is re-called every time variable is changed
-    @observable
-    collapsed: boolean = false;
-    closePresentation = action(() => this.curPresentation.width = 0);
-    next = async () => {
-        const current = NumCast(this.curPresentation.selectedDoc);
-        //asking to get document at current index
-        let docAtCurrentNext = await this.getDocAtIndex(current + 1);
-        if (docAtCurrentNext === undefined) {
-            return;
-        }
-        //asking for it's presentation id
-        let curNextPresId = StrCast(docAtCurrentNext.presentId);
-        let nextSelected = current + 1;
-
-        //if curDoc is in a group, selection slides until last one, if not it's next one
-        if (this.groupMappings.has(curNextPresId)) {
-            let currentsArray = this.groupMappings.get(StrCast(docAtCurrentNext.presentId))!;
-            nextSelected = current + currentsArray.length - currentsArray.indexOf(docAtCurrentNext);
-
-            //end of grup so go beyond
-            if (nextSelected === current) nextSelected = current + 1;
-        }
-
-        this.gotoDocument(nextSelected, current);
-
-    }
-    back = async () => {
-        const current = NumCast(this.curPresentation.selectedDoc);
-        //requesting for the doc at current index
-        let docAtCurrent = await this.getDocAtIndex(current);
-        if (docAtCurrent === undefined) {
-            return;
-        }
-
-        //asking for its presentation id.
-        let curPresId = StrCast(docAtCurrent.presentId);
-        let prevSelected = current - 1;
-        let zoomOut: boolean = false;
-
-        //checking if this presentation id is mapped to a group, if so chosing the first element in group
-        if (this.groupMappings.has(curPresId)) {
-            let currentsArray = this.groupMappings.get(StrCast(docAtCurrent.presentId))!;
-            prevSelected = current - currentsArray.length + (currentsArray.length - currentsArray.indexOf(docAtCurrent)) - 1;
-            //end of grup so go beyond
-            if (prevSelected === current) prevSelected = current - 1;
-
-            //checking if any of the group members had used zooming in
-            currentsArray.forEach((doc: Doc) => {
-                //let presElem: PresentationElement | undefined = this.presElementsMappings.get(doc);
-                if (this.presElementsMappings.get(doc)!.selected[buttonIndex.Show]) {
-                    zoomOut = true;
-                    return;
-                }
-            });
-
-        }
-
-        // if a group set that flag to zero or a single element
-        //If so making sure to zoom out, which goes back to state before zooming action
-        if (current > 0) {
-            if (zoomOut || this.presElementsMappings.get(docAtCurrent)!.selected[buttonIndex.Show]) {
-                let prevScale = NumCast(this.childrenDocs[prevSelected].viewScale, null);
-                let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[current]);
-                if (prevScale !== undefined) {
-                    if (prevScale !== curScale) {
-                        DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale);
-                    }
-                }
-            }
-        }
-        this.gotoDocument(prevSelected, current);
-
-    }
-
-    /**
-     * This is the method that checks for the actions that need to be performed
-     * after the document has been presented, which involves 3 button options:
-     * Hide Until Presented, Hide After Presented, Fade After Presented
-     */
-    showAfterPresented = (index: number) => {
-        this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => {
-            let selectedButtons: boolean[] = presElem.selected;
-            //the order of cases is aligned based on priority
-            if (selectedButtons[buttonIndex.HideTillPressed]) {
-                if (this.childrenDocs.indexOf(key) <= index) {
-                    key.opacity = 1;
-                }
-            }
-            if (selectedButtons[buttonIndex.HideAfter]) {
-                if (this.childrenDocs.indexOf(key) < index) {
-                    key.opacity = 0;
-                }
-            }
-            if (selectedButtons[buttonIndex.FadeAfter]) {
-                if (this.childrenDocs.indexOf(key) < index) {
-                    key.opacity = 0.5;
-                }
-            }
-        });
-    }
-
-    /**
-     * This is the method that checks for the actions that need to be performed
-     * before the document has been presented, which involves 3 button options:
-     * Hide Until Presented, Hide After Presented, Fade After Presented
-     */
-    hideIfNotPresented = (index: number) => {
-        this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => {
-            let selectedButtons: boolean[] = presElem.selected;
-
-            //the order of cases is aligned based on priority
-
-            if (selectedButtons[buttonIndex.HideAfter]) {
-                if (this.childrenDocs.indexOf(key) >= index) {
-                    key.opacity = 1;
-                }
-            }
-            if (selectedButtons[buttonIndex.FadeAfter]) {
-                if (this.childrenDocs.indexOf(key) >= index) {
-                    key.opacity = 1;
-                }
-            }
-            if (selectedButtons[buttonIndex.HideTillPressed]) {
-                if (this.childrenDocs.indexOf(key) > index) {
-                    key.opacity = 0;
-                }
-            }
-        });
-    }
-
-    /**
-     * This method makes sure that cursor navigates to the element that
-     * has the option open and last in the group. If not in the group, and it has
-     * te option open, navigates to that element.
-     */
-    navigateToElement = async (curDoc: Doc, fromDoc: number) => {
-        let docToJump: Doc = curDoc;
-        let curDocPresId = StrCast(curDoc.presentId, null);
-        let willZoom: boolean = false;
-
-        //checking if in group
-        if (curDocPresId !== undefined) {
-            if (this.groupMappings.has(curDocPresId)) {
-                let currentDocGroup = this.groupMappings.get(curDocPresId)!;
-                currentDocGroup.forEach((doc: Doc, index: number) => {
-                    let selectedButtons: boolean[] = this.presElementsMappings.get(doc)!.selected;
-                    if (selectedButtons[buttonIndex.Navigate]) {
-                        docToJump = doc;
-                        willZoom = false;
-                    }
-                    if (selectedButtons[buttonIndex.Show]) {
-                        docToJump = doc;
-                        willZoom = true;
-                    }
-                });
-            }
-
-        }
-        //docToJump stayed same meaning, it was not in the group or was the last element in the group
-        if (docToJump === curDoc) {
-            //checking if curDoc has navigation open
-            let curDocButtons = this.presElementsMappings.get(curDoc)!.selected;
-            if (curDocButtons[buttonIndex.Navigate]) {
-                this.jumpToTabOrRight(curDocButtons, curDoc);
-            } else if (curDocButtons[buttonIndex.Show]) {
-                let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]);
-                if (curDocButtons[buttonIndex.OpenRight]) {
-                    //awaiting jump so that new scale can be found, since jumping is async
-                    await DocumentManager.Instance.jumpToDocument(curDoc, true);
-                } else {
-                    await DocumentManager.Instance.jumpToDocument(curDoc, true, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined));
-                }
-
-                let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc);
-                curDoc.viewScale = newScale;
-
-                //saving the scale user was on before zooming in
-                if (curScale !== 1) {
-                    this.childrenDocs[fromDoc].viewScale = curScale;
-                }
-
-            }
-            return;
-        }
-        let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]);
-        let curDocButtons = this.presElementsMappings.get(docToJump)!.selected;
-
-
-        if (curDocButtons[buttonIndex.OpenRight]) {
-            //awaiting jump so that new scale can be found, since jumping is async
-            await DocumentManager.Instance.jumpToDocument(docToJump, willZoom);
-        } else {
-            await DocumentManager.Instance.jumpToDocument(docToJump, willZoom, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined));
-        }
-        let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc);
-        curDoc.viewScale = newScale;
-        //saving the scale that user was on
-        if (curScale !== 1) {
-            this.childrenDocs[fromDoc].viewScale = curScale;
-        }
-
-    }
-
-    /**
-     * This function  checks if right option is clicked on a presentation element, if not it does open it as a tab
-     * with help of CollectionDockingView.
-     */
-    jumpToTabOrRight = (curDocButtons: boolean[], curDoc: Doc) => {
-        if (curDocButtons[buttonIndex.OpenRight]) {
-            DocumentManager.Instance.jumpToDocument(curDoc, false);
-        } else {
-            DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined));
-        }
-    }
-
-    /**
-     * Async function that supposedly return the doc that is located at given index.
-     */
-    getDocAtIndex = async (index: number) => {
-        const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc)));
-        if (!list) {
-            return undefined;
-        }
-        if (index < 0 || index >= list.length) {
-            return undefined;
-        }
-
-        this.curPresentation.selectedDoc = index;
-        //awaiting async call to finish to get Doc instance
-        const doc = await list[index];
-        return doc;
-    }
-
-    /**
-     * The function that removes a doc from a presentation. It also makes sure to
-     * do necessary updates to backUps and mappings stored locally.
-     */
-    @action
-    public RemoveDoc = async (index: number) => {
-        const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc)));
-        if (value) {
-            let removedDoc = await value.splice(index, 1)[0];
-
-            //removing the Presentation Element stored for it
-            this.presElementsMappings.delete(removedDoc);
-
-            let removedDocPresentId = StrCast(removedDoc.presentId);
-
-            //Removing it from local mapping of the groups
-            if (this.groupMappings.has(removedDocPresentId)) {
-                let removedDocsGroup = this.groupMappings.get(removedDocPresentId);
-                if (removedDocsGroup) {
-                    removedDocsGroup.splice(removedDocsGroup.indexOf(removedDoc), 1);
-                    if (removedDocsGroup.length === 0) {
-                        this.groupMappings.delete(removedDocPresentId);
-                    }
-                }
-            }
-
-
-            let castedList = Cast(this.presButtonBackUp.selectedButtonDocs, listSpec(Doc));
-            if (castedList) {
-                for (let doc of castedList) {
-                    let curDoc = await doc;
-                    let curDocId = StrCast(curDoc.docId);
-                    if (curDocId === removedDoc[Id]) {
-                        castedList.splice(castedList.indexOf(curDoc), 1);
-                        break;
-
-                    }
-                }
-            }
-
-            //removing it from the backup of groups
-            let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs);
-            if (castedGroupDocs) {
-                castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => {
-                    let castedKey = StrCast(groupDoc.presentIdStore, null);
-                    if (castedKey === removedDocPresentId) {
-                        let castedGrouping = await DocListCastAsync(groupDoc.grouping);
-                        if (castedGrouping) {
-                            castedGrouping.splice(castedGrouping.indexOf(removedDoc), 1);
-                            if (castedGrouping.length === 0) {
-                                castedGroupDocs!.splice(castedGroupDocs!.indexOf(groupDoc), 1);
-                            }
-                        }
-                    }
-
-                });
-
-            }
-
-
-        }
-    }
-
-    /**
-     * An alternative remove method that removes a doc from presentation by its actual
-     * reference.
-     */
-    public removeDocByRef = (doc: Doc) => {
-        let indexOfDoc = this.childrenDocs.indexOf(doc);
-        const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc)));
-        if (value) {
-            value.splice(indexOfDoc, 1)[0];
-        }
-        if (indexOfDoc !== - 1) {
-            return true;
-        }
-        return false;
-    }
-
-    //The function that is called when a document is clicked or reached through next or back.
-    //it'll also execute the necessary actions if presentation is playing.
-    @action
-    public gotoDocument = async (index: number, fromDoc: number) => {
-        const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc)));
-        if (!list) {
-            return;
-        }
-        if (index < 0 || index >= list.length) {
-            return;
-        }
-        this.curPresentation.selectedDoc = index;
-
-        if (!this.presStatus) {
-            this.presStatus = true;
-            this.startPresentation(index);
-        }
-
-        const doc = await list[index];
-        if (this.presStatus) {
-            this.navigateToElement(doc, fromDoc);
-            this.hideIfNotPresented(index);
-            this.showAfterPresented(index);
-        }
-
-    }
-
-    //Function that is called to resetGroupIds, so that documents get new groupIds at
-    //first load, when presentation is changed.
-    resetGroupIds = async () => {
-        let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs);
-        if (castedGroupDocs !== undefined) {
-            castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => {
-                let castedGrouping = await DocListCastAsync(groupDoc.grouping);
-                if (castedGrouping) {
-                    castedGrouping.forEach((doc: Doc) => {
-                        doc.presentId = Utils.GenerateGuid();
-                    });
-                }
-            });
-        }
-        runInAction(() => this.groupMappings = new Map());
-    }
-
-    /**
-     * Adds a document to the presentation view
-     **/
-    @undoBatch
-    @action
-    public PinDoc(doc: Doc) {
-        //add this new doc to props.Document
-        const data = Cast(this.curPresentation.data, listSpec(Doc));
-        if (data) {
-            data.push(doc);
-        } else {
-            this.curPresentation.data = new List([doc]);
-        }
-
-        this.toggle(true);
-    }
-
-    //Function that sets the store of the children docs.
-    @action
-    setChildrenDocs = (docList: Doc[]) => {
-        this.childrenDocs = docList;
-    }
-
-    //The function that is called to render the play or pause button depending on
-    //if presentation is running or not.
-    renderPlayPauseButton = () => {
-        if (this.presStatus) {
-            return <button title="Reset Presentation" className="presentation-button" onClick={this.startOrResetPres}><FontAwesomeIcon icon="stop" /></button>;
-        } else {
-            return <button title="Start Presentation From Start" className="presentation-button" onClick={this.startOrResetPres}><FontAwesomeIcon icon="play" /></button>;
-        }
-    }
-
-    //The function that starts or resets presentaton functionally, depending on status flag.
-    @action
-    startOrResetPres = async () => {
-        if (this.presStatus) {
-            this.resetPresentation();
-        } else {
-            this.presStatus = true;
-            let startIndex = await this.findStartDocument();
-            this.startPresentation(startIndex);
-            const current = NumCast(this.curPresentation.selectedDoc);
-            this.gotoDocument(startIndex, current);
-        }
-        this.curPresentation.presStatus = this.presStatus;
-    }
-
-    /**
-     * This method is called to find the start document of presentation. So
-     * that when user presses on play, the correct presentation element will be
-     * selected.
-     */
-    findStartDocument = async () => {
-        let docAtZero = await this.getDocAtIndex(0);
-        if (docAtZero === undefined) {
-            return 0;
-        }
-        let docAtZeroPresId = StrCast(docAtZero.presentId);
-
-        if (this.groupMappings.has(docAtZeroPresId)) {
-            let group = this.groupMappings.get(docAtZeroPresId)!;
-            let lastDoc = group[group.length - 1];
-            return this.childrenDocs.indexOf(lastDoc);
-        } else {
-            return 0;
-        }
-    }
-
-    //The function that resets the presentation by removing every action done by it. It also
-    //stops the presentaton.
-    @action
-    resetPresentation = () => {
-        this.childrenDocs.forEach((doc: Doc) => {
-            doc.opacity = 1;
-            doc.viewScale = 1;
-        });
-        this.curPresentation.selectedDoc = 0;
-        this.presStatus = false;
-        this.curPresentation.presStatus = this.presStatus;
-        if (this.childrenDocs.length === 0) {
-            return;
-        }
-        DocumentManager.Instance.zoomIntoScale(this.childrenDocs[0], 1);
-    }
-
-
-    //The function that starts the presentation, also checking if actions should be applied
-    //directly at start.
-    startPresentation = (startIndex: number) => {
-        let selectedButtons: boolean[];
-        this.presElementsMappings.forEach((component: PresentationElement, doc: Doc) => {
-            selectedButtons = component.selected;
-            if (selectedButtons[buttonIndex.HideTillPressed]) {
-                if (this.childrenDocs.indexOf(doc) > startIndex) {
-                    doc.opacity = 0;
-                }
-
-            }
-            if (selectedButtons[buttonIndex.HideAfter]) {
-                if (this.childrenDocs.indexOf(doc) < startIndex) {
-                    doc.opacity = 0;
-                }
-            }
-            if (selectedButtons[buttonIndex.FadeAfter]) {
-                if (this.childrenDocs.indexOf(doc) < startIndex) {
-                    doc.opacity = 0.5;
-                }
-            }
-
-        });
-
-    }
-
-    /**
-     * The function that is called to add a new presentation to the presentationView.
-     * It sets up te mappings and local copies of it. Resets the groupings and presentation.
-     * Makes the new presentation current selected, and retrieve the back-Ups if present.
-     */
-    @action
-    addNewPresentation = (presTitle: string) => {
-        //creating a new presentation doc
-        let newPresentationDoc = Docs.Create.TreeDocument([], { title: presTitle });
-        this.props.Documents.push(newPresentationDoc);
-
-        //setting that new doc as current
-        this.curPresentation = newPresentationDoc;
-
-        //storing the doc in local copies for easier access
-        let newGuid = Utils.GenerateGuid();
-        this.presentationsMapping.set(newGuid, newPresentationDoc);
-        this.presentationsKeyMapping.set(newPresentationDoc, newGuid);
-
-        //resetting the previous presentation's actions so that new presentation can be loaded.
-        this.resetGroupIds();
-        this.resetPresentation();
-        this.presElementsMappings = new Map();
-        this.currentSelectedPresValue = newGuid;
-        this.setPresentationBackUps();
-
-    }
-
-    /**
-     * The function that is called to change the current selected presentation.
-     * Changes the presentation, also resetting groupings and presentation in process.
-     * Plus retrieving the backUps for the newly selected presentation.
-     */
-    @action
-    getSelectedPresentation = (e: React.ChangeEvent<HTMLSelectElement>) => {
-        //get the guid of the selected presentation
-        let selectedGuid = e.target.value;
-        //set that as current presentation
-        this.curPresentation = this.presentationsMapping.get(selectedGuid)!;
-
-        //reset current Presentations local things so that new one can be loaded
-        this.resetGroupIds();
-        this.resetPresentation();
-        this.presElementsMappings = new Map();
-        this.currentSelectedPresValue = selectedGuid;
-        this.setPresentationBackUps();
-
-
-    }
-
-    /**
-     * The function that is called to render either select for presentations, or title inputting.
-     */
-    renderSelectOrPresSelection = () => {
-        let presentationList = DocListCast(this.props.Documents);
-        if (this.PresTitleInputOpen || this.PresTitleChangeOpen) {
-            return <input ref={(e) => this.titleInputElement = e!} type="text" className="presentationView-title" placeholder="Enter Name!" onKeyDown={this.submitPresentationTitle} />;
-        } else {
-            return <select value={this.currentSelectedPresValue} id="pres_selector" className="presentationView-title" onChange={this.getSelectedPresentation}>
-                {presentationList.map((doc: Doc, index: number) => {
-                    let mappedGuid = this.presentationsKeyMapping.get(doc);
-                    let docGuid: string = mappedGuid ? mappedGuid.toString() : "";
-                    return <option key={docGuid} value={docGuid}>{StrCast(doc.title)}</option>;
-                })}
-            </select>;
-        }
-    }
-
-    /**
-     * The function that is called on enter press of title input. It gives the
-     * new presentation the title user entered. If nothing is entered, gives a default title.
-     */
-    @action
-    submitPresentationTitle = (e: React.KeyboardEvent) => {
-        if (e.keyCode === 13) {
-            let presTitle = this.titleInputElement!.value;
-            this.titleInputElement!.value = "";
-            if (this.PresTitleInputOpen) {
-                if (presTitle === "") {
-                    presTitle = "Presentation";
-                }
-                this.PresTitleInputOpen = false;
-                this.addNewPresentation(presTitle);
-            } else if (this.PresTitleChangeOpen) {
-                this.PresTitleChangeOpen = false;
-                this.changePresentationTitle(presTitle);
-            }
-        }
-    }
-
-    /**
-     * The function that is called to remove a presentation from all its copies, and the main Container's
-     * list. Sets up the next presentation as current.
-     */
-    @action
-    removePresentation = async () => {
-        if (this.presentationsMapping.size !== 1) {
-            let presentationList = Cast(this.props.Documents, listSpec(Doc));
-            let batch = UndoManager.StartBatch("presRemoval");
-
-            //getting the presentation that will be removed
-            let removedDoc = this.presentationsMapping.get(this.currentSelectedPresValue!);
-            //that presentation is removed
-            presentationList!.splice(presentationList!.indexOf(removedDoc!), 1);
-
-            //its mappings are removed from local copies
-            this.presentationsKeyMapping.delete(removedDoc!);
-            this.presentationsMapping.delete(this.currentSelectedPresValue!);
-
-            //the next presentation is set as current
-            let remainingPresentations = this.presentationsMapping.values();
-            let nextDoc = remainingPresentations.next().value;
-            this.curPresentation = nextDoc;
-
-
-            //Storing these for being able to undo changes
-            let curGuid = this.currentSelectedPresValue!;
-            let curPresStatus = this.presStatus;
-
-            //resetting the groups and presentation actions so that next presentation gets loaded
-            this.resetGroupIds();
-            this.resetPresentation();
-            this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString();
-            this.setPresentationBackUps();
-
-            //Storing for undo
-            let currentGroups = this.groupMappings;
-            let curPresElemMapping = this.presElementsMappings;
-
-            //Event to undo actions that are not related to doc directly, aka. local things
-            UndoManager.AddEvent({
-                undo: action(() => {
-                    this.curPresentation = removedDoc!;
-                    this.presentationsMapping.set(curGuid, removedDoc!);
-                    this.presentationsKeyMapping.set(removedDoc!, curGuid);
-                    this.currentSelectedPresValue = curGuid;
-
-                    this.presStatus = curPresStatus;
-                    this.groupMappings = currentGroups;
-                    this.presElementsMappings = curPresElemMapping;
-                    this.setPresentationBackUps();
-
-                }),
-                redo: action(() => {
-                    this.curPresentation = nextDoc;
-                    this.presStatus = false;
-                    this.presentationsKeyMapping.delete(removedDoc!);
-                    this.presentationsMapping.delete(curGuid);
-                    this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString();
-                    this.setPresentationBackUps();
-
-                }),
-            });
-
-            batch.end();
-        }
-    }
-
-    /**
-     * The function that is called to change title of presentation to what user entered.
-     */
-    @undoBatch
-    changePresentationTitle = (newTitle: string) => {
-        if (newTitle === "") {
-            return;
-        }
-        this.curPresentation.title = newTitle;
-    }
-
-    /**
-     * On pointer down element that is catched on resizer of te
-     * presentation view. Sets up the event listeners to change the size with
-     * mouse move.
-     */
-    _downsize = 0;
-    onPointerDown = (e: React.PointerEvent) => {
-        this._downsize = e.clientX;
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        document.addEventListener("pointermove", this.onPointerMove);
-        document.addEventListener("pointerup", this.onPointerUp);
-        e.stopPropagation();
-        e.preventDefault();
-    }
-    /**
-     * Changes the size of the presentation view, with mouse move.
-     * Minimum size is set to 300, so that every button is visible.
-     */
-    @action
-    onPointerMove = (e: PointerEvent) => {
-
-        this.curPresentation.width = Math.max(window.innerWidth - e.clientX, presMinWidth);
-    }
-
-    /**
-     * The method that is called on pointer up event. It checks if the button is just
-     * clicked so that presentation view will be closed. The way it's done is to check 
-     * for minimal pixel change like 4, and accept it as it's just a click on top of the dragger.
-     */
-    @action
-    onPointerUp = (e: PointerEvent) => {
-        if (Math.abs(e.clientX - this._downsize) < 4) {
-            let presWidth = NumCast(this.curPresentation.width);
-            if (presWidth - presMinWidth !== 0) {
-                this.curPresentation.width = 0;
-            }
-            if (presWidth === 0) {
-                this.curPresentation.width = presMinWidth;
-            }
-        }
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-    }
-
-    /**
-     * This function is a setter that opens up the 
-     * presentation mode, by setting it's render flag
-     * to true. It also closes the presentation view.
-     */
-    @action
-    openPresMode = () => {
-        if (!this.presMode) {
-            this.curPresentation.width = 0;
-            this.presMode = true;
-        }
-    }
-
-    /**
-     * This function closes the presentation mode by setting its
-     * render flag to false. It also opens up the presentation view.
-     * By setting it to it's minimum size.
-     */
-    @action
-    closePresMode = () => {
-        if (this.presMode) {
-            this.presMode = false;
-            this.curPresentation.width = presMinWidth;
-        }
-
-    }
-
-    /**
-     * Function that is called to render the presentation mode, depending on its flag.
-     */
-    renderPresMode = () => {
-        if (this.presMode) {
-            return <PresModeMenu next={this.next} back={this.back} startOrResetPres={this.startOrResetPres} presStatus={this.presStatus} closePresMode={this.closePresMode} />;
-        } else {
-            return (null);
-        }
-
-    }
-
-    render() {
-
-        let width = NumCast(this.curPresentation.width);
-
-        return (
-            <div>
-                <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflowY: "scroll", overflowX: "hidden", opacity: this.opacity, transition: "0.7s opacity ease" }}>
-                    <div className="presentationView-heading">
-                        {this.renderSelectOrPresSelection()}
-                        <button title="Close Presentation" className='presentation-icon' onClick={this.closePresentation}><FontAwesomeIcon icon={"times"} /></button>
-                        <button title="Open Presentation Mode" className="presentation-icon" style={{ marginRight: 10 }} onClick={this.openPresMode}><FontAwesomeIcon icon={"eye"} /></button>
-                        <button title="Add Presentation" className="presentation-icon" style={{ marginRight: 10 }} onClick={() => {
-                            runInAction(() => { if (this.PresTitleChangeOpen) { this.PresTitleChangeOpen = false; } });
-                            runInAction(() => this.PresTitleInputOpen ? this.PresTitleInputOpen = false : this.PresTitleInputOpen = true);
-                        }}><FontAwesomeIcon icon={"plus"} /></button>
-                        <button title="Remove Presentation" className='presentation-icon' style={{ marginRight: 10 }} onClick={this.removePresentation}><FontAwesomeIcon icon={"minus"} /></button>
-                        <button title="Change Presentation Title" className="presentation-icon" style={{ marginRight: 10 }} onClick={() => {
-                            runInAction(() => { if (this.PresTitleInputOpen) { this.PresTitleInputOpen = false; } });
-                            runInAction(() => this.PresTitleChangeOpen ? this.PresTitleChangeOpen = false : this.PresTitleChangeOpen = true);
-                        }}><FontAwesomeIcon icon={"edit"} /></button>
-                    </div>
-                    <div className="presentation-buttons">
-                        <button title="Back" className="presentation-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
-                        {this.renderPlayPauseButton()}
-                        <button title="Next" className="presentation-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
-                    </div>
-
-                    <PresentationViewList
-                        mainDocument={this.curPresentation}
-                        deleteDocument={this.RemoveDoc}
-                        gotoDocument={this.gotoDocument}
-                        groupMappings={this.groupMappings}
-                        PresElementsMappings={this.presElementsMappings}
-                        setChildrenDocs={this.setChildrenDocs}
-                        presStatus={this.presStatus}
-                        presButtonBackUp={this.presButtonBackUp}
-                        presGroupBackUp={this.presGroupBackUp}
-                        removeDocByRef={this.removeDocByRef}
-                        clearElemMap={() => this.presElementsMappings.clear()}
-                    />
-                    <input
-                        type="checkbox"
-                        onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
-                            this.persistOpacity = e.target.checked;
-                            this.opacity = this.persistOpacity ? 1 : 0.4;
-                        })}
-                        checked={this.persistOpacity}
-                        style={{ position: "absolute", bottom: 5, left: 5 }}
-                        onPointerEnter={action(() => this.labelOpacity = 1)}
-                        onPointerLeave={action(() => this.labelOpacity = 0)}
-                    />
-                    <p style={{ position: "absolute", bottom: 1, left: 22, opacity: this.labelOpacity, transition: "0.7s opacity ease" }}>opacity {this.persistOpacity ? "persistent" : "on focus"}</p>
-                </div>
-                <div className="mainView-libraryHandle"
-                    style={{ cursor: "ew-resize", right: `${width - 10}px`, backgroundColor: "white", opacity: this.opacity, transition: "0.7s opacity ease" }}
-                    onPointerDown={this.onPointerDown}>
-                    <span title="library View Dragger" style={{ width: "100%", height: "100%", position: "absolute" }} />
-                </div>
-                {this.renderPresMode()}
-
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 4bb2ed55b..41fc49c2e 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -203,6 +203,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     removeDocument={returnFalse}
                     ScreenToLocalTransform={Transform.Identity}
                     addDocTab={returnFalse}
+                    pinToPres={returnFalse}
                     renderDepth={1}
                     PanelWidth={returnXDimension}
                     PanelHeight={returnYDimension}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index a8b616565..31f1f7a12 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -1,19 +1,18 @@
-import { observable, action, runInAction, ObservableMap } from "mobx";
-import { serializable, primitive, map, alias, list, PropSchema, custom } from "serializr";
-import { autoObject, SerializationHelper, Deserializable, afterDocDeserialize } from "../client/util/SerializationHelper";
+import { observable, ObservableMap, runInAction } from "mobx";
+import { alias, map, serializable } from "serializr";
 import { DocServer } from "../client/DocServer";
-import { setter, getter, getField, updateFunction, deleteProperty, makeEditable, makeReadOnly } from "./util";
-import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast, BoolCast, StrCast } from "./Types";
-import { listSpec } from "./Schema";
-import { ObjectField } from "./ObjectField";
-import { RefField, FieldId } from "./RefField";
-import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id, Copy } from "./FieldSymbols";
-import { scriptingGlobal, CompileScript, Scripting } from "../client/util/Scripting";
-import { List } from "./List";
 import { DocumentType } from "../client/documents/DocumentTypes";
-import { ComputedField, ScriptField } from "./ScriptField";
+import { CompileScript, Scripting, scriptingGlobal } from "../client/util/Scripting";
+import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper";
+import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, Update } from "./FieldSymbols";
+import { List } from "./List";
+import { ObjectField } from "./ObjectField";
 import { PrefetchProxy, ProxyField } from "./Proxy";
-//import { CurrentUserUtils } from "../server/authentication/models/current_user_utils";
+import { FieldId, RefField } from "./RefField";
+import { listSpec } from "./Schema";
+import { ComputedField } from "./ScriptField";
+import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast, ToConstructor } from "./Types";
+import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -72,6 +71,7 @@ export const HeightSym = Symbol("Height");
 export const UpdatingFromServer = Symbol("UpdatingFromServer");
 const CachedUpdates = Symbol("Cached updates");
 
+
 function fetchProto(doc: Doc) {
     const proto = doc.proto;
     if (proto instanceof Promise) {
@@ -151,10 +151,10 @@ export class Doc extends RefField {
     }
 
     private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
-
+    public static CurrentUserEmail: string = "";
     public async [HandleUpdate](diff: any) {
         const set = diff.$set;
-        const sameAuthor = this.author === "foo@bar.com";//CurrentUserUtils.email;
+        const sameAuthor = this.author === Doc.CurrentUserEmail;
         if (set) {
             for (const key in set) {
                 if (!key.startsWith("fields.")) {
@@ -327,14 +327,12 @@ export namespace Doc {
         return Array.from(results);
     }
 
-    export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean) {
+    export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) {
         if (target[key] === undefined) {
-            console.log("target key undefined");
             Doc.GetProto(target)[key] = new List<Doc>();
         }
         let list = Cast(target[key], listSpec(Doc));
         if (list) {
-            console.log("has list");
             if (allowDuplicates !== true) {
                 let pind = list.reduce((l, d, i) => d instanceof Doc && Doc.AreProtosEqual(d, doc) ? i : l, -1);
                 if (pind !== -1) {
@@ -342,15 +340,18 @@ export namespace Doc {
                 }
             }
             if (first) {
-                console.log("is first");
                 list.splice(0, 0, doc);
             }
             else {
-                console.log("not first");
                 let ind = relativeTo ? list.indexOf(relativeTo) : -1;
-                if (ind === -1) list.push(doc);
-                else list.splice(before ? ind : ind + 1, 0, doc);
-                console.log("index", ind);
+                if (ind === -1) {
+                    if (reversed) list.splice(0, 0, doc);
+                    else list.push(doc);
+                }
+                else {
+                    if (reversed) list.splice(before ? (list.length - ind) + 1 : list.length - ind, 0, doc);
+                    else list.splice(before ? ind : ind + 1, 0, doc);
+                }
             }
         }
         return true;
@@ -595,10 +596,14 @@ export namespace Doc {
         });
     }
 
-    export class DocBrush {
+
+    export class DocData {
+        @observable _user_doc: Doc = undefined!;
         @observable BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
     }
-    const manager = new DocBrush();
+    const manager = new DocData();
+    export function UserDoc(): Doc { return manager._user_doc; }
+    export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
     export function IsBrushed(doc: Doc) {
         return manager.BrushedDoc.has(doc) || manager.BrushedDoc.has(Doc.GetDataDoc(doc));
     }
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index c546e2aac..04194509c 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -7,7 +7,6 @@ import { ObjectField } from "./ObjectField";
 import { action } from "mobx";
 import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
 import { DocServer } from "../client/DocServer";
-import { CurrentUserUtils } from "../server/authentication/models/current_user_utils";
 
 function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
@@ -61,7 +60,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
     }
     const writeMode = DocServer.getFieldWriteMode(prop as string);
     const fromServer = target[UpdatingFromServer];
-    const sameAuthor = fromServer || (receiver.author === CurrentUserUtils.email);
+    const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
     const writeToDoc = sameAuthor || (writeMode !== DocServer.WriteMode.LiveReadonly);
     const writeToServer = sameAuthor || (writeMode === DocServer.WriteMode.Default);
     if (writeToDoc) {
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index f36f5b73d..508655605 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -10,20 +10,17 @@ import { CollectionView } from "../../../client/views/collections/CollectionView
 import { Doc } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
-import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
-import { RouteStore } from "../../RouteStore";
+import { Cast, StrCast } from "../../../new_fields/Types";
 import { Utils } from "../../../Utils";
+import { RouteStore } from "../../RouteStore";
 
 export class CurrentUserUtils {
-    private static curr_email: string;
     private static curr_id: string;
-    @observable private static user_document: Doc;
     //TODO tfs: these should be temporary...
     private static mainDocId: string | undefined;
 
-    public static get email() { return this.curr_email; }
     public static get id() { return this.curr_id; }
-    @computed public static get UserDocument() { return this.user_document; }
+    @computed public static get UserDocument() { return Doc.UserDoc(); }
     public static get MainDocId() { return this.mainDocId; }
     public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
 
@@ -32,7 +29,7 @@ export class CurrentUserUtils {
         doc.viewType = CollectionViewType.Tree;
         doc.dropAction = "alias";
         doc.layout = CollectionView.LayoutString();
-        doc.title = this.email;
+        doc.title = Doc.CurrentUserEmail
         this.updateUserDocument(doc);
         doc.data = new List<Doc>();
         doc.gridGap = 5;
@@ -58,6 +55,12 @@ export class CurrentUserUtils {
             recentlyClosed.boxShadow = "0 0";
             doc.recentlyClosed = recentlyClosed;
         }
+        if (doc.curPresentation === undefined) {
+            const curPresentation = Docs.Create.PresDocument(new List<Doc>(), { title: "Presentation" });
+            curPresentation.excludeFromLibrary = true;
+            curPresentation.boxShadow = "0 0";
+            doc.curPresentation = curPresentation;
+        }
         if (doc.sidebar === undefined) {
             const sidebar = Docs.Create.StackingDocument([doc.workspaces as Doc, doc, doc.recentlyClosed as Doc], { title: "Sidebar" });
             sidebar.excludeFromLibrary = true;
@@ -85,15 +88,15 @@ export class CurrentUserUtils {
 
     public static async loadUserDocument({ id, email }: { id: string, email: string }) {
         this.curr_id = id;
-        this.curr_email = email;
+        Doc.CurrentUserEmail = email;
         await rp.get(Utils.prepend(RouteStore.getUserDocumentId)).then(id => {
             if (id) {
                 return DocServer.GetRefField(id).then(async field => {
                     if (field instanceof Doc) {
                         await this.updateUserDocument(field);
-                        runInAction(() => this.user_document = field);
+                        runInAction(() => Doc.SetUserDoc(field));
                     } else {
-                        runInAction(() => this.user_document = this.createUserDocument(id));
+                        runInAction(() => Doc.SetUserDoc(this.createUserDocument(id)));
                     }
                 });
             } else {
-- 
cgit v1.2.3-70-g09d2


From 8225f3d2f6647b4163f7fe056af73c86f85c28d2 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 19 Aug 2019 17:00:59 -0400
Subject: cleaned up some buttons

---
 src/client/DocServer.ts                            |  2 +
 src/client/views/MainView.tsx                      | 45 +++-------------------
 .../views/collections/CollectionTreeView.tsx       |  4 +-
 src/client/views/collections/CollectionView.tsx    |  6 ++-
 src/client/views/nodes/DocumentView.tsx            | 40 +++++++++++++------
 5 files changed, 43 insertions(+), 54 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index bf5168c22..2cec1046b 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -33,6 +33,8 @@ export namespace DocServer {
         LivePlayground = 3,
     }
 
+    export let AclsMode = WriteMode.Default;
+
     const fieldWriteModes: { [field: string]: WriteMode } = {};
     const docsWithUpdates: { [field: string]: Set<Doc> } = {};
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7b7a5542d..b27b91c12 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
 import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
+import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -38,7 +38,6 @@ import { OverlayView } from './OverlayView';
 import PDFMenu from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
 import { FilterBox } from './search/FilterBox';
-import { DocumentManager } from '../util/DocumentManager';
 import PresModeMenu from './presentationview/PresentationModeMenu';
 import { PresBox } from './nodes/PresBox';
 
@@ -172,7 +171,7 @@ export class MainView extends React.Component {
         library.add(faCat);
         library.add(faFilePdf);
         library.add(faObjectGroup);
-        library.add(faTable);
+        library.add(faTv);
         library.add(faGlobeAsia);
         library.add(faUndoAlt);
         library.add(faRedoAlt);
@@ -452,30 +451,15 @@ export class MainView extends React.Component {
 
         let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
             [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
-            [React.createRef<HTMLDivElement>(), "table", "Add Presentation Trail", addPresNode],
+            [React.createRef<HTMLDivElement>(), "tv", "Add Presentation Trail", addPresNode],
             [React.createRef<HTMLDivElement>(), "globe-asia", "Add Website", addWebNode],
             [React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
-            // [React.createRef<HTMLDivElement>(), "clone", "Add Docking Frame", addDockingNode],
+            [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode],
             [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode
             //[React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
-            [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode]
         ];
         if (!ClientUtils.RELEASE) btns.unshift([React.createRef<HTMLDivElement>(), "cat", "Add Cat Image", addImageNode]);
 
-        const setWriteMode = (mode: DocServer.WriteMode) => {
-            console.log(DocServer.WriteMode[mode]);
-            const mode1 = mode;
-            const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
-            DocServer.setFieldWriteMode("x", mode1);
-            DocServer.setFieldWriteMode("y", mode1);
-            DocServer.setFieldWriteMode("width", mode1);
-            DocServer.setFieldWriteMode("height", mode1);
-
-            DocServer.setFieldWriteMode("panX", mode2);
-            DocServer.setFieldWriteMode("panY", mode2);
-            DocServer.setFieldWriteMode("scale", mode2);
-            DocServer.setFieldWriteMode("viewType", mode2);
-        };
         return < div id="add-nodes-menu" style={{ left: this.flyoutWidth + 20, bottom: 20 }} >
 
             <input type="checkbox" id="add-menu-toggle" ref={this.addMenuToggle} />
@@ -484,7 +468,6 @@ export class MainView extends React.Component {
             <div id="add-options-content">
                 <ul id="add-options-list">
                     <li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li>
-                    <li key="presentation"><button className="add-button round-button" title="Show Current Presentation Trail" onClick={this.togglePresentationView}><FontAwesomeIcon icon="table" size="sm" /></button></li>
                     <li key="undo"><button className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
                     <li key="redo"><button className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
                     {btns.map(btn =>
@@ -493,13 +476,6 @@ export class MainView extends React.Component {
                                 <FontAwesomeIcon icon={btn[1]} size="sm" />
                             </button>
                         </div></li>)}
-                    <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
-                    {ClientUtils.RELEASE ? [] : [
-                        <li key="test"><button className="add-button round-button" title="Default" onClick={() => setWriteMode(DocServer.WriteMode.Default)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>,
-                        <li key="test1"><button className="add-button round-button" title="Playground" onClick={() => setWriteMode(DocServer.WriteMode.Playground)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>,
-                        <li key="test2"><button className="add-button round-button" title="Live Playground" onClick={() => setWriteMode(DocServer.WriteMode.LivePlayground)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>,
-                        <li key="test3"><button className="add-button round-button" title="Live Readonly" onClick={() => setWriteMode(DocServer.WriteMode.LiveReadonly)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
-                    ]}
                     <li key="color"><button className="add-button round-button" title="Select Color" style={{ zIndex: 1000 }} onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
                         <div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
                             <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
@@ -541,17 +517,6 @@ export class MainView extends React.Component {
         this.isSearchVisible = !this.isSearchVisible;
     }
 
-    togglePresentationView = () => {
-        if (CurrentUserUtils.UserDocument.curPresentation) {
-            let isOpen = DocumentManager.Instance.getDocumentView(CurrentUserUtils.UserDocument.curPresentation as Doc);
-            if (isOpen) {
-                CollectionDockingView.Instance.CloseRightSplit(CurrentUserUtils.UserDocument.curPresentation as Doc);
-            } else {
-                CollectionDockingView.Instance.AddRightSplit(CurrentUserUtils.UserDocument.curPresentation as Doc, undefined);
-            }
-        }
-    }
-
     private get dictationOverlay() {
         let display = this.dictationOverlayVisible;
         let success = this.dictationSuccess;
@@ -581,7 +546,7 @@ export class MainView extends React.Component {
         let next = () => PresBox.CurrentPresentation.next();
         let back = () => PresBox.CurrentPresentation.back();
         let startOrResetPres = () => PresBox.CurrentPresentation.startOrResetPres();
-        let closePresMode = action(() => {PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document)});
+        let closePresMode = action(() => { PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document) });
         return !PresBox.CurrentPresentation || !PresBox.CurrentPresentation.presMode ? (null) : <PresModeMenu next={next} back={back} presStatus={PresBox.CurrentPresentation.presStatus} startOrResetPres={startOrResetPres} closePresMode={closePresMode} > </PresModeMenu>
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 6b9cd57b3..b6a14238e 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -254,7 +254,7 @@ class TreeView extends React.Component<TreeViewProps> {
         doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
 
         let rows: JSX.Element[] = [];
-        for (let key of Object.keys(ids).sort()) {
+        for (let key of Object.keys(ids).slice().sort()) {
             let contents = doc[key];
             let contentElement: JSX.Element[] | JSX.Element = [];
 
@@ -419,7 +419,7 @@ class TreeView extends React.Component<TreeViewProps> {
         }
 
         let descending = BoolCast(containingCollection.stackingHeadersSortDescending);
-        docs.sort(function (a, b): 1 | -1 {
+        docs.slice().sort(function (a, b): 1 | -1 {
             let descA = descending ? b : a;
             let descB = descending ? a : b;
             let first = descA[String(containingCollection.sectionFilter)];
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7e1adaa19..3a8253720 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -108,9 +108,13 @@ export class CollectionView extends React.Component<FieldViewProps> {
             ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" });
             let existing = ContextMenu.Instance.findByDescription("Layout...");
             let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
-            layoutItems.push({ description: "Create Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
             layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
             !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
+
+            let makes = ContextMenu.Instance.findByDescription("Make...");
+            let makeItems: ContextMenuProps[] = makes && "subitems" in makes ? makes.subitems : [];
+            makeItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
+            !makes && ContextMenu.Instance.addItem({ description: "Make...", subitems: makeItems, icon: "hand-point-right" });
         }
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4378a51fb..9e71094b0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -573,21 +573,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
         let existingMake = ContextMenu.Instance.findByDescription("Make...");
         let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : [];
-        makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
-        makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" });
-        makes.push({ description: "Edit OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") });
+        makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
+        makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Into Button", event: this.makeBtnClicked, icon: "concierge-bell" });
+        makes.push({ description: "OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") });
         makes.push({
-            description: "Make Portal", event: () => {
+            description: "Into Portal", event: () => {
                 let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" });
-                //Doc.GetProto(this.props.Document).subBulletDocs = new List<Doc>([portal]);
-                //summary.proto!.maximizeLocation = "inTab";  // or "inPlace", or "onRight"
-                //Doc.GetProto(this.props.Document).templates = new List<string>([Templates.Bullet.Layout]);
-                //let coll = Docs.Create.StackingDocument([this.props.Document, portal], { x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y), width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".cont" });
-                //this.props.addDocument && this.props.addDocument(coll);
-                //this.props.removeDocument && this.props.removeDocument(this.props.Document);
                 DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal");
                 this.makeBtnClicked();
-
             }, icon: "window-restore"
         });
         !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" });
@@ -684,6 +677,31 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         runInAction(() => {
             cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" });
+            if (!ClientUtils.RELEASE) {
+                let setWriteMode = (mode: DocServer.WriteMode) => {
+                    console.log(DocServer.WriteMode[mode]);
+                    DocServer.AclsMode = mode;
+                    const mode1 = mode;
+                    const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
+                    DocServer.setFieldWriteMode("x", mode1);
+                    DocServer.setFieldWriteMode("y", mode1);
+                    DocServer.setFieldWriteMode("width", mode1);
+                    DocServer.setFieldWriteMode("height", mode1);
+
+                    DocServer.setFieldWriteMode("panX", mode2);
+                    DocServer.setFieldWriteMode("panY", mode2);
+                    DocServer.setFieldWriteMode("scale", mode2);
+                    DocServer.setFieldWriteMode("viewType", mode2);
+                };
+                let aclsMenu: ContextMenuProps[] = [];
+                aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
+                aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
+                aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
+                aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
+                cm.addItem({ description: "Collaboration ACLs...", subitems: aclsMenu, icon: "share" });
+                cm.addItem({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
+            }
+
             if (!this.topMost) {
                 // DocumentViews should stop propagation of this event
                 e.stopPropagation();
-- 
cgit v1.2.3-70-g09d2


From 73bb0f572e261850583b698dd819d35a6fe768ec Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 20 Aug 2019 21:42:37 -0400
Subject: cleanup

---
 src/client/views/MainView.tsx                      | 2 +-
 src/client/views/collections/CollectionSubView.tsx | 2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 6 +++---
 3 files changed, 5 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index e85374f6a..f28844009 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -15,7 +15,7 @@ import { listSpec } from '../../new_fields/Schema';
 import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
-import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString, PostToServer } from '../../Utils';
+import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils';
 import { DocServer } from '../DocServer';
 import { Docs } from '../documents/Documents';
 import { ClientUtils } from '../util/ClientUtils';
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5b50c545c..99e5ab7b3 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -213,7 +213,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 let proto = newBox.proto!;
                 proto.autoHeight = true;
                 proto[GoogleRef] = matches[2];
-                proto.data = "Please select and then click on this document's pull button to load its contents from from Google Docs...";
+                proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs...";
                 proto.backgroundColor = "#eeeeff";
                 this.props.addDocument(newBox);
                 return;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 2b01fb70a..606e8edb0 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -389,9 +389,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 };
             }
             let redo = async () => {
-                if (this._editorView && reference) {
-                    let data = Cast(this.dataDoc.data, RichTextField);
-                    let content = data ? data[ToPlainText]() : this._editorView.state.doc.textContent;
+                let data = Cast(this.dataDoc.data, RichTextField);
+                if (this._editorView && reference && data) {
+                    let content = data[ToPlainText]();
                     let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
                     let pushSuccess = response !== undefined && !("errors" in response);
                     dataDoc.unchanged = pushSuccess;
-- 
cgit v1.2.3-70-g09d2


From 5c9f40006aa157c58ec40828ebd4845c16daa8af Mon Sep 17 00:00:00 2001
From: monikahedman <monika_hedman@brown.edu>
Date: Wed, 21 Aug 2019 15:08:39 -0400
Subject: start of making link follow

---
 src/client/documents/DocumentTypes.ts           |   1 +
 src/client/documents/Documents.ts               |   4 +
 src/client/views/MainView.tsx                   |  11 ++
 src/client/views/linking/LinkFollowBox.scss     |   6 +
 src/client/views/linking/LinkFollowBox.tsx      | 148 ++++++++++++++++++++++++
 src/client/views/linking/LinkMenuItem.tsx       |  12 +-
 src/client/views/nodes/DocumentContentsView.tsx |   4 +-
 7 files changed, 179 insertions(+), 7 deletions(-)
 create mode 100644 src/client/views/linking/LinkFollowBox.scss

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

diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 1578e49fe..381981e1b 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -19,4 +19,5 @@ export enum DocumentType {
     YOUTUBE = "youtube",
     DRAGBOX = "dragbox",
     PRES = "presentation",
+    LINKFOLLOW = "linkfollow",
 }
\ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 47df17329..cd612aaa9 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -45,6 +45,7 @@ import { PresBox } from "../views/nodes/PresBox";
 import { ComputedField } from "../../new_fields/ScriptField";
 import { ProxyField } from "../../new_fields/Proxy";
 import { DocumentType } from "./DocumentTypes";
+import { LinkFollowBox } from "../views/linking/LinkFollowBox";
 //import { PresBox } from "../views/nodes/PresBox";
 //import { PresField } from "../../new_fields/PresField";
 var requestImageSize = require('../util/request-image-size');
@@ -169,6 +170,9 @@ export namespace Docs {
             [DocumentType.DRAGBOX, {
                 layout: { view: DragBox },
                 options: { width: 40, height: 40 },
+            }],
+            [DocumentType.LINKFOLLOW, {
+                layout: { view: LinkFollowBox }
             }]
         ]);
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f28844009..f3c8a176c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -40,6 +40,7 @@ import { PreviewCursor } from './PreviewCursor';
 import { FilterBox } from './search/FilterBox';
 import PresModeMenu from './presentationview/PresentationModeMenu';
 import { PresBox } from './nodes/PresBox';
+import { LinkFollowBox } from './linking/LinkFollowBox';
 
 @observer
 export class MainView extends React.Component {
@@ -55,6 +56,8 @@ export class MainView extends React.Component {
 
     public overlayTimeout: NodeJS.Timeout | undefined;
 
+    @observable private _linkFollowBox = false;
+
     public initiateDictationFade = () => {
         let duration = DictationManager.Commands.dictationFadeDuration;
         this.overlayTimeout = setTimeout(() => {
@@ -498,6 +501,12 @@ export class MainView extends React.Component {
         this._colorPickerDisplay = close ? false : !this._colorPickerDisplay;
     }
 
+    @action
+    toggleLinkFollowBox = () => {
+        console.log("toggling link editor")
+        this._linkFollowBox = !this._linkFollowBox;
+    }
+
     /* @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() {
@@ -506,6 +515,7 @@ export class MainView extends React.Component {
         return [
             this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <FilterBox /> </div> : null,
             <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
+                <button onClick={this.toggleLinkFollowBox}>Open Link Editor</button>
                 <button onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}>Log Out</button></div>
         ];
 
@@ -564,6 +574,7 @@ export class MainView extends React.Component {
                 <PDFMenu />
                 <MainOverlayTextBox firstinstance={true} />
                 <OverlayView />
+                {/* <LinkFollowBox /> */}
             </div >
         );
     }
diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss
new file mode 100644
index 000000000..c764b002f
--- /dev/null
+++ b/src/client/views/linking/LinkFollowBox.scss
@@ -0,0 +1,6 @@
+@import "../globalCssVariables";
+
+.linkFollowBox-main {
+    position: absolute;
+    background: $main-accent;
+}
\ No newline at end of file
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 061a4fa93..7fc4449d3 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -2,9 +2,157 @@ import { observable, computed, action, trace } from "mobx";
 import React = require("react");
 import { observer } from "mobx-react";
 import { FieldViewProps, FieldView } from "../nodes/FieldView";
+import { Doc } from "../../../new_fields/Doc";
+import { undoBatch } from "../../util/UndoManager";
+import { NumCast, FieldValue, Cast } from "../../../new_fields/Types";
+import { CollectionViewType } from "../collections/CollectionBaseView";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { SelectionManager } from "../../util/SelectionManager";
+import { DocumentManager } from "../../util/DocumentManager";
 
 @observer
 export class LinkFollowBox extends React.Component<FieldViewProps> {
 
     public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); }
+    public static Instance: LinkFollowBox;
+    //set this to be the default link behavior, can be any of the above
+
+    unhighlight = () => {
+        Doc.UnhighlightAll();
+        document.removeEventListener("pointerdown", this.unhighlight);
+    }
+
+    @action
+    highlightDoc = (destinationDoc: Doc) => {
+        document.removeEventListener("pointerdown", this.unhighlight);
+        Doc.HighlightDoc(destinationDoc);
+        window.setTimeout(() => {
+            document.addEventListener("pointerdown", this.unhighlight);
+        }, 10000);
+    }
+
+    // NOT TESTED
+    // col = collection the doc is in
+    // target = the document to center on
+    @undoBatch
+    openLinkColRight = (destinationDoc: Doc, col: Doc) => {
+        col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
+        if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+            const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2;
+            const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2;
+            col.panX = newPanX;
+            col.panY = newPanY;
+        }
+        CollectionDockingView.Instance.AddRightSplit(col, undefined);
+    }
+
+    // DONE
+    // this opens the linked doc in a right split, NOT in its collection
+    @undoBatch
+    openLinkRight = (destinationDoc: Doc) => {
+        this.highlightDoc(destinationDoc);
+        let alias = Doc.MakeAlias(destinationDoc);
+        CollectionDockingView.Instance.AddRightSplit(alias, undefined);
+        SelectionManager.DeselectAll();
+    }
+
+    // DONE
+    // this is the standard "follow link" (jump to document)
+    // taken from follow link
+    @undoBatch
+    jumpToLink = async (destinationDoc: Doc, shouldZoom: boolean, linkDoc: Doc) => {
+        //there is an issue right now so this will be false automatically
+        shouldZoom = false;
+        this.highlightDoc(destinationDoc);
+        let jumpToDoc = destinationDoc;
+        let pdfDoc = FieldValue(Cast(destinationDoc, Doc));
+        if (pdfDoc) {
+            jumpToDoc = pdfDoc;
+        }
+        let proto = Doc.GetProto(linkDoc);
+        let targetContext = await Cast(proto.targetContext, Doc);
+        let sourceContext = await Cast(proto.sourceContext, Doc);
+
+        let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
+
+        if (destinationDoc === linkDoc.anchor2 && targetContext) {
+            DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext);
+        }
+        else if (destinationDoc === linkDoc.anchor1 && sourceContext) {
+            DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!));
+        }
+        else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
+            DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, NumCast((destinationDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page)));
+
+        }
+        else {
+            DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc);
+        }
+    }
+
+    // DONE
+    // opens link in new tab (not in a collection)
+    // this opens it full screen, do we need a separate full screen option?
+    @undoBatch
+    openLinkTab = (destinationDoc: Doc) => {
+        this.highlightDoc(destinationDoc);
+        let fullScreenAlias = Doc.MakeAlias(destinationDoc);
+        this.props.addDocTab(fullScreenAlias, undefined, "inTab");
+        SelectionManager.DeselectAll();
+    }
+
+    // NOT TESTED
+    // opens link in new tab in collection
+    // col = collection the doc is in
+    // target = the document to center on
+    @undoBatch
+    openLinkColTab = (destinationDoc: Doc, col: Doc) => {
+        this.highlightDoc(destinationDoc);
+        col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
+        if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+            const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2;
+            const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2;
+            col.panX = newPanX;
+            col.panY = newPanY;
+        }
+        // CollectionDockingView.Instance.AddRightSplit(col, undefined);
+        this.props.addDocTab(col, undefined, "inTab");
+        SelectionManager.DeselectAll();
+    }
+
+    // DONE
+    // this will open a link next to the source doc
+    @undoBatch
+    openLinkInPlace = (destinationDoc: Doc, sourceDoc: Doc) => {
+        this.highlightDoc(destinationDoc);
+
+        let alias = Doc.MakeAlias(destinationDoc);
+        let y = NumCast(sourceDoc.y);
+        let x = NumCast(sourceDoc.x);
+
+        let width = NumCast(sourceDoc.width);
+        let height = NumCast(sourceDoc.height);
+
+        alias.x = x + width + 30;
+        alias.y = y;
+        alias.width = width;
+        alias.height = height;
+
+        SelectionManager.SelectedDocuments().map(dv => {
+            if (dv.props.Document === sourceDoc) {
+                dv.props.addDocument && dv.props.addDocument(alias, false);
+            }
+        });
+    }
+
+    private defaultLinkBehavior: any = this.openLinkInPlace;
+    private currentLinkBehavior: any = this.defaultLinkBehavior;
+
+    render() {
+        return (
+            <div className="linkFollowBox-main">
+
+            </div>
+        );
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 65516b374..41723030d 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -52,11 +52,11 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     // col = collection the doc is in
     // target = the document to center on
     @undoBatch
-    openLinkColRight = ({ col, target }: { col: Doc, target: Doc }) => {
+    openLinkColRight = (col: Doc) => {
         col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
         if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-            const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2;
-            const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2;
+            const newPanX = NumCast(this.props.destinationDoc.x) + NumCast(this.props.destinationDoc.width) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2;
+            const newPanY = NumCast(this.props.destinationDoc.y) + NumCast(this.props.destinationDoc.height) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2;
             col.panX = newPanX;
             col.panY = newPanY;
         }
@@ -124,12 +124,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     // col = collection the doc is in
     // target = the document to center on
     @undoBatch
-    openLinkColTab = ({ col, target }: { col: Doc, target: Doc }) => {
+    openLinkColTab = (col: Doc) => {
         this.highlightDoc();
         col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
         if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-            const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2;
-            const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2;
+            const newPanX = NumCast(this.props.destinationDoc.x) + NumCast(this.props.destinationDoc.width) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2;
+            const newPanY = NumCast(this.props.destinationDoc.y) + NumCast(this.props.destinationDoc.height) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2;
             col.panX = newPanX;
             col.panY = newPanY;
         }
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index d77662355..d0e117fe4 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -14,6 +14,7 @@ import { ImageBox } from "./ImageBox";
 import { DragBox } from "./DragBox";
 import { ButtonBox } from "./ButtonBox";
 import { PresBox } from "./PresBox";
+import { LinkFollowBox } from "../linking/LinkFollowBox";
 import { IconBox } from "./IconBox";
 import { KeyValueBox } from "./KeyValueBox";
 import { PDFBox } from "./PDFBox";
@@ -30,6 +31,7 @@ import { List } from "../../../new_fields/List";
 import { Doc } from "../../../new_fields/Doc";
 import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
 import { ScriptField } from "../../../new_fields/ScriptField";
+import { fromPromise } from "mobx-utils";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -108,7 +110,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
             blacklistedAttrs={[]}
-            components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox }}
+            components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox }}
             bindings={this.CreateBindings()}
             jsx={this.finalLayout}
             showWarnings={true}
-- 
cgit v1.2.3-70-g09d2


From 1fb290bcc1c46214cfd553f31c1282d2694530ea Mon Sep 17 00:00:00 2001
From: monikahedman <monika_hedman@brown.edu>
Date: Thu, 22 Aug 2019 19:42:54 -0400
Subject: making the box come up

---
 src/client/documents/Documents.ts           |  4 ++
 src/client/views/MainView.tsx               |  2 +
 src/client/views/linking/LinkFollowBox.scss |  2 +-
 src/client/views/linking/LinkFollowBox.tsx  | 87 ++++++++++++++++++-----------
 src/client/views/linking/LinkMenuItem.tsx   | 12 +++-
 5 files changed, 73 insertions(+), 34 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index cd612aaa9..b7c8f4c12 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -446,6 +446,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.DRAGBOX), undefined, { ...(options || {}) });
         }
 
+        export function LinkFollowBoxDocument(options?: DocumentOptions) {
+            return InstanceFromProto(Prototypes.get(DocumentType.LINKFOLLOW), undefined, { ...(options || {}) });
+        }
+
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
         }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f3c8a176c..79c44ae72 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -443,6 +443,7 @@ export class MainView extends React.Component {
         let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
 
         let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
+        let addLinkFollowBox = action(() => Docs.Create.LinkFollowBoxDocument({ width: 200, height: 500, title: "Link Follow Document" }));
         let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" }));
         let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" }));
         let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" }));
@@ -458,6 +459,7 @@ export class MainView extends React.Component {
             [React.createRef<HTMLDivElement>(), "globe-asia", "Add Website", addWebNode],
             [React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
             [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode],
+            [React.createRef<HTMLDivElement>(), "caret-up", "Open Link Follow Box", addLinkFollowBox],
             [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode
             //[React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
         ];
diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss
index c764b002f..522191792 100644
--- a/src/client/views/linking/LinkFollowBox.scss
+++ b/src/client/views/linking/LinkFollowBox.scss
@@ -2,5 +2,5 @@
 
 .linkFollowBox-main {
     position: absolute;
-    background: $main-accent;
+    background: red;
 }
\ No newline at end of file
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 7fc4449d3..247b67776 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -9,13 +9,22 @@ import { CollectionViewType } from "../collections/CollectionBaseView";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { SelectionManager } from "../../util/SelectionManager";
 import { DocumentManager } from "../../util/DocumentManager";
+import { DocumentView } from "../nodes/DocumentView";
+import "./LinkFollowBox.scss";
+
+export type LinkParamOptions = {
+    container: Doc;
+    context: Doc;
+    sourceDoc: Doc;
+    shoudldZoom: boolean;
+    linkDoc: Doc;
+};
 
 @observer
 export class LinkFollowBox extends React.Component<FieldViewProps> {
 
     public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); }
     public static Instance: LinkFollowBox;
-    //set this to be the default link behavior, can be any of the above
 
     unhighlight = () => {
         Doc.UnhighlightAll();
@@ -31,19 +40,32 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
         }, 10000);
     }
 
+    // DONE
+    @undoBatch
+    openFullScreen = (destinationDoc: Doc) => {
+        let view: DocumentView | null = DocumentManager.Instance.getDocumentView(destinationDoc)
+        view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
+    }
+
+    // should container be a doc or documentview or what? This one needs work and is more long term
+    @undoBatch
+    openInContainer = (destinationDoc: Doc, options: { container: Doc }) => {
+
+    }
+
     // NOT TESTED
     // col = collection the doc is in
     // target = the document to center on
     @undoBatch
-    openLinkColRight = (destinationDoc: Doc, col: Doc) => {
-        col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
-        if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+    openLinkColRight = (destinationDoc: Doc, options: { context: Doc }) => {
+        options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
+        if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
             const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2;
             const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2;
-            col.panX = newPanX;
-            col.panY = newPanY;
+            options.context.panX = newPanX;
+            options.context.panY = newPanY;
         }
-        CollectionDockingView.Instance.AddRightSplit(col, undefined);
+        CollectionDockingView.Instance.AddRightSplit(options.context, undefined);
     }
 
     // DONE
@@ -60,39 +82,39 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     // this is the standard "follow link" (jump to document)
     // taken from follow link
     @undoBatch
-    jumpToLink = async (destinationDoc: Doc, shouldZoom: boolean, linkDoc: Doc) => {
+    jumpToLink = async (destinationDoc: Doc, options: { shouldZoom: boolean, linkDoc: Doc }) => {
         //there is an issue right now so this will be false automatically
-        shouldZoom = false;
+        options.shouldZoom = false;
         this.highlightDoc(destinationDoc);
         let jumpToDoc = destinationDoc;
         let pdfDoc = FieldValue(Cast(destinationDoc, Doc));
         if (pdfDoc) {
             jumpToDoc = pdfDoc;
         }
-        let proto = Doc.GetProto(linkDoc);
+        let proto = Doc.GetProto(options.linkDoc);
         let targetContext = await Cast(proto.targetContext, Doc);
         let sourceContext = await Cast(proto.sourceContext, Doc);
 
         let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
 
-        if (destinationDoc === linkDoc.anchor2 && targetContext) {
-            DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext);
+        if (destinationDoc === options.linkDoc.anchor2 && targetContext) {
+            DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, async document => dockingFunc(document), undefined, targetContext);
         }
-        else if (destinationDoc === linkDoc.anchor1 && sourceContext) {
-            DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!));
+        else if (destinationDoc === options.linkDoc.anchor1 && sourceContext) {
+            DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, document => dockingFunc(sourceContext!));
         }
         else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
-            DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, NumCast((destinationDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page)));
+            DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, undefined, undefined, NumCast((destinationDoc === options.linkDoc.anchor2 ? options.linkDoc.anchor2Page : options.linkDoc.anchor1Page)));
 
         }
         else {
-            DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc);
+            DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, dockingFunc);
         }
     }
 
     // DONE
     // opens link in new tab (not in a collection)
-    // this opens it full screen, do we need a separate full screen option?
+    // this opens it full screen in new tab
     @undoBatch
     openLinkTab = (destinationDoc: Doc) => {
         this.highlightDoc(destinationDoc);
@@ -106,32 +128,32 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     // col = collection the doc is in
     // target = the document to center on
     @undoBatch
-    openLinkColTab = (destinationDoc: Doc, col: Doc) => {
+    openLinkColTab = (destinationDoc: Doc, options: { context: Doc }) => {
         this.highlightDoc(destinationDoc);
-        col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
-        if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+        options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
+        if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
             const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2;
             const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2;
-            col.panX = newPanX;
-            col.panY = newPanY;
+            options.context.panX = newPanX;
+            options.context.panY = newPanY;
         }
         // CollectionDockingView.Instance.AddRightSplit(col, undefined);
-        this.props.addDocTab(col, undefined, "inTab");
+        this.props.addDocTab(options.context, undefined, "inTab");
         SelectionManager.DeselectAll();
     }
 
     // DONE
     // this will open a link next to the source doc
     @undoBatch
-    openLinkInPlace = (destinationDoc: Doc, sourceDoc: Doc) => {
+    openLinkInPlace = (destinationDoc: Doc, options: { sourceDoc: Doc }) => {
         this.highlightDoc(destinationDoc);
 
         let alias = Doc.MakeAlias(destinationDoc);
-        let y = NumCast(sourceDoc.y);
-        let x = NumCast(sourceDoc.x);
+        let y = NumCast(options.sourceDoc.y);
+        let x = NumCast(options.sourceDoc.x);
 
-        let width = NumCast(sourceDoc.width);
-        let height = NumCast(sourceDoc.height);
+        let width = NumCast(options.sourceDoc.width);
+        let height = NumCast(options.sourceDoc.height);
 
         alias.x = x + width + 30;
         alias.y = y;
@@ -139,18 +161,19 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
         alias.height = height;
 
         SelectionManager.SelectedDocuments().map(dv => {
-            if (dv.props.Document === sourceDoc) {
+            if (dv.props.Document === options.sourceDoc) {
                 dv.props.addDocument && dv.props.addDocument(alias, false);
             }
         });
     }
 
-    private defaultLinkBehavior: any = this.openLinkInPlace;
-    private currentLinkBehavior: any = this.defaultLinkBehavior;
+    //set this to be the default link behavior, can be any of the above
+    private defaultLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.openLinkInPlace;
+    private currentLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.defaultLinkBehavior;
 
     render() {
         return (
-            <div className="linkFollowBox-main">
+            <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document.height), width: NumCast(this.props.Document.width) }}>
 
             </div>
         );
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 41723030d..60f27c664 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -15,6 +15,7 @@ import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { SelectionManager } from '../../util/SelectionManager';
 import { CollectionViewType } from '../collections/CollectionBaseView';
 import { DocumentView } from '../nodes/DocumentView';
+import { SearchUtil } from '../../util/SearchUtil';
 library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
 
 
@@ -63,6 +64,13 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         CollectionDockingView.Instance.AddRightSplit(col, undefined);
     }
 
+    // DONE
+    @undoBatch
+    openFullScreen = () => {
+        let view: DocumentView | null = DocumentManager.Instance.getDocumentView(this.props.destinationDoc);
+        view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
+    }
+
     // DONE
     // this opens the linked doc in a right split, NOT in its collection
     @undoBatch
@@ -161,10 +169,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                 dv.props.addDocument && dv.props.addDocument(alias, false);
             }
         });
+
+        this.jumpToLink(false);
     }
 
     //set this to be the default link behavior, can be any of the above
-    private defaultLinkBehavior: any = this.openLinkInPlace;
+    private defaultLinkBehavior: any = this.openLinkTab;
 
     onEdit = (e: React.PointerEvent): void => {
         e.stopPropagation();
-- 
cgit v1.2.3-70-g09d2


From da192a9f580174e3b452079026e70d5c08d8a90f Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 23 Aug 2019 16:11:23 -0400
Subject: added start of an overlay for documents.

---
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/OverlayView.tsx                   | 31 ++++++++++++++++++++--
 src/client/views/nodes/DocumentView.tsx            |  1 +
 .../authentication/models/current_user_utils.ts    |  6 +++++
 4 files changed, 37 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f28844009..a02214deb 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -404,7 +404,7 @@ export class MainView extends React.Component {
     get mainContent() {
         let sidebar = CurrentUserUtils.UserDocument.sidebar;
         if (!(sidebar instanceof Doc)) return (null);
-        return <div>
+        return <div className="mainContent" style={{ width: "100%", height: "100%", position: "absolute" }}>
             <div className="mainView-libraryHandle"
                 style={{ cursor: "ew-resize", left: `${this.flyoutWidth - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }}
                 onPointerDown={this.onPointerDown}>
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 2f2579057..a60dc591c 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,9 +1,15 @@
 import * as React from "react";
 import { observer } from "mobx-react";
 import { observable, action } from "mobx";
-import { Utils } from "../../Utils";
+import { Utils, emptyFunction, returnOne, returnTrue, returnEmptyString } from "../../Utils";
 
 import './OverlayView.scss';
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { DocListCast, Doc } from "../../new_fields/Doc";
+import { Id } from "../../new_fields/FieldSymbols";
+import { DocumentView } from "./nodes/DocumentView";
+import { Transform } from "../util/Transform";
+import { CollectionFreeFormDocumentView } from "./nodes/CollectionFreeFormDocumentView";
 
 export type OverlayDisposer = () => void;
 
@@ -134,8 +140,29 @@ export class OverlayView extends React.Component {
 
     render() {
         return (
-            <div>
+            <div className="overlayView">
                 {this._elements}
+                {CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => (
+                    <CollectionFreeFormDocumentView key={d[Id]}
+                        Document={d}
+                        bringToFront={emptyFunction}
+                        addDocument={undefined}
+                        removeDocument={undefined}
+                        ContentScaling={returnOne}
+                        PanelWidth={returnOne}
+                        PanelHeight={returnOne}
+                        ScreenToLocalTransform={Transform.Identity}
+                        renderDepth={1}
+                        selectOnLoad={false}
+                        parentActive={returnTrue}
+                        whenActiveChanged={emptyFunction}
+                        focus={emptyFunction}
+                        backgroundColor={returnEmptyString}
+                        addDocTab={emptyFunction}
+                        pinToPres={emptyFunction}
+                        ContainingCollectionView={undefined}
+                        zoomToScale={emptyFunction}
+                        getScale={returnOne} />))}
             </div>
         );
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6c944c6b2..3cf86b6f9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -628,6 +628,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         !existingAnalyze && cm.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" });
         cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle!
         cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
+        cm.addItem({ description: "Move To Overlay", icon: "laptop-code", event: () => ((o: Doc) => o && Doc.AddDocToList(o, "data", this.props.Document))(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc) });
         cm.addItem({
             description: "Download document", icon: "download", event: () => {
                 const a = document.createElement("a");
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index f7ce24967..83e45d3ce 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -73,6 +73,12 @@ export class CurrentUserUtils {
             sidebar.boxShadow = "1 1 3";
             doc.sidebar = sidebar;
         }
+        if (doc.overlays === undefined) {
+            const overlays = Docs.Create.FreeformDocument([], { title: "Overlays" });
+            overlays.excludeFromLibrary = true;
+            Doc.GetProto(overlays).backgroundColor = "#aca3a6";
+            doc.overlays = overlays;
+        }
         StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library");
         doc.width = 100;
         doc.preventTreeViewOpen = true;
-- 
cgit v1.2.3-70-g09d2


From ed7f9a9cd3255f7b774268cfda35685ddacfe2e9 Mon Sep 17 00:00:00 2001
From: monikahedman <monika_hedman@brown.edu>
Date: Fri, 23 Aug 2019 16:38:27 -0400
Subject: basic menu and setting docs working

---
 src/client/documents/Documents.ts           |   1 +
 src/client/views/MainView.tsx               |   6 +-
 src/client/views/linking/LinkFollowBox.scss |  77 +++++++++-
 src/client/views/linking/LinkFollowBox.tsx  | 213 +++++++++++++++++++++++-----
 src/client/views/linking/LinkMenuItem.tsx   |   5 +-
 5 files changed, 260 insertions(+), 42 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b7c8f4c12..e903d1e06 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -54,6 +54,7 @@ var path = require('path');
 export interface DocumentOptions {
     x?: number;
     y?: number;
+    z?: number;
     type?: string;
     width?: number;
     height?: number;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 79c44ae72..82e3c706a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
 import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv } from '@fortawesome/free-solid-svg-icons';
+import { faLink, faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -443,7 +443,7 @@ export class MainView extends React.Component {
         let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
 
         let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
-        let addLinkFollowBox = action(() => Docs.Create.LinkFollowBoxDocument({ width: 200, height: 500, title: "Link Follow Document" }));
+        let addLinkFollowBox = action(() => Docs.Create.LinkFollowBoxDocument({ width: 500, height: 350, title: "Link Follower" }));
         let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" }));
         let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" }));
         let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" }));
@@ -459,7 +459,7 @@ export class MainView extends React.Component {
             [React.createRef<HTMLDivElement>(), "globe-asia", "Add Website", addWebNode],
             [React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
             [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode],
-            [React.createRef<HTMLDivElement>(), "caret-up", "Open Link Follow Box", addLinkFollowBox],
+            [React.createRef<HTMLDivElement>(), "link", "Open Link Follow Box", addLinkFollowBox],
             [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode
             //[React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
         ];
diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss
index 522191792..aedbfdea4 100644
--- a/src/client/views/linking/LinkFollowBox.scss
+++ b/src/client/views/linking/LinkFollowBox.scss
@@ -2,5 +2,80 @@
 
 .linkFollowBox-main {
     position: absolute;
-    background: red;
+    background: whitesmoke;
+    color: grey;
+    border-radius: 15px;
+    box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
+    border: solid #BBBBBBBB 5px;
+    pointer-events: all;
+    // overflow: hidden;
+
+    .linkFollowBox-header {
+        height: 30px;
+        text-align: center;
+        text-transform: uppercase;
+        line-height: 30px;
+        letter-spacing: 2px;
+        font-size: 16px;
+    }
+
+    .linkFollowBox-footer {
+        height: 50px;
+        text-align: center;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+
+        button {
+            background-color: $darker-alt-accent;
+            // height: 30px;
+            width: 30%;
+            // font-size: 18px;
+        }
+    }
+
+    .linkFollowBox-content {
+        display: grid;
+        grid-template-columns: 1fr 1fr 1fr;
+        grid-column-gap: 5px;
+        margin-left: 5px;
+        margin-right: 5px;
+
+        .linkFollowBox-item {
+            background-color: $light-color;
+            width: 100%;
+            height: 100%;
+
+            .linkFollowBox-itemContent {
+                padding: 5px;
+                font-size: 12px;
+                // line-height: 40px;
+                overflow: scroll;
+
+
+                input[type=radio] {
+                    border: 0px;
+                    // width: 100%;
+                    // height: 20px;
+                    // width: 20px;
+                    margin-right: 5px;
+                }
+            }
+
+            .title {
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                text-transform: uppercase;
+                color: $light-color;
+                background-color: $lighter-alt-accent;
+                width: 100%;
+                height: 30px;
+                border-bottom: solid $darker-alt-accent 5px;
+                font-size: 12px;
+                text-align: center;
+            }
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 247b67776..8cd26bdec 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -1,10 +1,10 @@
-import { observable, computed, action, trace } from "mobx";
+import { observable, computed, action, trace, ObservableMap } from "mobx";
 import React = require("react");
 import { observer } from "mobx-react";
 import { FieldViewProps, FieldView } from "../nodes/FieldView";
 import { Doc } from "../../../new_fields/Doc";
 import { undoBatch } from "../../util/UndoManager";
-import { NumCast, FieldValue, Cast } from "../../../new_fields/Types";
+import { NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types";
 import { CollectionViewType } from "../collections/CollectionBaseView";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { SelectionManager } from "../../util/SelectionManager";
@@ -12,19 +12,41 @@ import { DocumentManager } from "../../util/DocumentManager";
 import { DocumentView } from "../nodes/DocumentView";
 import "./LinkFollowBox.scss";
 
-export type LinkParamOptions = {
-    container: Doc;
-    context: Doc;
-    sourceDoc: Doc;
-    shoudldZoom: boolean;
-    linkDoc: Doc;
-};
+enum FollowModes {
+    OPENTAB = "Open in Tab",
+    OPENRIGHT = "Open in Right Split",
+    OPENFULL = "Open Full Screen",
+    PAN = "Pan to Document",
+    INPLACE = "Open In Place"
+}
+
+enum FollowOptions {
+    ZOOM = "zoom",
+    NOZOOM = "no zoom",
+}
 
 @observer
 export class LinkFollowBox extends React.Component<FieldViewProps> {
 
     public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); }
     public static Instance: LinkFollowBox;
+    @observable static linkDoc: Doc | undefined = undefined;
+    @observable static destinationDoc: Doc | undefined = undefined;
+    @observable static sourceDoc: Doc | undefined = undefined;
+    @observable selectedMode: string = "";
+    @observable selectedOption: string = "";
+
+    constructor(props: FieldViewProps) {
+        super(props);
+        LinkFollowBox.Instance = this;
+    }
+
+    @action
+    setLinkDocs = (linkDoc: Doc, source: Doc, dest: Doc) => {
+        LinkFollowBox.linkDoc = linkDoc;
+        LinkFollowBox.sourceDoc = source;
+        LinkFollowBox.destinationDoc = dest;
+    }
 
     unhighlight = () => {
         Doc.UnhighlightAll();
@@ -40,11 +62,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
         }, 10000);
     }
 
-    // DONE
     @undoBatch
     openFullScreen = (destinationDoc: Doc) => {
-        let view: DocumentView | null = DocumentManager.Instance.getDocumentView(destinationDoc)
+        let view: DocumentView | null = DocumentManager.Instance.getDocumentView(destinationDoc);
         view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
+        SelectionManager.DeselectAll();
     }
 
     // should container be a doc or documentview or what? This one needs work and is more long term
@@ -54,8 +76,6 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     }
 
     // NOT TESTED
-    // col = collection the doc is in
-    // target = the document to center on
     @undoBatch
     openLinkColRight = (destinationDoc: Doc, options: { context: Doc }) => {
         options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
@@ -66,26 +86,22 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
             options.context.panY = newPanY;
         }
         CollectionDockingView.Instance.AddRightSplit(options.context, undefined);
+
+        this.highlightDoc(destinationDoc);
+        SelectionManager.DeselectAll();
     }
 
-    // DONE
-    // this opens the linked doc in a right split, NOT in its collection
     @undoBatch
     openLinkRight = (destinationDoc: Doc) => {
-        this.highlightDoc(destinationDoc);
         let alias = Doc.MakeAlias(destinationDoc);
         CollectionDockingView.Instance.AddRightSplit(alias, undefined);
+        this.highlightDoc(destinationDoc);
         SelectionManager.DeselectAll();
+
     }
 
-    // DONE
-    // this is the standard "follow link" (jump to document)
-    // taken from follow link
     @undoBatch
     jumpToLink = async (destinationDoc: Doc, options: { shouldZoom: boolean, linkDoc: Doc }) => {
-        //there is an issue right now so this will be false automatically
-        options.shouldZoom = false;
-        this.highlightDoc(destinationDoc);
         let jumpToDoc = destinationDoc;
         let pdfDoc = FieldValue(Cast(destinationDoc, Doc));
         if (pdfDoc) {
@@ -110,26 +126,23 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
         else {
             DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, dockingFunc);
         }
+
+        this.highlightDoc(destinationDoc);
+        SelectionManager.DeselectAll();
     }
 
-    // DONE
-    // opens link in new tab (not in a collection)
-    // this opens it full screen in new tab
     @undoBatch
     openLinkTab = (destinationDoc: Doc) => {
-        this.highlightDoc(destinationDoc);
         let fullScreenAlias = Doc.MakeAlias(destinationDoc);
         this.props.addDocTab(fullScreenAlias, undefined, "inTab");
+
+        this.highlightDoc(destinationDoc);
         SelectionManager.DeselectAll();
     }
 
     // NOT TESTED
-    // opens link in new tab in collection
-    // col = collection the doc is in
-    // target = the document to center on
     @undoBatch
     openLinkColTab = (destinationDoc: Doc, options: { context: Doc }) => {
-        this.highlightDoc(destinationDoc);
         options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
         if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
             const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2;
@@ -137,16 +150,14 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
             options.context.panX = newPanX;
             options.context.panY = newPanY;
         }
-        // CollectionDockingView.Instance.AddRightSplit(col, undefined);
         this.props.addDocTab(options.context, undefined, "inTab");
+
+        this.highlightDoc(destinationDoc);
         SelectionManager.DeselectAll();
     }
 
-    // DONE
-    // this will open a link next to the source doc
     @undoBatch
-    openLinkInPlace = (destinationDoc: Doc, options: { sourceDoc: Doc }) => {
-        this.highlightDoc(destinationDoc);
+    openLinkInPlace = (destinationDoc: Doc, options: { sourceDoc: Doc, linkDoc: Doc }) => {
 
         let alias = Doc.MakeAlias(destinationDoc);
         let y = NumCast(options.sourceDoc.y);
@@ -165,16 +176,146 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
                 dv.props.addDocument && dv.props.addDocument(alias, false);
             }
         });
+
+        this.jumpToLink(destinationDoc, { shouldZoom: false, linkDoc: options.linkDoc });
+
+        this.highlightDoc(destinationDoc);
+        SelectionManager.DeselectAll();
     }
 
     //set this to be the default link behavior, can be any of the above
     private defaultLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.openLinkInPlace;
-    private currentLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.defaultLinkBehavior;
+    // private currentLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.defaultLinkBehavior;
+
+    @computed
+    get LinkFollowTitle(): string {
+        if (LinkFollowBox.linkDoc) {
+            return StrCast(LinkFollowBox.linkDoc.title);
+        }
+        return "No Link Selected";
+    }
+
+    @action
+    currentLinkBehavior = () => {
+        if (this.selectedMode === FollowModes.INPLACE) {
+
+        }
+        else if (this.selectedMode === FollowModes.OPENFULL) {
+
+        }
+        else if (this.selectedMode === FollowModes.OPENRIGHT) {
+
+        }
+        else if (this.selectedMode === FollowModes.OPENTAB) {
+
+        }
+        else if (this.selectedMode === FollowModes.INPLACE) {
+
+        }
+        else if (this.selectedMode === FollowModes.PAN) {
+
+        }
+        else return;
+    }
+
+    @action
+    handleModeChange = (e: React.ChangeEvent) => {
+        let target = e.target as HTMLInputElement;
+        this.selectedMode = target.value;
+    }
+
+    @action
+    handleOptionChange = (e: React.ChangeEvent) => {
+        let target = e.target as HTMLInputElement;
+        this.selectedOption = target.value;
+    }
+
+    @computed
+    get availableModes() {
+        return (
+            <div>
+                <label><input
+                    type="radio"
+                    name="mode"
+                    value={FollowModes.OPENRIGHT}
+                    checked={this.selectedMode === FollowModes.OPENRIGHT}
+                    onChange={this.handleModeChange} />
+                    {FollowModes.OPENRIGHT}
+                </label><br />
+                <label><input
+                    type="radio"
+                    name="mode"
+                    value={FollowModes.OPENTAB}
+                    checked={this.selectedMode === FollowModes.OPENTAB}
+                    onChange={this.handleModeChange} />
+                    {FollowModes.OPENTAB}
+                </label><br />
+                <label><input
+                    type="radio"
+                    name="mode"
+                    value={FollowModes.OPENFULL}
+                    checked={this.selectedMode === FollowModes.OPENFULL}
+                    onChange={this.handleModeChange} />
+                    {FollowModes.OPENFULL}
+                </label><br />
+                <label><input
+                    type="radio"
+                    name="mode"
+                    value={FollowModes.PAN}
+                    checked={this.selectedMode === FollowModes.PAN}
+                    onChange={this.handleModeChange} />
+                    {FollowModes.PAN}
+                </label><br />
+                <label><input
+                    type="radio"
+                    name="mode"
+                    value={FollowModes.INPLACE}
+                    checked={this.selectedMode === FollowModes.INPLACE}
+                    onChange={this.handleModeChange} />
+                    {FollowModes.INPLACE}
+                </label><br />
+            </div>
+        );
+    }
+
+    @computed
+    get contexts() {
+        return (
+            <div>
+
+            </div>
+        );
+    }
 
     render() {
         return (
             <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document.height), width: NumCast(this.props.Document.width) }}>
+                <div className="linkFollowBox-header">{this.LinkFollowTitle}</div>
+                <div className="linkFollowBox-content" style={{ height: NumCast(this.props.Document.height) - 90 }}>
+                    <div className="linkFollowBox-item">
+                        <div className="linkFollowBox-item title">Mode</div>
+                        <div className="linkFollowBox-itemContent">{this.availableModes}</div>
+                    </div>
+                    <div className="linkFollowBox-item">
+                        <div className="linkFollowBox-item title">Context</div>
+                        <div className="linkFollowBox-itemContent">
+
+                        </div>
+                    </div>
+                    <div className="linkFollowBox-item">
+                        <div className="linkFollowBox-item title">Options</div>
+                        <div className="linkFollowBox-itemContent">
 
+                        </div>
+                    </div>
+                </div>
+                <div className="linkFollowBox-footer">
+                    <button
+                        onClick={this.currentLinkBehavior}
+                        disabled={(LinkFollowBox.linkDoc) ? false : true}>
+                        Follow Link
+                    </button>
+                </div>
             </div>
         );
     }
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 60f27c664..406429ebf 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -16,6 +16,7 @@ import { SelectionManager } from '../../util/SelectionManager';
 import { CollectionViewType } from '../collections/CollectionBaseView';
 import { DocumentView } from '../nodes/DocumentView';
 import { SearchUtil } from '../../util/SearchUtil';
+import { LinkFollowBox } from './LinkFollowBox';
 library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
 
 
@@ -174,7 +175,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     }
 
     //set this to be the default link behavior, can be any of the above
-    private defaultLinkBehavior: any = this.openLinkTab;
+    // private defaultLinkBehavior: any = LinkFollowBox.computeLinkDocs(this.props.linkDoc);
 
     onEdit = (e: React.PointerEvent): void => {
         e.stopPropagation();
@@ -242,7 +243,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                             {/* Original */}
                             {/* <div title="Follow link" className="button" onPointerDown={this.onFollowLink}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div> */}
                             {/* New */}
-                            <div title="Follow link" className="button" onPointerDown={this.defaultLinkBehavior}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div>
+                            <div title="Follow link" className="button" onClick={() => LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc)}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div>
                         </div>
                     </div>
                     {this._showMore ? this.renderMetadata() : <></>}
-- 
cgit v1.2.3-70-g09d2


From 045d889f138861c5b22ec671e24aa553eaa4f441 Mon Sep 17 00:00:00 2001
From: monikahedman <monika_hedman@brown.edu>
Date: Mon, 26 Aug 2019 18:10:14 -0400
Subject: ALMOST EVERYTHING DONE

---
 src/client/views/MainView.tsx               |   3 +-
 src/client/views/linking/LinkFollowBox.scss |   8 +-
 src/client/views/linking/LinkFollowBox.tsx  | 255 +++++++++++++++++-----------
 3 files changed, 160 insertions(+), 106 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7520dff8c..aae67ffb2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -443,7 +443,7 @@ export class MainView extends React.Component {
         let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
 
         let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
-        let addLinkFollowBox = action(() => Docs.Create.LinkFollowBoxDocument({ width: 500, height: 350, title: "Link Follower" }));
+        let addLinkFollowBox = action(() => Docs.Create.LinkFollowBoxDocument({ width: 500, height: 370, title: "Link Follower" }));
         let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" }));
         let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" }));
         let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" }));
@@ -576,7 +576,6 @@ export class MainView extends React.Component {
                 <PDFMenu />
                 <MainOverlayTextBox firstinstance={true} />
                 <OverlayView />
-                {/* <LinkFollowBox /> */}
             </div >
         );
     }
diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss
index aedbfdea4..7fad9ae93 100644
--- a/src/client/views/linking/LinkFollowBox.scss
+++ b/src/client/views/linking/LinkFollowBox.scss
@@ -11,14 +11,18 @@
     // overflow: hidden;
 
     .linkFollowBox-header {
-        height: 30px;
+        height: 50px;
         text-align: center;
         text-transform: uppercase;
-        line-height: 30px;
+        // line-height: 30px;
         letter-spacing: 2px;
         font-size: 16px;
     }
 
+    .direction-indicator {
+        font-size: 12px;
+    }
+
     .linkFollowBox-footer {
         height: 50px;
         text-align: center;
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index bf742cb81..213022bc8 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -14,6 +14,8 @@ import "./LinkFollowBox.scss";
 import { SearchUtil } from "../../util/SearchUtil";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { listSpec } from "../../../new_fields/Schema";
+import { DocServer } from "../../DocServer";
+import { RefField } from "../../../new_fields/RefField";
 
 enum FollowModes {
     OPENTAB = "Open in Tab",
@@ -37,13 +39,15 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     @observable static destinationDoc: Doc | undefined = undefined;
     @observable static sourceDoc: Doc | undefined = undefined;
     @observable selectedMode: string = "";
-    @observable selectedContext: any = undefined;
+    @observable selectedContext: Doc | undefined = undefined;
+    @observable selectedContextAliases: Doc[] | undefined = undefined;
     @observable selectedOption: string = "";
     @observable selectedContextString: string = "";
     @observable sourceView: DocumentView | undefined = undefined;
     @observable canPan: boolean = false;
     @observable shouldUseOnlyParentContext = false;
-    _panDisposer?: IReactionDisposer;
+    _contextDisposer?: IReactionDisposer;
+    collectionTypes: string[];
 
     @observable private _docs: { col: Doc, target: Doc }[] = [];
     @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
@@ -51,33 +55,49 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     constructor(props: FieldViewProps) {
         super(props);
         LinkFollowBox.Instance = this;
+
+        this.collectionTypes = ["Invalid", "Freeform", "Schema", "Docking", "Tree", "Stacking", "Masonry"];
     }
 
     componentDidMount = () => {
         this.resetVars();
 
-        this._panDisposer = reaction(
-            () => LinkFollowBox.destinationDoc,
+        this._contextDisposer = reaction(
+            () => this.selectedContextString,
             async () => {
-                if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionView) {
-                    let colDoc = this.sourceView.props.ContainingCollectionView.props.Document;
-                    runInAction(() => { this.canPan = false; });
-                    if (colDoc.viewType && colDoc.viewType === 1) {
-                        //this means its in a freeform collection
-                        let docs = Cast(colDoc.data, listSpec(Doc), []);
-                        let aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(LinkFollowBox.destinationDoc));
-
-                        aliases.forEach(alias => {
-                            if (docs.filter(doc => doc === alias).length > 0) { runInAction(() => { this.canPan = true; }); }
-                        });
+                let ref = await DocServer.GetRefField(this.selectedContextString);
+                runInAction(() => {
+                    if (ref instanceof Doc) {
+                        this.selectedContext = ref;
                     }
+                });
+                if (this.selectedContext instanceof Doc) {
+                    let aliases = await SearchUtil.GetViewsOfDocument(this.selectedContext);
+                    runInAction(() => { this.selectedContextAliases = aliases; });
                 }
             }
         );
     }
 
     componentWillUnmount = () => {
-        this._panDisposer && this._panDisposer();
+        this._contextDisposer && this._contextDisposer();
+    }
+
+    async resetPan() {
+        if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionView) {
+            let colDoc = this.sourceView.props.ContainingCollectionView.props.Document;
+            runInAction(() => { this.canPan = false; });
+            if (colDoc.viewType && colDoc.viewType === CollectionViewType.Freeform) {
+                let docs = Cast(colDoc.data, listSpec(Doc), []);
+                let aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(LinkFollowBox.destinationDoc));
+
+                aliases.forEach(alias => {
+                    if (docs.filter(doc => doc === alias).length > 0) {
+                        runInAction(() => { this.canPan = true; });
+                    }
+                });
+            }
+        }
     }
 
     @action
@@ -124,6 +144,8 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
                 this.sourceView = dv;
             }
         });
+
+        this.resetPan();
     }
 
     unhighlight = () => {
@@ -151,15 +173,30 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
         }
     }
 
+    @undoBatch
+    openColFullScreen = (options: { context: Doc }) => {
+        if (LinkFollowBox.destinationDoc) {
+            if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2;
+                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2;
+                options.context.panX = newPanX;
+                options.context.panY = newPanY;
+            }
+            let view = DocumentManager.Instance.getDocumentView(options.context);
+            view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
+            this.highlightDoc();
+            SelectionManager.DeselectAll();
+        }
+    }
+
     // should container be a doc or documentview or what? This one needs work and is more long term
     @undoBatch
     openInContainer = (options: { container: Doc }) => {
 
     }
 
-    // NOT TESTED
     @undoBatch
-    openLinkColRight = (options: { context: Doc }) => {
+    openLinkColRight = (options: { context: Doc, shouldZoom: boolean }) => {
         if (LinkFollowBox.destinationDoc) {
             options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
             if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
@@ -170,6 +207,8 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
             }
             CollectionDockingView.Instance.AddRightSplit(options.context, undefined);
 
+            if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom });
+
             this.highlightDoc();
             SelectionManager.DeselectAll();
         }
@@ -231,9 +270,8 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
         }
     }
 
-    // NOT TESTED
     @undoBatch
-    openLinkColTab = (options: { context: Doc }) => {
+    openLinkColTab = (options: { context: Doc, shouldZoom: boolean }) => {
         if (LinkFollowBox.destinationDoc) {
             options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
             if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
@@ -243,6 +281,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
                 options.context.panY = newPanY;
             }
             this.props.addDocTab(options.context, undefined, "inTab");
+            if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom });
 
             this.highlightDoc();
             SelectionManager.DeselectAll();
@@ -254,7 +293,6 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
 
         if (LinkFollowBox.destinationDoc && LinkFollowBox.sourceDoc) {
             let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
-            console.log(alias)
             let y = NumCast(LinkFollowBox.sourceDoc.y);
             let x = NumCast(LinkFollowBox.sourceDoc.x);
 
@@ -279,33 +317,35 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
 
     //set this to be the default link behavior, can be any of the above
     private defaultLinkBehavior: (options?: any) => void = this.openLinkInPlace;
-    // private currentLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.defaultLinkBehavior;
 
     @action
     currentLinkBehavior = () => {
-        let shouldZoom: boolean | undefined = this.selectedOption === "" ? undefined :
-            this.selectedOption === FollowOptions.NOZOOM ? false : true;
-
-        if (this.selectedMode === FollowModes.INPLACE) {
-            if (shouldZoom !== undefined) this.openLinkInPlace({ shouldZoom: shouldZoom });
-        }
-        else if (this.selectedMode === FollowModes.OPENFULL) {
-            this.openFullScreen();
-        }
-        else if (this.selectedMode === FollowModes.OPENRIGHT) {
-            if (this.selectedContextString === "self") this.openLinkRight();
-            else this.selectedContext && this.openLinkColRight({ context: this.selectedContext });
-        }
-        else if (this.selectedMode === FollowModes.OPENTAB) {
-
-        }
-        else if (this.selectedMode === FollowModes.INPLACE) {
-
-        }
-        else if (this.selectedMode === FollowModes.PAN) {
+        // this.resetPan();
+        if (LinkFollowBox.destinationDoc) {
+            if (this.selectedOption === "") this.selectedOption = FollowOptions.NOZOOM;
+            let shouldZoom: boolean = this.selectedOption === FollowOptions.NOZOOM ? false : true;
+            let notOpenInContext: boolean = this.selectedContextString === "self" || this.selectedContextString === LinkFollowBox.destinationDoc[Id];
 
+            if (this.selectedMode === FollowModes.INPLACE) {
+                if (shouldZoom !== undefined) this.openLinkInPlace({ shouldZoom: shouldZoom });
+            }
+            else if (this.selectedMode === FollowModes.OPENFULL) {
+                if (notOpenInContext) this.openFullScreen();
+                else this.selectedContext && this.openColFullScreen({ context: this.selectedContext });
+            }
+            else if (this.selectedMode === FollowModes.OPENRIGHT) {
+                if (notOpenInContext) this.openLinkRight();
+                else this.selectedContext && this.openLinkColRight({ context: this.selectedContext, shouldZoom: shouldZoom });
+            }
+            else if (this.selectedMode === FollowModes.OPENTAB) {
+                if (notOpenInContext) this.openLinkTab();
+                else this.selectedContext && this.openLinkColTab({ context: this.selectedContext, shouldZoom: shouldZoom })
+            }
+            else if (this.selectedMode === FollowModes.PAN) {
+                this.jumpToLink({ shouldZoom: shouldZoom });
+            }
+            else return;
         }
-        else return;
     }
 
     @action
@@ -317,11 +357,6 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
 
         this.shouldUseOnlyParentContext = (this.selectedMode === FollowModes.INPLACE || this.selectedMode === FollowModes.PAN);
 
-        if (this.selectedMode === FollowModes.OPENFULL) {
-            this.selectedContextString = "self";
-            this.selectedContext = LinkFollowBox.destinationDoc;
-        }
-
         if (this.shouldUseOnlyParentContext) {
             if (this.sourceView && this.sourceView.props.ContainingCollectionView) {
                 this.selectedContext = this.sourceView.props.ContainingCollectionView.props.Document;
@@ -339,10 +374,8 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     @action
     handleContextChange = (e: React.ChangeEvent) => {
         let target = e.target as HTMLInputElement;
-        let toArr = target.value;
-        let arr = toArr.split(",");
-        this.selectedContextString = arr[0];
-        console.log(this.selectedContextString)
+        this.selectedContextString = target.value;
+        // selectedContext is updated in reaction
         this.selectedOption = "";
     }
 
@@ -351,10 +384,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
         if (this.sourceView && this.sourceView.props.ContainingCollectionView) {
             let colView = this.sourceView.props.ContainingCollectionView;
             let colDoc = colView.props.Document;
-            if (colDoc.viewType && colDoc.viewType === 1) {
-                //this means its in a freeform collection
-                return true;
-            }
+            if (colDoc.viewType && colDoc.viewType === CollectionViewType.Freeform) return true;
         }
         return false;
     }
@@ -436,75 +466,96 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
                 <label><input
                     type="radio" disabled={true}
                     name="context"
-                    value={["col", this.parentID]}
+                    value={this.parentID}
                     checked={true} />
                     {this.parentName} (Parent Collection)
                 </label>
                 :
                 <div>
                     <label><input
-                        type="radio" disabled={LinkFollowBox.linkDoc && !(this.selectedMode === FollowModes.OPENFULL) ? false : true}
+                        type="radio" disabled={LinkFollowBox.linkDoc ? false : true}
                         name="context"
-                        value={LinkFollowBox.destinationDoc ? ["self", StrCast(LinkFollowBox.destinationDoc[Id])] : "self"}
-                        checked={this.selectedContextString === "self"}
+                        value={LinkFollowBox.destinationDoc ? StrCast(LinkFollowBox.destinationDoc[Id]) : "self"}
+                        checked={LinkFollowBox.destinationDoc ? this.selectedContextString === StrCast(LinkFollowBox.destinationDoc[Id]) : this.selectedContextString === "self"}
                         onChange={this.handleContextChange} />
                         Open Self
                 </label><br />
-                    {this.selectedMode === FollowModes.OPENFULL ? null :
-                        [...this._docs, ...this._otherDocs].map(doc => {
-                            if (doc && doc.target) {
-                                return <div key={doc.col[Id] + doc.target[Id]}><label key={doc.col[Id] + doc.target[Id]}>
-                                    <input
-                                        type="radio" disabled={LinkFollowBox.linkDoc ? false : true}
-                                        name="context"
-                                        value={[StrCast(doc.col.title), StrCast(doc.col[Id])]}
-                                        checked={this.selectedContextString === StrCast(doc.col.title)}
-                                        onChange={this.handleContextChange} />
-                                    {doc.col.title}
-                                </label><br /></div>;
-                            }
-                        })}
+                    {[...this._docs, ...this._otherDocs].map(doc => {
+                        if (doc && doc.target && doc.col.title !== "Recently Closed") {
+                            return <div key={doc.col[Id] + doc.target[Id]}><label key={doc.col[Id] + doc.target[Id]}>
+                                <input
+                                    type="radio" disabled={LinkFollowBox.linkDoc ? false : true}
+                                    name="context"
+                                    value={StrCast(doc.col[Id])}
+                                    checked={this.selectedContextString === StrCast(doc.col[Id])}
+                                    onChange={this.handleContextChange} />
+                                {doc.col.title}
+                            </label><br /></div>;
+                        }
+                    })}
                 </div>
         );
     }
 
+    @computed
+    get shouldShowZoom(): boolean {
+        if (this.selectedMode === FollowModes.OPENFULL) return false;
+        if (this.shouldUseOnlyParentContext) return true;
+        if (LinkFollowBox.destinationDoc ? this.selectedContextString === LinkFollowBox.destinationDoc[Id] : "self") return false;
+
+        let contextMatch: boolean = false;
+        if (this.selectedContextAliases) {
+            this.selectedContextAliases.forEach(alias => {
+                if (alias.viewType === CollectionViewType.Freeform) contextMatch = true;
+            });
+        }
+        if (contextMatch) return true;
+
+        return false;
+    }
+
     @computed
     get availableOptions() {
-        let shouldShowZoom = (this.shouldUseOnlyParentContext || this.selectedContextString !== "self");
-        return (
-            shouldShowZoom ?
-                <div>
-                    <label><input
-                        type="radio"
-                        name="option"
-                        value={FollowOptions.ZOOM}
-                        checked={this.selectedOption === FollowOptions.ZOOM}
-                        onChange={this.handleOptionChange}
-                        disabled={false} />
-                        {FollowOptions.ZOOM}
-                    </label><br />
-                    <label><input
-                        type="radio"
-                        name="option"
-                        value={FollowOptions.NOZOOM}
-                        checked={this.selectedOption === FollowOptions.NOZOOM}
-                        onChange={this.handleOptionChange}
-                        disabled={false} />
-                        {FollowOptions.NOZOOM}
-                    </label><br />
-                </div>
-                :
-                <div>No Available Options</div>
-        );
+        if (LinkFollowBox.destinationDoc) {
+            return (
+                this.shouldShowZoom ?
+                    <div>
+                        <label><input
+                            type="radio"
+                            name="option"
+                            value={FollowOptions.ZOOM}
+                            checked={this.selectedOption === FollowOptions.ZOOM}
+                            onChange={this.handleOptionChange}
+                            disabled={false} />
+                            {FollowOptions.ZOOM}
+                        </label><br />
+                        <label><input
+                            type="radio"
+                            name="option"
+                            value={FollowOptions.NOZOOM}
+                            checked={this.selectedOption === FollowOptions.NOZOOM}
+                            onChange={this.handleOptionChange}
+                            disabled={false} />
+                            {FollowOptions.NOZOOM}
+                        </label><br />
+                    </div>
+                    :
+                    <div>No Available Options</div>
+            );
+        }
+        return null;
     }
 
     render() {
         return (
             <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document.height), width: NumCast(this.props.Document.width) }}>
                 <div className="linkFollowBox-header">
-                    {LinkFollowBox.linkDoc ? "Link Title :" + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"}
+                    {LinkFollowBox.linkDoc ? "Link Title: " + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"}
+                    <div className="linkFollowBox-header direction-indicator">{LinkFollowBox.linkDoc ?
+                        LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc ? "Source: " + StrCast(LinkFollowBox.sourceDoc.title) + ", Destination: " + StrCast(LinkFollowBox.destinationDoc.title)
+                            : "" : ""}</div>
                 </div>
-                <div className="linkFollowBox-content" style={{ height: NumCast(this.props.Document.height) - 90 }}>
+                <div className="linkFollowBox-content" style={{ height: NumCast(this.props.Document.height) - 110 }}>
                     <div className="linkFollowBox-item">
                         <div className="linkFollowBox-item title">Mode</div>
                         <div className="linkFollowBox-itemContent">
-- 
cgit v1.2.3-70-g09d2


From b4f79e48d5d30312367e268c2c505f65c5cacdb4 Mon Sep 17 00:00:00 2001
From: monikahedman <monika_hedman@brown.edu>
Date: Tue, 27 Aug 2019 13:19:20 -0400
Subject: things are progressing

---
 src/client/views/MainView.tsx               | 30 ++++++++++++++++++-----------
 src/client/views/OverlayView.tsx            |  2 +-
 src/client/views/linking/LinkFollowBox.scss |  1 +
 src/client/views/linking/LinkFollowBox.tsx  | 12 +++++++++++-
 4 files changed, 32 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index aae67ffb2..b49274e7e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -12,7 +12,7 @@ import { List } from '../../new_fields/List';
 import { Id } from '../../new_fields/FieldSymbols';
 import { InkTool } from '../../new_fields/InkField';
 import { listSpec } from '../../new_fields/Schema';
-import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types';
+import { BoolCast, Cast, FieldValue, StrCast, NumCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
 import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils';
@@ -41,6 +41,7 @@ import { FilterBox } from './search/FilterBox';
 import PresModeMenu from './presentationview/PresentationModeMenu';
 import { PresBox } from './nodes/PresBox';
 import { LinkFollowBox } from './linking/LinkFollowBox';
+import { DocumentManager } from '../util/DocumentManager';
 
 @observer
 export class MainView extends React.Component {
@@ -436,14 +437,27 @@ export class MainView extends React.Component {
         }
     }
 
+    toggleLinkFollowBox = () => {
+        if (LinkFollowBox.Instance) {
+            console.log("is instance!")
+            // if (LinkFollowBox.Instance.props && LinkFollowBox.Instance.props.removeDocument) LinkFollowBox.Instance.props.removeDocument(LinkFollowBox.Instance.props.Document);
+        }
+        else {
+            let overlayView = document.getElementById("overlayView");
+            let overlayWidth = 0;
+            if (overlayView) overlayWidth = overlayView.offsetWidth;
+            let x: number = overlayWidth - 500;
+            let doc = Docs.Create.LinkFollowBoxDocument({ x: x, y: 20, width: 500, height: 370, title: "Link Follower" });
+            Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", doc);
+        }
+    }
+
     @observable private _colorPickerDisplay = false;
     /* 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 addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
-        let addLinkFollowBox = action(() => Docs.Create.LinkFollowBoxDocument({ width: 500, height: 370, title: "Link Follower" }));
         let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" }));
         let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" }));
         let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" }));
@@ -459,7 +473,7 @@ export class MainView extends React.Component {
             [React.createRef<HTMLDivElement>(), "globe-asia", "Add Website", addWebNode],
             [React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
             [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode],
-            [React.createRef<HTMLDivElement>(), "link", "Open Link Follow Box", addLinkFollowBox],
+            // [React.createRef<HTMLDivElement>(), "link", "Open Link Follow Box", addLinkFollowBox],
             [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode
             //[React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
         ];
@@ -481,6 +495,7 @@ export class MainView extends React.Component {
                                 <FontAwesomeIcon icon={btn[1]} size="sm" />
                             </button>
                         </div></li>)}
+                    <li key="linkFollow"><button className="add-button round-button" title="Open Link Follower" onClick={this.toggleLinkFollowBox}><FontAwesomeIcon icon="link" size="sm" /></button></li>
                     <li key="color"><button className="add-button round-button" title="Select Color" style={{ zIndex: 1000 }} onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
                         <div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
                             <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
@@ -503,12 +518,6 @@ export class MainView extends React.Component {
         this._colorPickerDisplay = close ? false : !this._colorPickerDisplay;
     }
 
-    @action
-    toggleLinkFollowBox = () => {
-        console.log("toggling link editor")
-        this._linkFollowBox = !this._linkFollowBox;
-    }
-
     /* @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() {
@@ -517,7 +526,6 @@ export class MainView extends React.Component {
         return [
             this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <FilterBox /> </div> : null,
             <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
-                <button onClick={this.toggleLinkFollowBox}>Open Link Editor</button>
                 <button onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}>Log Out</button></div>
         ];
 
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index a60dc591c..8d80ad1f1 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -140,7 +140,7 @@ export class OverlayView extends React.Component {
 
     render() {
         return (
-            <div className="overlayView">
+            <div className="overlayView" id="overlayView">
                 {this._elements}
                 {CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => (
                     <CollectionFreeFormDocumentView key={d[Id]}
diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss
index 7fad9ae93..a34285727 100644
--- a/src/client/views/linking/LinkFollowBox.scss
+++ b/src/client/views/linking/LinkFollowBox.scss
@@ -8,6 +8,7 @@
     box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
     border: solid #BBBBBBBB 5px;
     pointer-events: all;
+    // transform-origin: top right;
     // overflow: hidden;
 
     .linkFollowBox-header {
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 213022bc8..cf44ae649 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -16,6 +16,7 @@ import { Id } from "../../../new_fields/FieldSymbols";
 import { listSpec } from "../../../new_fields/Schema";
 import { DocServer } from "../../DocServer";
 import { RefField } from "../../../new_fields/RefField";
+import { Docs } from "../../documents/Documents";
 
 enum FollowModes {
     OPENTAB = "Open in Tab",
@@ -59,6 +60,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
         this.collectionTypes = ["Invalid", "Freeform", "Schema", "Docking", "Tree", "Stacking", "Masonry"];
     }
 
+    @computed
+    get getDoc() {
+        return this.props.Document;
+    }
+
     componentDidMount = () => {
         this.resetVars();
 
@@ -322,6 +328,10 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     currentLinkBehavior = () => {
         // this.resetPan();
         if (LinkFollowBox.destinationDoc) {
+            if (this.selectedContextString === "") {
+                this.selectedContextString = "self";
+                this.selectedContext = LinkFollowBox.destinationDoc;
+            }
             if (this.selectedOption === "") this.selectedOption = FollowOptions.NOZOOM;
             let shouldZoom: boolean = this.selectedOption === FollowOptions.NOZOOM ? false : true;
             let notOpenInContext: boolean = this.selectedContextString === "self" || this.selectedContextString === LinkFollowBox.destinationDoc[Id];
@@ -476,7 +486,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
                         type="radio" disabled={LinkFollowBox.linkDoc ? false : true}
                         name="context"
                         value={LinkFollowBox.destinationDoc ? StrCast(LinkFollowBox.destinationDoc[Id]) : "self"}
-                        checked={LinkFollowBox.destinationDoc ? this.selectedContextString === StrCast(LinkFollowBox.destinationDoc[Id]) : this.selectedContextString === "self"}
+                        checked={LinkFollowBox.destinationDoc ? this.selectedContextString === StrCast(LinkFollowBox.destinationDoc[Id]) || this.selectedContextString === "self" : true}
                         onChange={this.handleContextChange} />
                         Open Self
                 </label><br />
-- 
cgit v1.2.3-70-g09d2


From 34e0b1cf34474ec6765e212b6a35defefbfb49c9 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 27 Aug 2019 15:46:03 -0400
Subject: removed selectOnLoad prop.  fixed textboxes from deselecting on
 carriage return.

---
 src/client/views/MainOverlayTextBox.tsx                    |  2 +-
 src/client/views/MainView.tsx                              |  2 --
 src/client/views/OverlayView.tsx                           |  1 -
 src/client/views/collections/CollectionDockingView.tsx     |  1 -
 src/client/views/collections/CollectionSchemaCells.tsx     |  1 -
 src/client/views/collections/CollectionSchemaView.tsx      |  1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx          | 14 +++-----------
 src/client/views/nodes/DocumentView.tsx                    |  4 +---
 src/client/views/nodes/FieldView.tsx                       |  2 --
 src/client/views/nodes/FormattedTextBox.tsx                |  9 ++++++---
 src/client/views/nodes/KeyValuePair.tsx                    |  1 -
 src/client/views/presentationview/PresentationElement.tsx  |  1 -
 src/client/views/search/SearchItem.tsx                     |  1 -
 13 files changed, 11 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 65a291d99..f15b60347 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -156,7 +156,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                                 DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
                                 onClick={undefined}
                                 ChromeHeight={this.ChromeHeight}
-                                isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true}
+                                isSelected={returnTrue} select={emptyFunction} renderDepth={0}
                                 ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne}
                                 ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction}
                                 pinToPres={returnZero} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a02214deb..6b856443b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -326,7 +326,6 @@ export class MainView extends React.Component {
                             PanelHeight={this.getPHeight}
                             renderDepth={0}
                             backgroundColor={returnEmptyString}
-                            selectOnLoad={false}
                             focus={emptyFunction}
                             parentActive={returnTrue}
                             whenActiveChanged={emptyFunction}
@@ -389,7 +388,6 @@ export class MainView extends React.Component {
             PanelWidth={this.flyoutWidthFunc}
             PanelHeight={this.getPHeight}
             renderDepth={0}
-            selectOnLoad={false}
             focus={emptyFunction}
             backgroundColor={returnEmptyString}
             parentActive={returnTrue}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index a60dc591c..538614089 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -153,7 +153,6 @@ export class OverlayView extends React.Component {
                         PanelHeight={returnOne}
                         ScreenToLocalTransform={Transform.Identity}
                         renderDepth={1}
-                        selectOnLoad={false}
                         parentActive={returnTrue}
                         whenActiveChanged={emptyFunction}
                         focus={emptyFunction}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index dc0cbda0b..95f94875c 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -636,7 +636,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             PanelHeight={this.panelHeight}
             ScreenToLocalTransform={this.ScreenToLocalTransform}
             renderDepth={0}
-            selectOnLoad={false}
             parentActive={returnTrue}
             whenActiveChanged={emptyFunction}
             focus={emptyFunction}
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 551b485e7..9c26a08f0 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -153,7 +153,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             isSelected: returnFalse,
             select: emptyFunction,
             renderDepth: this.props.renderDepth + 1,
-            selectOnLoad: false,
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyFunction,
             active: returnFalse,
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 4008dea51..9d83aa6c1 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1006,7 +1006,6 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         parentActive={this.props.active}
                         ScreenToLocalTransform={this.getTransform}
                         renderDepth={this.props.renderDepth + 1}
-                        selectOnLoad={false}
                         ContentScaling={this.contentScaling}
                         PanelWidth={this.PanelWidth}
                         PanelHeight={this.PanelHeight}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f185222ce..62f848a2b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,7 +1,7 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
@@ -38,6 +38,7 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import { DocServer } from "../../../DocServer";
+import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
 
@@ -165,8 +166,6 @@ export namespace PivotView {
             return prev;
         }, elements);
 
-        target.resetSelectOnLoaded();
-
         return docViews;
     };
 
@@ -177,7 +176,6 @@ const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema)
 
 @observer
 export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
-    private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
     private _lastX: number = 0;
     private _lastY: number = 0;
     private get _pwidth() { return this.props.PanelWidth(); }
@@ -241,7 +239,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
     private addLiveTextBox = (newBox: Doc) => {
-        this._selectOnLoaded = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+        FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
         this.addDocument(newBox, false);
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
@@ -642,7 +640,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             onClick: this.props.onClick,
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
             renderDepth: this.props.renderDepth + 1,
-            selectOnLoad: childLayout[Id] === this._selectOnLoaded,
             PanelWidth: childLayout[WidthSym],
             PanelHeight: childLayout[HeightSym],
             ContentScaling: returnOne,
@@ -668,7 +665,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             onClick: this.props.onClick,
             ScreenToLocalTransform: this.getTransform,
             renderDepth: this.props.renderDepth,
-            selectOnLoad: layoutDoc[Id] === this._selectOnLoaded,
             PanelWidth: layoutDoc[WidthSym],
             PanelHeight: layoutDoc[HeightSym],
             ContentScaling: returnOne,
@@ -768,13 +764,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             return prev;
         }, elements);
 
-        this.resetSelectOnLoaded();
-
         return docviews;
     }
 
-    resetSelectOnLoaded = () => setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way ....
-
     @computed.struct
     get views() {
         let source = this.elements;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3cf86b6f9..f7ebfb75a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -92,7 +92,6 @@ export interface DocumentViewProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     focus: (doc: Doc, willZoom: boolean, scale?: number) => void;
-    selectOnLoad: boolean;
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
@@ -717,7 +716,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             isSelected={this.isSelected}
             select={this.select}
             onClick={this.onClickHandler}
-            selectOnLoad={this.props.selectOnLoad}
             layoutKey={"layout"}
             fitToBox={BoolCast(this.props.Document.fitToBox) ? true : this.props.fitToBox}
             DataDoc={this.dataDoc} />);
@@ -808,7 +806,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         }
                         {!showCaption ? (null) :
                             <div style={{ position: "absolute", bottom: 0, transformOrigin: "bottom left", width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
-                                <FormattedTextBox {...this.props} onClick={this.onClickHandler} DataDoc={this.dataDoc} active={returnTrue} isSelected={this.isSelected} focus={emptyFunction} select={this.select} selectOnLoad={this.props.selectOnLoad} fieldExt={""} hideOnLeave={true} fieldKey={showCaption} />
+                                <FormattedTextBox {...this.props} onClick={this.onClickHandler} DataDoc={this.dataDoc} active={returnTrue} isSelected={this.isSelected} focus={emptyFunction} select={this.select} fieldExt={""} hideOnLeave={true} fieldKey={showCaption} />
                             </div>
                         }
                     </div>
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index f0f1b3b73..d9774303b 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -36,7 +36,6 @@ export interface FieldViewProps {
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
     renderDepth: number;
-    selectOnLoad: boolean;
     addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
     pinToPres: (document: Doc) => void;
@@ -108,7 +107,6 @@ export class FieldView extends React.Component<FieldViewProps> {
             //         PanelWidth={returnHundred}
             //         PanelHeight={returnHundred}
             //         renderDepth={0} //TODO Why is renderDepth reset?
-            //         selectOnLoad={false}
             //         focus={emptyFunction}
             //         isSelected={this.props.isSelected}
             //         select={returnFalse}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index acfd2a3b8..c28bf1821 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -622,12 +622,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         }
 
-        if (this.props.selectOnLoad) {
-            if (!this.props.isOverlay) this.props.select(false);
-            else this._editorView!.focus();
+        if (this.props.Document[Id] == FormattedTextBox.SelectOnLoad) {
+            FormattedTextBox.SelectOnLoad = "";
+            this.props.select(false);
         }
+        else if (this.props.isOverlay) this._editorView!.focus();
     }
 
+    public static SelectOnLoad = "";
+
     componentWillUnmount() {
         this._editorView && this._editorView.destroy();
         this._reactionDisposer && this._reactionDisposer();
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 8001b24a7..5afd4d834 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -60,7 +60,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             isSelected: returnFalse,
             select: emptyFunction,
             renderDepth: 1,
-            selectOnLoad: false,
             active: returnFalse,
             whenActiveChanged: emptyFunction,
             ScreenToLocalTransform: Transform.Identity,
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 83413814f..80aa25f48 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -359,7 +359,6 @@ export default class PresentationElement extends React.Component<PresentationEle
                     PanelHeight={() => 90}
                     focus={emptyFunction}
                     backgroundColor={returnEmptyString}
-                    selectOnLoad={false}
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
                     bringToFront={emptyFunction}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 41fc49c2e..672892fdf 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -209,7 +209,6 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     PanelHeight={returnYDimension}
                     focus={emptyFunction}
                     backgroundColor={returnEmptyString}
-                    selectOnLoad={false}
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
                     bringToFront={emptyFunction}
-- 
cgit v1.2.3-70-g09d2


From 9c116867bb73b7d84654d9e56688ebd8a982462e Mon Sep 17 00:00:00 2001
From: monikahedman <monika_hedman@brown.edu>
Date: Tue, 27 Aug 2019 17:45:20 -0400
Subject: about to make big changes

---
 src/client/views/MainView.tsx               |  19 ++-
 src/client/views/linking/LinkFollowBox.scss |  23 +++
 src/client/views/linking/LinkFollowBox.tsx  |  28 +++-
 src/client/views/linking/LinkMenuItem.tsx   | 214 ++++------------------------
 4 files changed, 77 insertions(+), 207 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b49274e7e..d580925d6 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -57,8 +57,6 @@ export class MainView extends React.Component {
 
     public overlayTimeout: NodeJS.Timeout | undefined;
 
-    @observable private _linkFollowBox = false;
-
     public initiateDictationFade = () => {
         let duration = DictationManager.Commands.dictationFadeDuration;
         this.overlayTimeout = setTimeout(() => {
@@ -437,17 +435,16 @@ export class MainView extends React.Component {
         }
     }
 
-    toggleLinkFollowBox = () => {
+    toggleLinkFollowBox = (shouldClose: boolean) => {
         if (LinkFollowBox.Instance) {
-            console.log("is instance!")
-            // if (LinkFollowBox.Instance.props && LinkFollowBox.Instance.props.removeDocument) LinkFollowBox.Instance.props.removeDocument(LinkFollowBox.Instance.props.Document);
+            let dvs = DocumentManager.Instance.getDocumentViews(LinkFollowBox.Instance.props.Document);
+            // if it already exisits, close it
+            if (dvs.length > 0 && shouldClose) LinkFollowBox.Instance.close();
+            // open it if not
+            else Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", LinkFollowBox.Instance.props.Document);
         }
         else {
-            let overlayView = document.getElementById("overlayView");
-            let overlayWidth = 0;
-            if (overlayView) overlayWidth = overlayView.offsetWidth;
-            let x: number = overlayWidth - 500;
-            let doc = Docs.Create.LinkFollowBoxDocument({ x: x, y: 20, width: 500, height: 370, title: "Link Follower" });
+            let doc = Docs.Create.LinkFollowBoxDocument({ x: this.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" });
             Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", doc);
         }
     }
@@ -495,7 +492,7 @@ export class MainView extends React.Component {
                                 <FontAwesomeIcon icon={btn[1]} size="sm" />
                             </button>
                         </div></li>)}
-                    <li key="linkFollow"><button className="add-button round-button" title="Open Link Follower" onClick={this.toggleLinkFollowBox}><FontAwesomeIcon icon="link" size="sm" /></button></li>
+                    <li key="linkFollow"><button className="add-button round-button" title="Open Link Follower" onClick={() => this.toggleLinkFollowBox(true)}><FontAwesomeIcon icon="link" size="sm" /></button></li>
                     <li key="color"><button className="add-button round-button" title="Select Color" style={{ zIndex: 1000 }} onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
                         <div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
                             <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss
index a34285727..e425bde69 100644
--- a/src/client/views/linking/LinkFollowBox.scss
+++ b/src/client/views/linking/LinkFollowBox.scss
@@ -18,12 +18,35 @@
         // line-height: 30px;
         letter-spacing: 2px;
         font-size: 16px;
+        width: 100%;
     }
 
     .direction-indicator {
         font-size: 12px;
     }
 
+    .closeDocument {
+        position: relative;
+        // z-index: inherit;
+        max-width: 30px;
+        top: -20px;
+        // left: 230px;
+        left: 460px;
+        color: $darker-alt-accent // right: 5px;
+    }
+
+    .closeDocument:hover {
+        color: $main-accent;
+    }
+
+    .topHeader {
+        // display: grid;
+        // grid-template-columns: 90% 10%;
+        width: 100%;
+        // flex-direction: row;
+        height: 25px;
+    }
+
     .linkFollowBox-footer {
         height: 50px;
         text-align: center;
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index cf44ae649..588f48017 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -2,7 +2,7 @@ import { observable, computed, action, trace, ObservableMap, runInAction, reacti
 import React = require("react");
 import { observer } from "mobx-react";
 import { FieldViewProps, FieldView } from "../nodes/FieldView";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCastAsync } from "../../../new_fields/Doc";
 import { undoBatch } from "../../util/UndoManager";
 import { NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types";
 import { CollectionViewType } from "../collections/CollectionBaseView";
@@ -17,6 +17,9 @@ import { listSpec } from "../../../new_fields/Schema";
 import { DocServer } from "../../DocServer";
 import { RefField } from "../../../new_fields/RefField";
 import { Docs } from "../../documents/Documents";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faTimes } from '@fortawesome/free-solid-svg-icons';
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 
 enum FollowModes {
     OPENTAB = "Open in Tab",
@@ -48,7 +51,6 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     @observable canPan: boolean = false;
     @observable shouldUseOnlyParentContext = false;
     _contextDisposer?: IReactionDisposer;
-    collectionTypes: string[];
 
     @observable private _docs: { col: Doc, target: Doc }[] = [];
     @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
@@ -56,8 +58,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     constructor(props: FieldViewProps) {
         super(props);
         LinkFollowBox.Instance = this;
-
-        this.collectionTypes = ["Invalid", "Freeform", "Schema", "Docking", "Tree", "Stacking", "Masonry"];
+        this.props.Document.isBackground = true;
     }
 
     @computed
@@ -322,7 +323,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     }
 
     //set this to be the default link behavior, can be any of the above
-    private defaultLinkBehavior: (options?: any) => void = this.openLinkInPlace;
+    public defaultLinkBehavior: (options?: any) => void = this.openLinkRight;
 
     @action
     currentLinkBehavior = () => {
@@ -556,12 +557,20 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
         return null;
     }
 
+    async close() {
+        let res = await DocListCastAsync((CurrentUserUtils.UserDocument.overlays as Doc).data);
+        if (res) res.splice(res.indexOf(LinkFollowBox.Instance.props.Document), 1);
+    }
+
     render() {
         return (
             <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document.height), width: NumCast(this.props.Document.width) }}>
                 <div className="linkFollowBox-header">
-                    {LinkFollowBox.linkDoc ? "Link Title: " + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"}
-                    <div className="linkFollowBox-header direction-indicator">{LinkFollowBox.linkDoc ?
+                    <div className="topHeader">
+                        {LinkFollowBox.linkDoc ? "Link Title: " + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"}
+                        <div onClick={this.close} className="closeDocument"><FontAwesomeIcon icon={faTimes} size="lg" /></div>
+                    </div>
+                    <div className=" direction-indicator">{LinkFollowBox.linkDoc ?
                         LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc ? "Source: " + StrCast(LinkFollowBox.sourceDoc.title) + ", Destination: " + StrCast(LinkFollowBox.destinationDoc.title)
                             : "" : ""}</div>
                 </div>
@@ -586,6 +595,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
                     </div>
                 </div>
                 <div className="linkFollowBox-footer">
+                    <button
+                        onClick={this.resetVars}>
+                        Clear Link
+                    </button>
+                    <div style={{ width: 20 }}></div>
                     <button
                         onClick={this.currentLinkBehavior}
                         disabled={(LinkFollowBox.linkDoc) ? false : true}>
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d2dc3f7ff..0dda948c4 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -17,6 +17,9 @@ import { CollectionViewType } from '../collections/CollectionBaseView';
 import { DocumentView } from '../nodes/DocumentView';
 import { SearchUtil } from '../../util/SearchUtil';
 import { LinkFollowBox } from './LinkFollowBox';
+import { ContextMenu } from '../ContextMenu';
+import { MainView } from '../MainView';
+import { Docs } from '../../documents/Documents';
 library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
 
 
@@ -35,148 +38,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     @observable private _showMore: boolean = false;
     @action toggleShowMore() { this._showMore = !this._showMore; }
 
-
-    unhighlight = () => {
-        Doc.UnhighlightAll();
-        document.removeEventListener("pointerdown", this.unhighlight);
-    }
-
-    @action
-    highlightDoc = () => {
-        document.removeEventListener("pointerdown", this.unhighlight);
-        Doc.HighlightDoc(this.props.destinationDoc);
-        window.setTimeout(() => {
-            document.addEventListener("pointerdown", this.unhighlight);
-        }, 10000);
-    }
-
-    // NOT TESTED
-    // col = collection the doc is in
-    // target = the document to center on
-    @undoBatch
-    openLinkColRight = (col: Doc) => {
-        col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
-        if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-            const newPanX = NumCast(this.props.destinationDoc.x) + NumCast(this.props.destinationDoc.width) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2;
-            const newPanY = NumCast(this.props.destinationDoc.y) + NumCast(this.props.destinationDoc.height) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2;
-            col.panX = newPanX;
-            col.panY = newPanY;
-        }
-        CollectionDockingView.Instance.AddRightSplit(col, undefined);
-    }
-
-    // DONE
-    @undoBatch
-    openFullScreen = () => {
-        let view: DocumentView | null = DocumentManager.Instance.getDocumentView(this.props.destinationDoc);
-        view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
-    }
-
-    // DONE
-    // this opens the linked doc in a right split, NOT in its collection
-    @undoBatch
-    openLinkRight = () => {
-        this.highlightDoc();
-        let alias = Doc.MakeAlias(this.props.destinationDoc);
-        CollectionDockingView.Instance.AddRightSplit(alias, undefined);
-        SelectionManager.DeselectAll();
-    }
-
-    // DONE
-    // this is the standard "follow link" (jump to document)
-    // taken from follow link
-    @undoBatch
-    jumpToLink = async (shouldZoom: boolean) => {
-        //there is an issue right now so this will be false automatically
-        shouldZoom = false;
-        this.highlightDoc();
-        let jumpToDoc = this.props.destinationDoc;
-        let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc));
-        if (pdfDoc) {
-            jumpToDoc = pdfDoc;
-        }
-        let proto = Doc.GetProto(this.props.linkDoc);
-        let targetContext = await Cast(proto.targetContext, Doc);
-        let sourceContext = await Cast(proto.sourceContext, Doc);
-        let self = this;
-
-        let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
-
-        if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) {
-            DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext);
-        }
-        else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) {
-            DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!));
-        }
-        else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
-            DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page)));
-
-        }
-        else {
-            DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc);
-        }
-    }
-
-    // DONE
-    // opens link in new tab (not in a collection)
-    // this opens it full screen, do we need a separate full screen option?
-    @undoBatch
-    openLinkTab = () => {
-        this.highlightDoc();
-        let fullScreenAlias = Doc.MakeAlias(this.props.destinationDoc);
-        this.props.addDocTab(fullScreenAlias, undefined, "inTab");
-        SelectionManager.DeselectAll();
-    }
-
-    // NOT TESTED
-    // opens link in new tab in collection
-    // col = collection the doc is in
-    // target = the document to center on
-    @undoBatch
-    openLinkColTab = (col: Doc) => {
-        this.highlightDoc();
-        col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
-        if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-            const newPanX = NumCast(this.props.destinationDoc.x) + NumCast(this.props.destinationDoc.width) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2;
-            const newPanY = NumCast(this.props.destinationDoc.y) + NumCast(this.props.destinationDoc.height) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2;
-            col.panX = newPanX;
-            col.panY = newPanY;
-        }
-        // CollectionDockingView.Instance.AddRightSplit(col, undefined);
-        this.props.addDocTab(col, undefined, "inTab");
-        SelectionManager.DeselectAll();
-    }
-
-    // DONE
-    // this will open a link next to the source doc
-    @undoBatch
-    openLinkInPlace = () => {
-        this.highlightDoc();
-
-        let alias = Doc.MakeAlias(this.props.destinationDoc);
-        let y = NumCast(this.props.sourceDoc.y);
-        let x = NumCast(this.props.sourceDoc.x);
-
-        let width = NumCast(this.props.sourceDoc.width);
-        let height = NumCast(this.props.sourceDoc.height);
-
-        alias.x = x + width + 30;
-        alias.y = y;
-        alias.width = width;
-        alias.height = height;
-
-        SelectionManager.SelectedDocuments().map(dv => {
-            if (dv.props.Document === this.props.sourceDoc) {
-                dv.props.addDocument && dv.props.addDocument(alias, false);
-            }
-        });
-
-        this.jumpToLink(false);
-    }
-
-    //set this to be the default link behavior, can be any of the above
-    private defaultLinkBehavior: any = this.openLinkInPlace;
-
     onEdit = (e: React.PointerEvent): void => {
         e.stopPropagation();
         this.props.showEditor(this.props.linkDoc);
@@ -226,6 +87,26 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         e.stopPropagation();
     }
 
+    onContextMenu = (e: React.MouseEvent) => {
+        e.preventDefault();
+        ContextMenu.Instance.addItem({ description: "Open in Link Follower", event: () => console.log("opening 1!"), icon: "link" });
+        ContextMenu.Instance.addItem({ description: "Follow Default Link", event: () => this.followDefault(), icon: "arrow-right" });
+        ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
+    }
+
+    followDefault = () => {
+        if (!LinkFollowBox.Instance) {
+            Docs.Create.LinkFollowBoxDocument({ x: MainView.Instance.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" });
+        }
+        LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
+        LinkFollowBox.Instance.defaultLinkBehavior();
+    }
+
+    openLinkFollower = () => {
+        LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
+        MainView.Instance.toggleLinkFollowBox(false);
+    }
+
     render() {
 
         let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
@@ -240,12 +121,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                             {canExpand ? <div title="Show more" className="button" onPointerDown={() => this.toggleShowMore()}>
                                 <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
                             <div title="Edit link" className="button" onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
-                            {/* Original */}
-                            {/* <div title="Follow link" className="button" onPointerDown={this.onFollowLink}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div> */}
-                            {/* New */}
-                            <div title="Follow link" className="button" onClick={() => LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc)}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div>
-                            {/* <div title="Follow link" className="button" onClick={this.defaultLinkBehavior}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div> */}
-
+                            <div title="Follow link" className="button" onClick={this.followDefault} onContextMenu={this.onContextMenu}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div>
                         </div>
                     </div>
                     {this._showMore ? this.renderMetadata() : <></>}
@@ -254,44 +130,4 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
             </div >
         );
     }
-}
-
-    // @undoBatch
-    // onFollowLink = async (e: React.PointerEvent): Promise<void> => {
-    //     e.stopPropagation();
-    //     e.persist();
-    //     let jumpToDoc = this.props.destinationDoc;
-    //     let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc));
-    //     if (pdfDoc) {
-    //         jumpToDoc = pdfDoc;
-    //     }
-    //     let proto = Doc.GetProto(this.props.linkDoc);
-    //     let targetContext = await Cast(proto.targetContext, Doc);
-    //     let sourceContext = await Cast(proto.sourceContext, Doc);
-    //     let self = this;
-
-
-    //     let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
-    //     if (e.ctrlKey) {
-    //         dockingFunc = (document: Doc) => CollectionDockingView.Instance.AddRightSplit(document, undefined);
-    //     }
-
-    //     if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) {
-    //         DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!);
-    //         console.log("1")
-    //     }
-    //     else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) {
-    //         DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!));
-    //         console.log("2")
-    //     }
-    //     else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
-    //         DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page)));
-    //         console.log("3")
-
-    //     }
-    //     else {
-    //         DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, dockingFunc);
-    //         console.log("4")
-
-    //     }
-    // }
\ No newline at end of file
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From c1adf10630a4fd163c1c9ef11abf9ad090bf29a3 Mon Sep 17 00:00:00 2001
From: monikahedman <monika_hedman@brown.edu>
Date: Tue, 27 Aug 2019 18:29:02 -0400
Subject: haudfhajfjadjad['jad'

---
 src/client/views/MainView.tsx             | 2 +-
 src/client/views/linking/LinkMenuItem.tsx | 8 +++++---
 2 files changed, 6 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d580925d6..20fc1dce8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -155,6 +155,7 @@ export class MainView extends React.Component {
     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) {
@@ -470,7 +471,6 @@ export class MainView extends React.Component {
             [React.createRef<HTMLDivElement>(), "globe-asia", "Add Website", addWebNode],
             [React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
             [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode],
-            // [React.createRef<HTMLDivElement>(), "link", "Open Link Follow Box", addLinkFollowBox],
             [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode
             //[React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
         ];
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 0dda948c4..1210d7198 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -8,7 +8,7 @@ import './LinkMenu.scss';
 import React = require("react");
 import { Doc, DocListCastAsync, WidthSym } from '../../../new_fields/Doc';
 import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types';
-import { observable, action } from 'mobx';
+import { observable, action, computed } from 'mobx';
 import { LinkManager } from '../../util/LinkManager';
 import { DragLinkAsDocument } from '../../util/DragManager';
 import { CollectionDockingView } from '../collections/CollectionDockingView';
@@ -20,6 +20,7 @@ import { LinkFollowBox } from './LinkFollowBox';
 import { ContextMenu } from '../ContextMenu';
 import { MainView } from '../MainView';
 import { Docs } from '../../documents/Documents';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
 
 
@@ -95,8 +96,9 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     }
 
     followDefault = () => {
-        if (!LinkFollowBox.Instance) {
-            Docs.Create.LinkFollowBoxDocument({ x: MainView.Instance.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" });
+        if (LinkFollowBox.Instance === undefined) {
+            let doc = Docs.Create.LinkFollowBoxDocument({ x: MainView.Instance.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" });
+            Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", doc);
         }
         LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
         LinkFollowBox.Instance.defaultLinkBehavior();
-- 
cgit v1.2.3-70-g09d2


From f6c15087d30f27c15b3b6304af58c93c65536131 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 29 Aug 2019 14:50:22 -0400
Subject: changed linkFollowBox creation approach.  fixed target contexts to
 add link default.

---
 src/client/views/MainView.tsx                      |   8 +-
 src/client/views/OverlayView.tsx                   | 108 +++++++++++----------
 src/client/views/linking/LinkFollowBox.tsx         |  20 ++--
 src/client/views/linking/LinkMenuItem.tsx          |  18 ++--
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   1 -
 src/client/views/nodes/FormattedTextBoxComment.tsx |   8 +-
 .../authentication/models/current_user_utils.ts    |   3 +
 7 files changed, 78 insertions(+), 88 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 1fc8d1397..b64986084 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -438,13 +438,7 @@ export class MainView extends React.Component {
         if (LinkFollowBox.Instance) {
             let dvs = DocumentManager.Instance.getDocumentViews(LinkFollowBox.Instance.props.Document);
             // if it already exisits, close it
-            if (dvs.length > 0 && shouldClose) LinkFollowBox.Instance.close();
-            // open it if not
-            else Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", LinkFollowBox.Instance.props.Document);
-        }
-        else {
-            let doc = Docs.Create.LinkFollowBoxDocument({ x: this.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" });
-            Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", doc);
+            LinkFollowBox.Instance.props.Document.isMinimized = (dvs.length > 0 && shouldClose);
         }
     }
 
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index a1afe651c..54ab8696e 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,6 +1,6 @@
 import * as React from "react";
 import { observer } from "mobx-react";
-import { observable, action } from "mobx";
+import { observable, action, trace, computed } from "mobx";
 import { Utils, emptyFunction, returnOne, returnTrue, returnEmptyString, returnZero, returnFalse } from "../../Utils";
 
 import './OverlayView.scss';
@@ -140,61 +140,65 @@ export class OverlayView extends React.Component {
         return remove;
     }
 
+    @computed get overlayDocs() {
+        return CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => {
+            let offsetx = 0, offsety = 0;
+            let onPointerMove = action((e: PointerEvent) => {
+                if (e.buttons === 1) {
+                    d.x = e.clientX + offsetx;
+                    d.y = e.clientY + offsety;
+                    e.stopPropagation();
+                    e.preventDefault();
+                }
+            });
+            let onPointerUp = action((e: PointerEvent) => {
+                document.removeEventListener("pointermove", onPointerMove);
+                document.removeEventListener("pointerup", onPointerUp);
+                e.stopPropagation();
+                e.preventDefault();
+            });
+
+            let onPointerDown = (e: React.PointerEvent) => {
+                offsetx = NumCast(d.x) - e.clientX;
+                offsety = NumCast(d.y) - e.clientY;
+                e.stopPropagation();
+                e.preventDefault();
+                document.addEventListener("pointermove", onPointerMove);
+                document.addEventListener("pointerup", onPointerUp);
+            };
+            return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)`, display: d.isMinimized ? "none" : "" }}>
+                <DocumentContentsView
+                    Document={d}
+                    ChromeHeight={returnZero}
+                    isSelected={returnFalse}
+                    select={emptyFunction}
+                    layoutKey={"layout"}
+                    bringToFront={emptyFunction}
+                    addDocument={undefined}
+                    removeDocument={undefined}
+                    ContentScaling={returnOne}
+                    PanelWidth={returnOne}
+                    PanelHeight={returnOne}
+                    ScreenToLocalTransform={Transform.Identity}
+                    renderDepth={1}
+                    parentActive={returnTrue}
+                    whenActiveChanged={emptyFunction}
+                    focus={emptyFunction}
+                    backgroundColor={returnEmptyString}
+                    addDocTab={emptyFunction}
+                    pinToPres={emptyFunction}
+                    ContainingCollectionView={undefined}
+                    zoomToScale={emptyFunction}
+                    getScale={returnOne} />
+            </div>;
+        })
+    }
+
     render() {
         return (
             <div className="overlayView" id="overlayView">
                 {this._elements}
-                {CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => {
-                    let offsetx = 0, offsety = 0;
-                    let onPointerMove = action((e: PointerEvent) => {
-                        if (e.buttons === 1) {
-                            d.x = e.clientX + offsetx;
-                            d.y = e.clientY + offsety;
-                            e.stopPropagation();
-                            e.preventDefault();
-                        }
-                    });
-                    let onPointerUp = action((e: PointerEvent) => {
-                        document.removeEventListener("pointermove", onPointerMove);
-                        document.removeEventListener("pointerup", onPointerUp);
-                        e.stopPropagation();
-                        e.preventDefault();
-                    });
-
-                    let onPointerDown = (e: React.PointerEvent) => {
-                        offsetx = NumCast(d.x) - e.clientX;
-                        offsety = NumCast(d.y) - e.clientY;
-                        e.stopPropagation();
-                        e.preventDefault();
-                        document.addEventListener("pointermove", onPointerMove);
-                        document.addEventListener("pointerup", onPointerUp);
-                    }
-                    return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)` }}>
-                        <DocumentContentsView
-                            Document={d}
-                            ChromeHeight={returnZero}
-                            isSelected={returnFalse}
-                            select={emptyFunction}
-                            layoutKey={"layout"}
-                            bringToFront={emptyFunction}
-                            addDocument={undefined}
-                            removeDocument={undefined}
-                            ContentScaling={returnOne}
-                            PanelWidth={returnOne}
-                            PanelHeight={returnOne}
-                            ScreenToLocalTransform={Transform.Identity}
-                            renderDepth={1}
-                            parentActive={returnTrue}
-                            whenActiveChanged={emptyFunction}
-                            focus={emptyFunction}
-                            backgroundColor={returnEmptyString}
-                            addDocTab={emptyFunction}
-                            pinToPres={emptyFunction}
-                            ContainingCollectionView={undefined}
-                            zoomToScale={emptyFunction}
-                            getScale={returnOne} />
-                    </div>
-                })}
+                {this.overlayDocs}
             </div>
         );
     }
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 07f07f72a..74663f9af 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -1,4 +1,4 @@
-import { observable, computed, action, trace, ObservableMap, runInAction, reaction, IReactionDisposer } from "mobx";
+import { observable, computed, action, runInAction, reaction, IReactionDisposer } from "mobx";
 import React = require("react");
 import { observer } from "mobx-react";
 import { FieldViewProps, FieldView } from "../nodes/FieldView";
@@ -15,8 +15,6 @@ import { SearchUtil } from "../../util/SearchUtil";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { listSpec } from "../../../new_fields/Schema";
 import { DocServer } from "../../DocServer";
-import { RefField } from "../../../new_fields/RefField";
-import { Docs } from "../../documents/Documents";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faTimes } from '@fortawesome/free-solid-svg-icons';
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
@@ -126,9 +124,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
             const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs)));
             allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
             docs.forEach(doc => map.delete(doc));
-            runInAction(() => {
+            runInAction(async () => {
                 this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest }));
                 this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
+                let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.targetContext, Doc)) as Doc;
+                runInAction(() => tcontext && this._docs.splice(0, 0, { col: tcontext, target: dest }));
             });
         }
     }
@@ -268,7 +268,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
             let fullScreenAlias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
             // THIS IS EMPTY FUNCTION
             this.props.addDocTab(fullScreenAlias, undefined, "inTab");
-            console.log(this.props.addDocTab)
+            console.log(this.props.addDocTab);
 
             this.highlightDoc();
             SelectionManager.DeselectAll();
@@ -348,7 +348,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
             }
             else if (this.selectedMode === FollowModes.OPENTAB) {
                 if (notOpenInContext) this.openLinkTab();
-                else this.selectedContext && this.openLinkColTab({ context: this.selectedContext, shouldZoom: shouldZoom })
+                else this.selectedContext && this.openLinkColTab({ context: this.selectedContext, shouldZoom: shouldZoom });
             }
             else if (this.selectedMode === FollowModes.PAN) {
                 this.jumpToLink({ shouldZoom: shouldZoom });
@@ -555,19 +555,13 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
         return null;
     }
 
-    async close() {
-        let res = await DocListCastAsync((CurrentUserUtils.UserDocument.overlays as Doc).data);
-        if (res) res.splice(res.indexOf(LinkFollowBox.Instance!.props.Document), 1);
-        LinkFollowBox.Instance = undefined;
-    }
-
     render() {
         return (
             <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document.height), width: NumCast(this.props.Document.width) }}>
                 <div className="linkFollowBox-header">
                     <div className="topHeader">
                         {LinkFollowBox.linkDoc ? "Link Title: " + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"}
-                        <div onClick={this.close} className="closeDocument"><FontAwesomeIcon icon={faTimes} size="lg" /></div>
+                        <div onClick={() => this.props.Document.isMinimized = true} className="closeDocument"><FontAwesomeIcon icon={faTimes} size="lg" /></div>
                     </div>
                     <div className=" direction-indicator">{LinkFollowBox.linkDoc ?
                         LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc ? "Source: " + StrCast(LinkFollowBox.sourceDoc.title) + ", Destination: " + StrCast(LinkFollowBox.destinationDoc.title)
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 6c10d4fb6..780f161d3 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -97,24 +97,20 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
 
     @action.bound
     async followDefault() {
-        if (LinkFollowBox.Instance === undefined) {
-            let doc = await Docs.Create.LinkFollowBoxDocument({ x: MainView.Instance.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" });
-            await Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", doc);
-        } else {
-            LinkFollowBox.Instance!.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
-            LinkFollowBox.Instance!.defaultLinkBehavior();
+        if (LinkFollowBox.Instance !== undefined) {
+            LinkFollowBox.Instance.props.Document.isMinimized = false;
+            LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
+            LinkFollowBox.Instance.defaultLinkBehavior();
         }
     }
 
     @action.bound
     async openLinkFollower() {
-        if (LinkFollowBox.Instance === undefined) {
-            let doc = await Docs.Create.LinkFollowBoxDocument({ x: MainView.Instance.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" });
-            await Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", doc);
-        } else {
+        if (LinkFollowBox.Instance !== undefined) {
+            LinkFollowBox.Instance.props.Document.isMinimized = false;
             MainView.Instance.toggleLinkFollowBox(false);
+            LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
         }
-        LinkFollowBox.Instance!.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
     }
 
     render() {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7631ecc6c..c9c394960 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -8,7 +8,6 @@ import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"
 import "./DocumentView.scss";
 import React = require("react");
 import { Doc } from "../../../new_fields/Doc";
-import { returnEmptyString } from "../../../Utils";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     x?: number;
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 9123f8aed..0287d93a9 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -1,12 +1,12 @@
-import { Plugin, EditorState } from "prosemirror-state"
-import './FormattedTextBoxComment.scss'
+import { Plugin, EditorState } from "prosemirror-state";
+import './FormattedTextBoxComment.scss';
 import { ResolvedPos, Mark } from "prosemirror-model";
 import { EditorView } from "prosemirror-view";
 import { Doc } from "../../../new_fields/Doc";
 
 export let selectionSizePlugin = new Plugin({
     view(editorView) { return new SelectionSizeTooltip(editorView); }
-})
+});
 export function findOtherUserMark(marks: Mark[]): Mark | undefined {
     return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
 }
@@ -106,5 +106,5 @@ export class SelectionSizeTooltip {
         }
     }
 
-    destroy() { SelectionSizeTooltip.tooltip.style.display = "none" }
+    destroy() { SelectionSizeTooltip.tooltip.style.display = "none"; }
 }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 83e45d3ce..9866e22eb 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -79,6 +79,9 @@ export class CurrentUserUtils {
             Doc.GetProto(overlays).backgroundColor = "#aca3a6";
             doc.overlays = overlays;
         }
+        if (doc.linkFollowBox === undefined) {
+            PromiseValue(Cast(doc.overlays, Doc)).then(overlays => overlays && Doc.AddDocToList(overlays, "data", doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, width: 500, height: 370, title: "Link Follower" })));
+        }
         StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library");
         doc.width = 100;
         doc.preventTreeViewOpen = true;
-- 
cgit v1.2.3-70-g09d2


From 233893698083cbcfcf39ddad8b57049aeb1ba842 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 13 Sep 2019 14:18:55 -0400
Subject: refactored how ruleProvider's work.   overloaded custom template  for
 creating metadata fields

---
 src/client/util/RichTextRules.ts                   |  8 +++----
 src/client/util/TooltipTextMenu.tsx                |  4 ++--
 src/client/views/DocumentDecorations.tsx           | 11 ++-------
 src/client/views/InkingControl.tsx                 |  5 ++--
 src/client/views/MainOverlayTextBox.tsx            |  2 +-
 src/client/views/MainView.tsx                      |  2 ++
 src/client/views/TemplateMenu.tsx                  | 16 ++++++-------
 .../views/collections/CollectionDockingView.tsx    |  1 +
 .../views/collections/CollectionSchemaView.tsx     |  1 +
 src/client/views/collections/CollectionSubView.tsx |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 27 +++++++++++-----------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 --
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 13 +++++++----
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/FormattedTextBox.tsx        |  2 +-
 .../views/presentationview/PresentationElement.tsx |  1 +
 src/client/views/search/SearchItem.tsx             |  1 +
 src/new_fields/Doc.ts                              |  2 +-
 19 files changed, 51 insertions(+), 51 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index c0c62463a..c727eec73 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -61,7 +61,7 @@ export const inpRules = {
             new RegExp(/^#([0-9]+)\s$/),
             (state, match, start, end) => {
                 let size = Number(match[1]);
-                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     (Cast(FormattedTextBox.InputBoxOverlay!.props.Document, Doc) as Doc).heading = Number(match[1]);
@@ -74,7 +74,7 @@ export const inpRules = {
             (state, match, start, end) => {
                 let node = (state.doc.resolve(start) as any).nodeAfter;
                 let sm = state.storedMarks || undefined;
-                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleAlign_" + heading] = "center";
@@ -88,7 +88,7 @@ export const inpRules = {
             (state, match, start, end) => {
                 let node = (state.doc.resolve(start) as any).nodeAfter;
                 let sm = state.storedMarks || undefined;
-                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleAlign_" + heading] = "left";
@@ -100,7 +100,7 @@ export const inpRules = {
             (state, match, start, end) => {
                 let node = (state.doc.resolve(start) as any).nodeAfter;
                 let sm = state.storedMarks || undefined;
-                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleAlign_" + heading] = "right";
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index c376b6f86..84d045e6f 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -496,7 +496,7 @@ export class TooltipTextMenu {
             if (markType.name[0] === 'p') {
                 let size = this.fontSizeToNum.get(markType);
                 if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
-                let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = this.editorProps.ruleProvider;
                 let heading = NumCast(this.editorProps.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleSize_" + heading] = size;
@@ -505,7 +505,7 @@ export class TooltipTextMenu {
             else {
                 let fontName = this.fontStylesToName.get(markType);
                 if (fontName) { this.updateFontStyleDropdown(fontName); }
-                let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = this.editorProps.ruleProvider;
                 let heading = NumCast(this.editorProps.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleFont_" + heading] = fontName;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6d63e8f73..ebdf2a749 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -365,14 +365,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
                     let docViews = SelectionManager.ViewsSortedVertically();
                     let topDocView = docViews[0];
-                    let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
-                    if (ind !== -1) {
-                        topDocView.templates.splice(ind, 1);
-                        topDocView.props.Document.subBulletDocs = undefined;
-                    } else {
-                        topDocView.addTemplate(Templates.Bullet);
-                        topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
-                    }
+                    topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
                 }
             }
             this._removeIcon = false;
@@ -439,7 +432,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let usingRule = false;
         SelectionManager.SelectedDocuments().map(dv => {
             let cv = dv.props.ContainingCollectionView;
-            let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
+            let ruleProvider = cv && cv.props.ruleProvider;
             let heading = NumCast(dv.props.Document.heading);
             ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`);
             usingRule = usingRule || (ruleProvider && heading ? true : false);
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 867735c0b..86d0fc0be 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -71,9 +71,8 @@ export class InkingControl extends React.Component {
                     targetDoc.backgroundColor = this._selectedColor;
                 if (view.props.Document.heading) {
                     let cv = view.props.ContainingCollectionView;
-                    let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
-                    let parback = cv && StrCast(cv.props.Document.backgroundColor);
-                    cv && parback && (Doc.GetProto(ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb));
+                    let ruleProvider = cv && (Cast(cv.props.ruleProvider, Doc) as Doc);
+                    cv && (Doc.GetProto(ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb));
                     // if (parback && cv && parback.indexOf("rgb") !== -1) {
                     //     let parcol = Utils.fromRGBAstr(parback);
                     //     let hsl = Utils.RGBToHSL(parcol.r, parcol.g, parcol.b);
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index c3a2cb214..71fb2707d 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -72,7 +72,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         if (this._textTargetDiv) {
             this._textTargetDiv.style.color = this._textColor;
         }
-        this._textAutoHeight = autoHeight;
         this.TextFieldKey = textFieldKey!;
         let txf = tx ? tx : () => Transform.Identity();
         this._textXf = txf;
@@ -143,6 +142,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                                 Document={FormattedTextBox.InputBoxOverlay.props.Document}
                                 DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
                                 onClick={undefined}
+                                ruleProvider={this._textBox ? this._textBox.props.ruleProvider : undefined}
                                 ChromeHeight={this.ChromeHeight}
                                 isSelected={returnTrue} select={emptyFunction} renderDepth={0}
                                 ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b64986084..2cec1c052 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -322,6 +322,7 @@ export class MainView extends React.Component {
                             addDocTab={emptyFunction}
                             pinToPres={emptyFunction}
                             onClick={undefined}
+                            ruleProvider={undefined}
                             removeDocument={undefined}
                             ScreenToLocalTransform={Transform.Identity}
                             ContentScaling={returnOne}
@@ -385,6 +386,7 @@ export class MainView extends React.Component {
             addDocTab={this.addDocTabFunc}
             pinToPres={emptyFunction}
             removeDocument={undefined}
+            ruleProvider={undefined}
             onClick={undefined}
             ScreenToLocalTransform={Transform.Identity}
             ContentScaling={returnOne}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 0ef1a137d..060191e29 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -51,16 +51,16 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @observable private _hidden: boolean = true;
     dragRef = React.createRef<HTMLUListElement>();
 
-    constructor(props: TemplateMenuProps) {
-        super(props);
-    }
-
     toggleCustom = (e: React.MouseEvent): void => {
         this.props.docs.map(dv => {
-            if (dv.Document.type !== DocumentType.COL && dv.Document.type !== DocumentType.TEMPLATE) {
-                dv.makeCustomViewClicked();
-            } else if (dv.Document.nativeLayout) {
-                dv.makeNativeViewClicked();
+            if (dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.DataDoc) {
+                Doc.MakeMetadataFieldTemplate(dv.props.Document, dv.props.ContainingCollectionView.props.DataDoc)
+            } else {
+                if (dv.Document.type !== DocumentType.COL && dv.Document.type !== DocumentType.TEMPLATE) {
+                    dv.makeCustomViewClicked();
+                } else if (dv.Document.nativeLayout) {
+                    dv.makeNativeViewClicked();
+                }
             }
         });
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index fb8b0c41b..166fa0811 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -631,6 +631,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             bringToFront={emptyFunction}
             addDocument={undefined}
             removeDocument={undefined}
+            ruleProvider={undefined}
             ContentScaling={this.contentScaling}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 9d83aa6c1..dca1d7c1d 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -995,6 +995,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         Document={this.props.Document}
                         fitToBox={this.props.fitToBox}
                         onClick={this.props.onClick}
+                        ruleProvider={undefined}
                         showOverlays={this.props.showOverlays}
                         addDocument={this.props.addDocument}
                         removeDocument={this.props.removeDocument}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 99e5ab7b3..270916522 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -34,6 +34,7 @@ export interface CollectionViewProps extends FieldViewProps {
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+    ruleProvider: Doc | undefined;
 }
 
 export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9a8ae3535..4a3e5039a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -257,18 +257,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
     private addLiveTextBox = (newBox: Doc) => {
         FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
-        let heading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
-        heading = heading === 0 || this.childDocs.length === 0 ? 1 : heading === 1 ? 2 : 0;
+        let maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
+        let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0;
         if (heading === 0) {
             let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 :
                 DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0);
-            heading = !sorted.length ? 1 : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
-        }
-        newBox.heading = heading;
-
-        if (Cast(this.props.Document.ruleProvider, Doc) as Doc) {
-            newBox.ruleProvider = Doc.GetProto(Cast(this.props.Document.ruleProvider, Doc) as Doc);
+            heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
         }
+        !this.props.Document.isRuleProvider && (newBox.heading = heading);
         this.addDocument(newBox, false);
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
@@ -698,6 +694,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
+            ruleProvider: this.props.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider,
             onClick: this.props.onClick,
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
             renderDepth: this.props.renderDepth + 1,
@@ -723,6 +720,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
+            ruleProvider: this.props.ruleProvider,
             onClick: this.props.onClick,
             ScreenToLocalTransform: this.getTransform,
             renderDepth: this.props.renderDepth,
@@ -817,6 +815,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     if (pair.layout && !(pair.data instanceof Promise)) {
                         prev.push({
                             ele: <CollectionFreeFormDocumentView key={doc[Id]}
+                                ruleProvider={this.props.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
                                 jitterRotation={NumCast(this.props.Document.jitterRotation)}
                                 x={script ? pos.x : undefined} y={script ? pos.y : undefined}
                                 width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
@@ -873,6 +872,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }, "arrange contents");
     }
 
+    autoFormat = () => {
+        this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider;
+        this.childDocs.map(child => child.heading = undefined);
+    }
+
     analyzeStrokes = async () => {
         let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField);
         if (!data) {
@@ -900,11 +904,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             },
             icon: !this.props.Document.useClusters ? "braille" : "braille"
         });
-        layoutItems.push({
-            description: `${this.props.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`,
-            event: () => this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider,
-            icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard"
-        });
+        layoutItems.push({ description: `${this.props.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: !this.props.Document.isRuleProvider ? "chalkboard" : "chalkboard" });
         layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
         layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
         layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
@@ -1034,7 +1034,6 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps &
 @observer
 class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
     @computed get backgroundView() {
-        let props = this.props;
         return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
             renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 4308497a1..e46e8cb88 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -329,7 +329,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 selected = [newCollection];
                 newCollection.x = bounds.left + bounds.width;
                 summary.proto!.subBulletDocs = new List<Doc>(selected);
-                summary.templates = new List<string>([Templates.Bullet.Layout]);
                 let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
                 container.viewType = CollectionViewType.Stacking;
                 container.autoHeight = true;
@@ -356,7 +355,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 this.props.addLiveTextDocument(summary);
             }
             else {
-                newCollection.ruleProvider = this.props.container.props.Document.isRuleProvider ? this.props.container.props.Document : this.props.container.props.Document.ruleProvider;
                 this.props.addDocument(newCollection, false);
                 this.props.selectDocuments([newCollection]);
             }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 082e5c5e3..4872a7aa1 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -74,7 +74,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     borderRounding = () => {
-        let ruleProvider = this.props.Document.ruleProvider as Doc;
+        let ruleProvider = this.props.ruleProvider;
         let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined;
         let br = StrCast(this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout.borderRounding : this.props.Document.borderRounding);
         br = !br && ruleRounding ? ruleRounding : br;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0816cb813..cc04c5a9f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, reaction, runInAction, trace, observable } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
 import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
@@ -9,12 +9,13 @@ import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
 import { ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { RouteStore } from '../../../server/RouteStore';
 import { emptyFunction, returnTrue, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from "../../documents/Documents";
+import { DocumentType } from '../../documents/DocumentTypes';
 import { ClientUtils } from '../../util/ClientUtils';
 import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from "../../util/DocumentManager";
@@ -35,12 +36,10 @@ import { MainView } from '../MainView';
 import { OverlayView } from '../OverlayView';
 import { ScriptBox } from '../ScriptBox';
 import { ScriptingRepl } from '../ScriptingRepl';
-import { Template } from "./../Templates";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import { FormattedTextBox } from './FormattedTextBox';
 import React = require("react");
-import { DocumentType } from '../../documents/DocumentTypes';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
@@ -89,6 +88,7 @@ export interface DocumentViewProps {
     renderDepth: number;
     showOverlays?: (doc: Doc) => { title?: string, caption?: string };
     ContentScaling: () => number;
+    ruleProvider: Doc | undefined;
     PanelWidth: () => number;
     PanelHeight: () => number;
     focus: (doc: Doc, willZoom: boolean, scale?: number) => void;
@@ -470,6 +470,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     this.props.Document.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) :
                         Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
 
+                fieldTemplate.backgroundColor = StrCast(this.props.Document.backgroundColor);
+                fieldTemplate.heading = 1;
+
                 let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) });
                 let proto = Doc.GetProto(docTemplate);
                 Doc.MakeMetadataFieldTemplate(fieldTemplate, proto);
@@ -800,7 +803,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
 
     render() {
-        let ruleProvider = this.props.Document.ruleProvider as Doc;
+        let ruleProvider = this.props.ruleProvider;
         let ruleColor = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(this.props.Document.heading)]) : undefined;
         let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined;
         let colorSet = this.layoutDoc.backgroundColor !== this.layoutDoc.defaultBackgroundColor;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index d9774303b..943d181d6 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -30,6 +30,7 @@ export interface FieldViewProps {
     leaveNativeSize?: boolean;
     fitToBox?: boolean;
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
+    ruleProvider: Doc | undefined;
     Document: Doc;
     DataDoc?: Doc;
     onClick?: ScriptField;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index d39291743..a0dc054cf 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -458,7 +458,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
 
         this._rulesReactionDisposer = reaction(() => {
-            let ruleProvider = Cast(this.props.Document.ruleProvider, Doc);
+            let ruleProvider = this.props.ruleProvider;
             let heading = NumCast(this.props.Document.heading);
             if (ruleProvider instanceof Doc) {
                 return {
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 80aa25f48..7be44faf6 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -351,6 +351,7 @@ export default class PresentationElement extends React.Component<PresentationEle
                     Document={this.props.document}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
+                    ruleProvider={undefined}
                     ScreenToLocalTransform={Transform.Identity}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index c56d093fa..cd3dd912c 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -165,6 +165,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     Document={this.props.doc}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
+                    ruleProvider={undefined}
                     ScreenToLocalTransform={Transform.Identity}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 1a3d689bb..5b22a62a1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -560,7 +560,7 @@ export namespace Doc {
 
     export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc) {
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
-        let metadataFieldName = StrCast(fieldTemplate.title);
+        let metadataFieldName = StrCast(fieldTemplate.title).replace(/^-/, "");
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
         let fieldLayoutDoc = fieldTemplate;
         if (fieldTemplate.layout instanceof Doc) {
-- 
cgit v1.2.3-70-g09d2

