From 17b27d3575d3f91f461262e5ad72a457238d198a Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Wed, 7 Aug 2019 16:28:51 -0400
Subject: correlation matrix completed

---
 .../collectionFreeForm/CollectionFreeFormView.tsx     | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 29f9b1429..9344b43d2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
+import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faBrain } from "@fortawesome/free-solid-svg-icons";
 import { action, computed } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc";
@@ -37,8 +37,9 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
+import { ClientRecommender } from "../../../ClientRecommender";
 
-library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
+library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBrain);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -596,6 +597,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 input.click();
             }
         });
+        ContextMenu.Instance.addItem({
+            description: "Recommender System",
+            event: async () => {
+                new ClientRecommender();
+                let activedocs = this.getActiveDocuments();
+                await Promise.all(activedocs.map((doc: Doc) => {
+                    console.log(StrCast(doc.title));
+                    const extdoc = doc.data_ext as Doc;
+                    return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+                }));
+                console.log(ClientRecommender.Instance.createDistanceMatrix());
+            },
+            icon: "brain"
+        });
     }
 
 
-- 
cgit v1.2.3-70-g09d2


From 17f28e393e0c24fcace33a3ecd5564bc766fe685 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Thu, 8 Aug 2019 13:00:51 -0400
Subject: fuck you react

---
 src/client/ClientRecommender.ts                    | 101 --------------
 src/client/ClientRecommender.tsx                   | 148 +++++++++++++++++++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 src/server/Recommender.ts                          |   2 +
 4 files changed, 153 insertions(+), 102 deletions(-)
 delete mode 100644 src/client/ClientRecommender.ts
 create mode 100644 src/client/ClientRecommender.tsx

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

diff --git a/src/client/ClientRecommender.ts b/src/client/ClientRecommender.ts
deleted file mode 100644
index 7ff79ab50..000000000
--- a/src/client/ClientRecommender.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { Doc } from "../new_fields/Doc";
-import { StrCast } from "../new_fields/Types";
-import { List } from "../new_fields/List";
-import { CognitiveServices } from "./cognitive_services/CognitiveServices";
-
-
-var assert = require('assert');
-
-export class ClientRecommender {
-
-    static Instance: ClientRecommender;
-    private docVectors: Set<number[]>;
-
-    constructor() {
-        //console.log("creating client recommender...");
-        ClientRecommender.Instance = this;
-        this.docVectors = new Set<number[]>();
-    }
-
-
-    /***
-     * Computes the cosine similarity between two vectors in Euclidean space. 
-     */
-
-    private distance(vector1: number[], vector2: number[]) {
-        assert(vector1.length === vector2.length, "Vectors are not the same length");
-        var dotproduct = 0;
-        var mA = 0;
-        var mB = 0;
-        for (let i = 0; i < vector1.length; i++) { // here you missed the i++
-            dotproduct += (vector1[i] * vector2[i]);
-            mA += (vector1[i] * vector1[i]);
-            mB += (vector2[i] * vector2[i]);
-        }
-        mA = Math.sqrt(mA);
-        mB = Math.sqrt(mB);
-        var similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets
-        return similarity;
-    }
-
-    /***
-     * Computes the mean of a set of vectors
-     */
-
-    public mean(paragraph: Set<number[]>) {
-        const n = 200;
-        const num_words = paragraph.size;
-        let meanVector = new Array<number>(n).fill(0); // mean vector
-        paragraph.forEach((wordvec: number[]) => {
-            for (let i = 0; i < n; i++) {
-                meanVector[i] += wordvec[i];
-            }
-        });
-        meanVector = meanVector.map(x => x / num_words);
-        this.addToDocSet(meanVector);
-        return meanVector;
-    }
-
-    private addToDocSet(vector: number[]) {
-        if (this.docVectors) {
-            this.docVectors.add(vector);
-        }
-    }
-
-    /***
-     * Uses Cognitive Services to extract keywords from a document
-     */
-
-    public async extractText(dataDoc: Doc, extDoc: Doc) {
-        let data = StrCast(dataDoc.title);
-        //console.log(data);
-        let converter = (results: any) => {
-            let keyterms = new List<string>();
-            results.documents.forEach((doc: any) => {
-                let keyPhrases = doc.keyPhrases;
-                keyPhrases.map((kp: string) => keyterms.push(kp));
-            });
-            return keyterms;
-        };
-        await CognitiveServices.Text.Manager.analyzer(extDoc, ["key words"], data, converter);
-    }
-
-    /***
-     * Creates distance matrix for all Documents analyzed
-     */
-
-    public createDistanceMatrix(documents: Set<number[]> = this.docVectors) {
-        const documents_list = Array.from(documents);
-        const n = documents_list.length;
-        var matrix = new Array(n).fill(0).map(() => new Array(n).fill(0));
-        for (let i = 0; i < n; i++) {
-            var doc1 = documents_list[i];
-            for (let j = 0; j < n; j++) {
-                var doc2 = documents_list[j];
-                matrix[i][j] = this.distance(doc1, doc2);
-            }
-        }
-        return matrix;
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
new file mode 100644
index 000000000..2344ee490
--- /dev/null
+++ b/src/client/ClientRecommender.tsx
@@ -0,0 +1,148 @@
+import { Doc } from "../new_fields/Doc";
+import { StrCast } from "../new_fields/Types";
+import { List } from "../new_fields/List";
+import { CognitiveServices } from "./cognitive_services/CognitiveServices";
+import React = require("react");
+import { observer } from "mobx-react";
+import { observable, action, computed, reaction } from "mobx";
+var assert = require('assert');
+import Table from 'react-bootstrap/Table';
+
+export interface RecommenderProps {
+    title: string;
+}
+
+@observer
+export class ClientRecommender extends React.Component<RecommenderProps> {
+
+    static Instance: ClientRecommender;
+    private docVectors: Set<number[]>;
+    private corr_matrix = observable([[0, 0], [0, 0]]);
+    @observable private firstnum = 0;
+    //@observable private corr_matrix: number[][] = [[0, 0], [0, 0]];
+
+    constructor(props: RecommenderProps) {
+        //console.log("creating client recommender...");
+        super(props);
+        ClientRecommender.Instance = this;
+        this.docVectors = new Set<number[]>();
+        //this.corr_matrix = [[0, 0], [0, 0]];
+    }
+
+
+    /***
+     * Computes the cosine similarity between two vectors in Euclidean space. 
+     */
+
+    private distance(vector1: number[], vector2: number[]) {
+        assert(vector1.length === vector2.length, "Vectors are not the same length");
+        var dotproduct = 0;
+        var mA = 0;
+        var mB = 0;
+        for (let i = 0; i < vector1.length; i++) { // here you missed the i++
+            dotproduct += (vector1[i] * vector2[i]);
+            mA += (vector1[i] * vector1[i]);
+            mB += (vector2[i] * vector2[i]);
+        }
+        mA = Math.sqrt(mA);
+        mB = Math.sqrt(mB);
+        var similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets
+        return similarity;
+    }
+
+    /***
+     * Computes the mean of a set of vectors
+     */
+
+    public mean(paragraph: Set<number[]>) {
+        const n = 200;
+        const num_words = paragraph.size;
+        let meanVector = new Array<number>(n).fill(0); // mean vector
+        paragraph.forEach((wordvec: number[]) => {
+            for (let i = 0; i < n; i++) {
+                meanVector[i] += wordvec[i];
+            }
+        });
+        meanVector = meanVector.map(x => x / num_words);
+        this.addToDocSet(meanVector);
+        return meanVector;
+    }
+
+    private addToDocSet(vector: number[]) {
+        if (this.docVectors) {
+            this.docVectors.add(vector);
+        }
+    }
+
+    /***
+     * Uses Cognitive Services to extract keywords from a document
+     */
+
+    public async extractText(dataDoc: Doc, extDoc: Doc) {
+        let data = StrCast(dataDoc.title);
+        //console.log(data);
+        let converter = (results: any) => {
+            let keyterms = new List<string>();
+            results.documents.forEach((doc: any) => {
+                let keyPhrases = doc.keyPhrases;
+                keyPhrases.map((kp: string) => keyterms.push(kp));
+            });
+            return keyterms;
+        };
+        await CognitiveServices.Text.Manager.analyzer(extDoc, ["key words"], data, converter);
+    }
+
+    /***
+     * Creates distance matrix for all Documents analyzed
+     */
+
+    @action
+    public createDistanceMatrix(documents: Set<number[]> = this.docVectors) {
+        //this.corr_matrix[0][0] = 500;
+        this.firstnum = 500;
+        const documents_list = Array.from(documents);
+        const n = documents_list.length;
+        var matrix = new Array<number>(n).fill(0).map(() => new Array<number>(n).fill(0));
+        for (let i = 0; i < n; i++) {
+            var doc1 = documents_list[i];
+            for (let j = 0; j < n; j++) {
+                var doc2 = documents_list[j];
+                matrix[i][j] = this.distance(doc1, doc2);
+            }
+        }
+        //this.corr_matrix = matrix;
+
+        return matrix;
+    }
+
+    @computed get first_num() {
+        return this.firstnum;
+    }
+
+    dumb_reaction = reaction(
+        () => this.first_num,
+        number => {
+            console.log("number has changed", number);
+            this.forceUpdate();
+        }
+    );
+
+    render() {
+        return (<div>
+            <h3>{this.props.title ? this.props.title : "hello"}</h3>
+            <table>
+                <tbody>
+                    <tr>
+                        <td>{this.first_num}</td>
+                        <td>{this.corr_matrix[0][1]}</td>
+                    </tr>
+                    <tr>
+                        <td>{this.corr_matrix[1][0]}</td>
+                        <td>{this.corr_matrix[1][1]}</td>
+                    </tr>
+                </tbody>
+            </table>
+        </div>);
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9344b43d2..5259b9b49 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -523,6 +523,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
     }
 
+    @action
     onContextMenu = (e: React.MouseEvent) => {
         let layoutItems: ContextMenuProps[] = [];
         layoutItems.push({
@@ -600,7 +601,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ContextMenu.Instance.addItem({
             description: "Recommender System",
             event: async () => {
-                new ClientRecommender();
+                // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
                 let activedocs = this.getActiveDocuments();
                 await Promise.all(activedocs.map((doc: Doc) => {
                     console.log(StrCast(doc.title));
@@ -715,6 +716,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
         return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
             {this.props.children}
+            <ClientRecommender title="Correlation Matrix" />
         </div>;
     }
 }
\ No newline at end of file
diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts
index ea59703c3..d175b67c7 100644
--- a/src/server/Recommender.ts
+++ b/src/server/Recommender.ts
@@ -10,6 +10,7 @@ export class Recommender {
 
     private _model: any;
     static Instance: Recommender;
+    private dimension: number = 0;
 
     constructor() {
         console.log("creating recommender...");
@@ -25,6 +26,7 @@ export class Recommender {
         return new Promise(res => {
             w2v.loadModel("./node_modules/word2vec/vectors.txt", function (err: any, model: any) {
                 self._model = model;
+                self.dimension = model.size;
                 res(model);
             });
         });
-- 
cgit v1.2.3-70-g09d2


From 116f218c2eeec377fb465027bcfaa7521d9af492 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Thu, 8 Aug 2019 17:48:58 -0400
Subject: euclidian

---
 src/client/ClientRecommender.scss                  |   8 ++
 src/client/ClientRecommender.tsx                   | 100 +++++++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   3 +-
 3 files changed, 71 insertions(+), 40 deletions(-)
 create mode 100644 src/client/ClientRecommender.scss

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

diff --git a/src/client/ClientRecommender.scss b/src/client/ClientRecommender.scss
new file mode 100644
index 000000000..710d80f34
--- /dev/null
+++ b/src/client/ClientRecommender.scss
@@ -0,0 +1,8 @@
+@import "/views/globalCssVariables.scss";
+
+.space{
+    border: 1px dashed blue;
+    border-spacing: 25px;
+    border-collapse: separate;
+    align-content: center;
+}
\ No newline at end of file
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index 2344ee490..0793f0887 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -6,7 +6,7 @@ import React = require("react");
 import { observer } from "mobx-react";
 import { observable, action, computed, reaction } from "mobx";
 var assert = require('assert');
-import Table from 'react-bootstrap/Table';
+import "./ClientRecommender.scss";
 
 export interface RecommenderProps {
     title: string;
@@ -17,37 +17,53 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
 
     static Instance: ClientRecommender;
     private docVectors: Set<number[]>;
-    private corr_matrix = observable([[0, 0], [0, 0]]);
-    @observable private firstnum = 0;
-    //@observable private corr_matrix: number[][] = [[0, 0], [0, 0]];
+    @observable private corr_matrix = [[0, 0], [0, 0]];
 
     constructor(props: RecommenderProps) {
         //console.log("creating client recommender...");
         super(props);
-        ClientRecommender.Instance = this;
+        if (!ClientRecommender.Instance) ClientRecommender.Instance = this;
         this.docVectors = new Set<number[]>();
         //this.corr_matrix = [[0, 0], [0, 0]];
     }
 
+    @action
+    public reset_docs() {
+        this.docVectors = new Set();
+        this.corr_matrix = [[0, 0], [0, 0]];
+    }
 
     /***
      * Computes the cosine similarity between two vectors in Euclidean space. 
      */
 
-    private distance(vector1: number[], vector2: number[]) {
+    private distance(vector1: number[], vector2: number[], metric: string = "cosine") {
         assert(vector1.length === vector2.length, "Vectors are not the same length");
-        var dotproduct = 0;
-        var mA = 0;
-        var mB = 0;
-        for (let i = 0; i < vector1.length; i++) { // here you missed the i++
-            dotproduct += (vector1[i] * vector2[i]);
-            mA += (vector1[i] * vector1[i]);
-            mB += (vector2[i] * vector2[i]);
+        let similarity: number;
+        switch (metric) {
+            case "cosine":
+                var dotproduct = 0;
+                var mA = 0;
+                var mB = 0;
+                for (let i = 0; i < vector1.length; i++) { // here you missed the i++
+                    dotproduct += (vector1[i] * vector2[i]);
+                    mA += (vector1[i] * vector1[i]);
+                    mB += (vector2[i] * vector2[i]);
+                }
+                mA = Math.sqrt(mA);
+                mB = Math.sqrt(mB);
+                similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets
+                return similarity;
+            case "euclidian":
+                var sum = 0;
+                for (let i = 0; i < vector1.length; i++) {
+                    sum += Math.pow(vector1[i] - vector2[i], 2);
+                }
+                similarity = Math.sqrt(sum);
+                return similarity;
+            default:
+                return 0;
         }
-        mA = Math.sqrt(mA);
-        mB = Math.sqrt(mB);
-        var similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets
-        return similarity;
     }
 
     /***
@@ -98,8 +114,6 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
 
     @action
     public createDistanceMatrix(documents: Set<number[]> = this.docVectors) {
-        //this.corr_matrix[0][0] = 500;
-        this.firstnum = 500;
         const documents_list = Array.from(documents);
         const n = documents_list.length;
         var matrix = new Array<number>(n).fill(0).map(() => new Array<number>(n).fill(0));
@@ -107,40 +121,48 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             var doc1 = documents_list[i];
             for (let j = 0; j < n; j++) {
                 var doc2 = documents_list[j];
-                matrix[i][j] = this.distance(doc1, doc2);
+                matrix[i][j] = this.distance(doc1, doc2, "euclidian");
             }
         }
-        //this.corr_matrix = matrix;
-
+        this.corr_matrix = matrix;
         return matrix;
     }
 
-    @computed get first_num() {
-        return this.firstnum;
-    }
-
-    dumb_reaction = reaction(
-        () => this.first_num,
-        number => {
-            console.log("number has changed", number);
-            this.forceUpdate();
+    @computed
+    private get generateRows() {
+        const n = this.corr_matrix.length;
+        let rows: React.ReactElement[] = [];
+        for (let i = 0; i < n; i++) {
+            let children: React.ReactElement[] = [];
+            for (let j = 0; j < n; j++) {
+                let cell = React.createElement("td", this.corr_matrix[i][j]);
+                children.push(cell);
+            }
+            let row = React.createElement("tr", { children: children });
+            rows.push(row);
         }
-    );
+        return rows;
+    }
 
     render() {
         return (<div>
             <h3>{this.props.title ? this.props.title : "hello"}</h3>
-            <table>
+            {/* <table className="space" >
                 <tbody>
-                    <tr>
-                        <td>{this.first_num}</td>
-                        <td>{this.corr_matrix[0][1]}</td>
+                    <tr key="1">
+                        <td key="1">{this.corr_matrix[0][0].toFixed(4)}</td>
+                        <td key="2">{this.corr_matrix[0][1].toFixed(4)}</td>
                     </tr>
-                    <tr>
-                        <td>{this.corr_matrix[1][0]}</td>
-                        <td>{this.corr_matrix[1][1]}</td>
+                    <tr key="2">
+                        <td key="1">{this.corr_matrix[1][0].toFixed(4)}</td>
+                        <td key="2">{this.corr_matrix[1][1].toFixed(4)}</td>
                     </tr>
                 </tbody>
+            </table> */}
+            <table>
+                <tbody>
+                    {this.generateRows}
+                </tbody>
             </table>
         </div>);
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5259b9b49..2b9f32136 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -603,6 +603,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             event: async () => {
                 // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
                 let activedocs = this.getActiveDocuments();
+                ClientRecommender.Instance.reset_docs();
                 await Promise.all(activedocs.map((doc: Doc) => {
                     console.log(StrCast(doc.title));
                     const extdoc = doc.data_ext as Doc;
@@ -716,7 +717,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
         return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
             {this.props.children}
-            <ClientRecommender title="Correlation Matrix" />
+            <ClientRecommender title="Distance Matrix" />
         </div>;
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From b8ab5a823ae22b021c09dfd713b77211a51b3eae Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Fri, 9 Aug 2019 17:04:25 -0400
Subject: idk man

---
 solr-8.1.1/server/solr/dash/conf/schema.xml         |  2 +-
 src/client/ClientRecommender.scss                   |  4 ++++
 src/client/ClientRecommender.tsx                    | 17 ++++++++++-------
 src/client/util/SearchUtil.ts                       | 21 ++++++++++++++++++++-
 src/client/views/SearchBox.tsx                      |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx   |  6 +++++-
 src/server/Search.ts                                |  2 +-
 7 files changed, 42 insertions(+), 12 deletions(-)

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

diff --git a/solr-8.1.1/server/solr/dash/conf/schema.xml b/solr-8.1.1/server/solr/dash/conf/schema.xml
index 8610786af..c0a4bab07 100644
--- a/solr-8.1.1/server/solr/dash/conf/schema.xml
+++ b/solr-8.1.1/server/solr/dash/conf/schema.xml
@@ -52,7 +52,7 @@
   <dynamicField name="*_i" type="string" indexed="true" stored="true" uninvertible="false" docValues="false"/>
   <dynamicField name="*_b" type="boolean" indexed="true" stored="true" uninvertible="false" docValues="false"/>
 
-  <dynamicField name="*_a" type="text" indexed="true" stored="false" uninvertible="false" docValues="false"/>
+  <dynamicField name="*_a" type="text" indexed="true" stored="false" uninvertible="false" docValues="false" multiValued="true"/>
 
   <copyField source="*_t" dest="text"/>
 
diff --git a/src/client/ClientRecommender.scss b/src/client/ClientRecommender.scss
index 710d80f34..49163cdc8 100644
--- a/src/client/ClientRecommender.scss
+++ b/src/client/ClientRecommender.scss
@@ -5,4 +5,8 @@
     border-spacing: 25px;
     border-collapse: separate;
     align-content: center;
+}
+
+.wrapper{
+    text-align: -webkit-center;
 }
\ No newline at end of file
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index 0793f0887..ddaa8a7fc 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -7,6 +7,7 @@ import { observer } from "mobx-react";
 import { observable, action, computed, reaction } from "mobx";
 var assert = require('assert');
 import "./ClientRecommender.scss";
+import { JSXElement } from "babel-types";
 
 export interface RecommenderProps {
     title: string;
@@ -131,22 +132,24 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
     @computed
     private get generateRows() {
         const n = this.corr_matrix.length;
-        let rows: React.ReactElement[] = [];
+        let rows: JSX.Element[] = [];
         for (let i = 0; i < n; i++) {
-            let children: React.ReactElement[] = [];
+            let children: JSX.Element[] = [];
             for (let j = 0; j < n; j++) {
-                let cell = React.createElement("td", this.corr_matrix[i][j]);
+                //let cell = React.createElement("td", this.corr_matrix[i][j]);
+                let cell = <td>{this.corr_matrix[i][j].toFixed(4)}</td>;
                 children.push(cell);
             }
-            let row = React.createElement("tr", { children: children });
+            //let row = React.createElement("tr", { children: children, key: i });
+            let row = <tr>{children}</tr>;
             rows.push(row);
         }
         return rows;
     }
 
     render() {
-        return (<div>
-            <h3>{this.props.title ? this.props.title : "hello"}</h3>
+        return (<div className="wrapper">
+            <h3 >{this.props.title ? this.props.title : "hello"}</h3>
             {/* <table className="space" >
                 <tbody>
                     <tr key="1">
@@ -159,7 +162,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
                     </tr>
                 </tbody>
             </table> */}
-            <table>
+            <table className="space">
                 <tbody>
                     {this.generateRows}
                 </tbody>
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index ee5a83710..3a3ba1803 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -77,4 +77,23 @@ export namespace SearchUtil {
         aliasContexts.forEach(result => contexts.aliasContexts.push(...result.ids));
         return contexts;
     }
-}
\ No newline at end of file
+
+    export async function GetAllDocs() {
+        const query = "*";
+        let response = await rp.get(Utils.prepend('/search'), {
+            qs: {
+                query
+            }
+        });
+        let res: string[] = JSON.parse(response);
+        const fields = await DocServer.GetRefFields(res);
+        const docs: Doc[] = [];
+        for (const id of res) {
+            const field = fields[id];
+            if (field instanceof Doc) {
+                docs.push(field);
+            }
+        }
+        return docs;
+    }
+}
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 33cb63df5..17f99fa05 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -47,7 +47,7 @@ export class SearchBox extends React.Component {
     }
 
     @action
-    getResults = async (query: string) => {
+    public getResults = async (query: string) => {
         let response = await rp.get(Utils.prepend('/search'), {
             qs: {
                 query
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2b9f32136..0beb0086b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -38,6 +38,8 @@ import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
 import { ClientRecommender } from "../../../ClientRecommender";
+import { SearchUtil } from "../../../util/SearchUtil";
+import { SearchBox } from "../../SearchBox";
 
 library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBrain);
 
@@ -603,9 +605,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             event: async () => {
                 // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
                 let activedocs = this.getActiveDocuments();
+                let allDocs = await SearchUtil.GetAllDocs();
+                allDocs.forEach(doc => console.log(doc.title));
                 ClientRecommender.Instance.reset_docs();
                 await Promise.all(activedocs.map((doc: Doc) => {
-                    console.log(StrCast(doc.title));
+                    //console.log(StrCast(doc.title));
                     const extdoc = doc.data_ext as Doc;
                     return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
                 }));
diff --git a/src/server/Search.ts b/src/server/Search.ts
index 723dc101b..44c1b1298 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -14,7 +14,7 @@ export class Search {
             });
             return res;
         } catch (e) {
-            // console.warn("Search error: " + e + document);
+            console.warn("Search error: " + e + document);
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 9dd2a31b72e5e527e2dae3b68f856ab8da879e93 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Mon, 12 Aug 2019 16:41:23 -0400
Subject: documentation

---
 package.json                                       |  2 +-
 src/client/ClientRecommender.tsx                   | 18 +++++-----
 src/client/cognitive_services/CognitiveServices.ts | 42 ++++++++++++----------
 src/client/util/SearchUtil.ts                      | 13 ++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 src/server/Recommender.ts                          |  1 +
 6 files changed, 45 insertions(+), 32 deletions(-)

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

diff --git a/package.json b/package.json
index 1e2c74411..1c7a10ac8 100644
--- a/package.json
+++ b/package.json
@@ -225,4 +225,4 @@
         "xoauth2": "^1.2.0",
         "youtube": "^0.1.0"
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index ddaa8a7fc..63f85c737 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -75,13 +75,15 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
         const n = 200;
         const num_words = paragraph.size;
         let meanVector = new Array<number>(n).fill(0); // mean vector
-        paragraph.forEach((wordvec: number[]) => {
-            for (let i = 0; i < n; i++) {
-                meanVector[i] += wordvec[i];
-            }
-        });
-        meanVector = meanVector.map(x => x / num_words);
-        this.addToDocSet(meanVector);
+        if (num_words > 0) { // check to see if paragraph actually was vectorized
+            paragraph.forEach((wordvec: number[]) => {
+                for (let i = 0; i < n; i++) {
+                    meanVector[i] += wordvec[i];
+                }
+            });
+            meanVector = meanVector.map(x => x / num_words);
+            this.addToDocSet(meanVector);
+        }
         return meanVector;
     }
 
@@ -106,7 +108,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             });
             return keyterms;
         };
-        await CognitiveServices.Text.Manager.analyzer(extDoc, ["key words"], data, converter);
+        await CognitiveServices.Text.Appliers.analyzer(extDoc, ["key words"], data, converter);
     }
 
     /***
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 954a05585..75d0760ed 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -258,32 +258,38 @@ export namespace CognitiveServices {
                 };
                 console.log("requested!");
                 return request.post(options);
-            },
-            analyzer: async (target: Doc, keys: string[], data: string, converter: Converter) => {
-                let results = await ExecuteQuery<string, any>(Service.Text, Manager, data);
+            }
+        };
+
+        export namespace Appliers {
+
+            export async function vectorize(keyterms: any) {
+                console.log("vectorizing...");
+                //keyterms = ["father", "king"];
+                let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
+                await requestPromise.post(args).then(async (wordvecs) => {
+                    var vectorValues = new Set<number[]>();
+                    wordvecs.forEach((wordvec: any) => {
+                        //console.log(wordvec.word);
+                        vectorValues.add(wordvec.values as number[]);
+                    });
+                    ClientRecommender.Instance.mean(vectorValues);
+                    //console.log(vectorValues.size);
+                });
+            }
+
+            export const analyzer = async (target: Doc, keys: string[], data: string, converter: Converter) => {
+                let results = await ExecuteQuery(Service.Text, Manager, data);
                 console.log(results);
                 let keyterms = converter(results);
                 //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis");
                 target[keys[0]] = keyterms;
                 console.log("analyzed!");
                 await vectorize(keyterms);
-            }
-        };
-        async function vectorize(keyterms: any) {
-            console.log("vectorizing...");
-            //keyterms = ["father", "king"];
-            let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
-            await requestPromise.post(args).then(async (wordvecs) => {
-                var vectorValues = new Set<number[]>();
-                wordvecs.forEach((wordvec: any) => {
-                    //console.log(wordvec.word);
-                    vectorValues.add(wordvec.values as number[]);
-                });
-                ClientRecommender.Instance.mean(vectorValues);
-                //console.log(vectorValues.size);
-            });
+            };
         }
 
     }
 
+
 }
\ No newline at end of file
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 3a3ba1803..1fce995d7 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -82,18 +82,21 @@ export namespace SearchUtil {
         const query = "*";
         let response = await rp.get(Utils.prepend('/search'), {
             qs: {
-                query
+                q: query
             }
         });
-        let res: string[] = JSON.parse(response);
-        const fields = await DocServer.GetRefFields(res);
+        let result: IdSearchResult = JSON.parse(response);
+        const { ids, numFound, highlighting } = result;
+        const docMap = await DocServer.GetRefFields(ids);
         const docs: Doc[] = [];
-        for (const id of res) {
-            const field = fields[id];
+        for (const id of ids) {
+            const field = docMap[id];
             if (field instanceof Doc) {
                 docs.push(field);
             }
         }
         return docs;
+        // const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc);
+        // return docs as Doc[];
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e9791df4e..d1e8031fd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -894,6 +894,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 let activedocs = this.getActiveDocuments();
                 let allDocs = await SearchUtil.GetAllDocs();
                 allDocs.forEach(doc => console.log(doc.title));
+                // clears internal representation of documents as vectors
                 ClientRecommender.Instance.reset_docs();
                 await Promise.all(activedocs.map((doc: Doc) => {
                     //console.log(StrCast(doc.title));
diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts
index d175b67c7..1c95d7ea4 100644
--- a/src/server/Recommender.ts
+++ b/src/server/Recommender.ts
@@ -70,6 +70,7 @@ export class Recommender {
         }
         if (this._model) {
             let word_vecs = this._model.getVectors(text);
+
             return word_vecs;
         }
     }
-- 
cgit v1.2.3-70-g09d2


From e03a1b2cc90e0fdb7789f4826e482e9040aa7075 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Tue, 13 Aug 2019 17:26:49 -0400
Subject: 80% done, garbage collection is much needed

---
 src/Recommendations.scss                           |  21 ---
 src/Recommendations.tsx                            |  28 ----
 src/client/util/SearchUtil.ts                      |   7 +-
 src/client/views/MainView.tsx                      |   2 +
 src/client/views/Recommendations.scss              |  65 ++++++++
 src/client/views/Recommendations.tsx               | 169 +++++++++++++++++++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  13 +-
 src/client/views/nodes/DocumentView.tsx            |  34 ++++-
 src/client/views/nodes/ImageBox.tsx                |  21 ++-
 9 files changed, 297 insertions(+), 63 deletions(-)
 delete mode 100644 src/Recommendations.scss
 delete mode 100644 src/Recommendations.tsx
 create mode 100644 src/client/views/Recommendations.scss
 create mode 100644 src/client/views/Recommendations.tsx

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

diff --git a/src/Recommendations.scss b/src/Recommendations.scss
deleted file mode 100644
index 5129a59d9..000000000
--- a/src/Recommendations.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-.recommendation-content *{
-    display: inline-block;
-    margin: auto;
-    border: 1px dashed grey;
-    padding: 2px 2px;
-}
-
-.recommendation-content {
-    float: left;
-    border: 1px solid green;
-    width: 200px;
-    align-content: center;
-}
-
-.rec-scroll {
-    overflow-y: scroll;
-    height: 300px; 
-    width: auto;
-    position: absolute;
-    background: #cdcdcd;
-}
\ No newline at end of file
diff --git a/src/Recommendations.tsx b/src/Recommendations.tsx
deleted file mode 100644
index ca1123ef9..000000000
--- a/src/Recommendations.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { observer } from "mobx-react";
-import React = require("react");
-import { Doc } from "./new_fields/Doc";
-import { NumCast } from "./new_fields/Types";
-
-export interface RecProps {
-    documents: { preview: string, similarity: number }[],
-    node: Doc;
-}
-
-@observer
-export class Recommendations extends React.Component<RecProps> {
-    render() {
-        const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px"
-        return (
-            <div className="rec-scroll" style={{ transform: transform }}>
-                {this.props.documents.map(doc => {
-                    return (
-                        <div className="recommendation-content">
-                            <img src={doc.preview} />
-                            <div>{doc.similarity}</div>
-                        </div>
-                    )
-                })}
-            </div>
-        )
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 1fce995d7..85e593529 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -81,12 +81,13 @@ export namespace SearchUtil {
     export async function GetAllDocs() {
         const query = "*";
         let response = await rp.get(Utils.prepend('/search'), {
-            qs: {
-                q: query
-            }
+            qs:
+                { start: 0, rows: 10000, q: query },
+
         });
         let result: IdSearchResult = JSON.parse(response);
         const { ids, numFound, highlighting } = result;
+        console.log(ids.length);
         const docMap = await DocServer.GetRefFields(ids);
         const docs: Doc[] = [];
         for (const id of ids) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 419b15697..0b6fe3876 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -41,6 +41,7 @@ import { ClientUtils } from '../util/ClientUtils';
 import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField';
 //import { DocumentManager } from '../util/DocumentManager';
 import { DictationManager } from '../util/DictationManager';
+import { Recommendations } from './Recommendations';
 
 @observer
 export class MainView extends React.Component {
@@ -581,6 +582,7 @@ export class MainView extends React.Component {
                 {this.mainContent}
                 <PreviewCursor />
                 <ContextMenu />
+                <Recommendations />
                 {this.nodesMenu()}
                 {this.miscButtons}
                 <PDFMenu />
diff --git a/src/client/views/Recommendations.scss b/src/client/views/Recommendations.scss
new file mode 100644
index 000000000..5d8f17e37
--- /dev/null
+++ b/src/client/views/Recommendations.scss
@@ -0,0 +1,65 @@
+@import "globalCssVariables";
+
+.rec-content *{
+    display: inline-block;
+    margin: auto;
+    width: 50;
+    height: 30px;
+    border: 1px dashed grey;
+    padding: 10px 10px;
+}
+
+.rec-content {
+    float: left;
+    width: inherit;
+    align-content: center;
+}
+
+.rec-scroll {
+    overflow-y: scroll;
+    overflow-x: hidden;
+    position: absolute;
+    // display: flex;
+    z-index: 10000;
+    box-shadow: gray 0.2vw 0.2vw 0.4vw;
+    // flex-direction: column;
+    background: whitesmoke;
+    padding-bottom: 10px;
+    border-radius: 15px;
+    border: solid #BBBBBBBB 1px;
+    width: 200px;
+    text-align: center;
+    max-height: 250px;
+    text-transform: uppercase;
+    color: grey;
+    letter-spacing: 2px;
+}
+
+.content {
+    padding: 10px;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: center;
+}
+
+.image-background {
+    pointer-events: none;
+    background-color: transparent;
+    width: 50%;
+    text-align: center;
+    height: 35px;
+    margin-left: 5px;
+}
+
+img{
+    width: 100%;
+    height: 100%;
+}
+
+.score {
+    // margin-left: 15px;
+    width: 50%;
+    height: 100%;
+    text-align: center;
+}
diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx
new file mode 100644
index 000000000..8569996b3
--- /dev/null
+++ b/src/client/views/Recommendations.tsx
@@ -0,0 +1,169 @@
+import { observer } from "mobx-react";
+import React = require("react");
+import { observable, action } from "mobx";
+import Measure from "react-measure";
+import "./Recommendations.scss";
+import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc";
+import { DocumentIcon } from "./nodes/DocumentIcon";
+import { StrCast, NumCast } from "../../new_fields/Types";
+import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../Utils";
+import { Transform } from "../util/Transform";
+import { ObjectField } from "../../new_fields/ObjectField";
+import { DocumentView } from "./nodes/DocumentView";
+import { DocumentType } from "../documents/Documents";
+
+
+export interface RecProps {
+    documents: { preview: Doc, similarity: number }[];
+    node: Doc;
+}
+
+@observer
+export class Recommendations extends React.Component<{}> {
+
+    static Instance: Recommendations;
+    @observable private _display: boolean = false;
+    @observable private _pageX: number = 0;
+    @observable private _pageY: number = 0;
+    @observable private _width: number = 0;
+    @observable private _height: number = 0;
+    @observable private _documents: { preview: Doc, score: number }[] = [];
+
+    constructor(props: {}) {
+        super(props);
+        Recommendations.Instance = this;
+    }
+
+    private DocumentIcon(doc: Doc) {
+        let layoutresult = StrCast(doc.type);
+        let renderDoc = doc;
+        //let box: number[] = [];
+        if (layoutresult.indexOf(DocumentType.COL) !== -1) {
+            renderDoc = Doc.MakeDelegate(renderDoc);
+            let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => {
+                var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
+                let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
+                return {
+                    x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
+                    r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
+                };
+            }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
+        }
+        let returnXDimension = () => 50;
+        let returnYDimension = () => 50;
+        let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension());
+        let newRenderDoc = Doc.MakeDelegate(renderDoc); ///   newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
+        const docview = <div>
+            {/* onPointerDown={action(() => {
+                this._useIcons = !this._useIcons;
+                this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE);
+            })}
+            onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))}
+            onPointerLeave={action(() => this._displayDim = 50)} > */}
+            <DocumentView
+                fitToBox={StrCast(doc.type).indexOf(DocumentType.COL) !== -1}
+                Document={doc}
+                addDocument={returnFalse}
+                removeDocument={returnFalse}
+                ScreenToLocalTransform={Transform.Identity}
+                addDocTab={returnFalse}
+                renderDepth={1}
+                PanelWidth={returnXDimension}
+                PanelHeight={returnYDimension}
+                focus={emptyFunction}
+                backgroundColor={returnEmptyString}
+                selectOnLoad={false}
+                parentActive={returnFalse}
+                whenActiveChanged={returnFalse}
+                bringToFront={emptyFunction}
+                zoomToScale={emptyFunction}
+                getScale={returnOne}
+                ContainingCollectionView={undefined}
+                ContentScaling={scale}
+            />
+        </div>;
+        const data = renderDoc.data;
+        if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data);
+        newRenderDoc.preview = true;
+        return docview;
+
+    }
+
+    @action
+    closeMenu = () => {
+        this._display = false;
+    }
+
+    @action
+    resetDocuments = () => {
+        this._documents = [];
+    }
+
+    @action
+    addDocuments = (documents: { preview: Doc, score: number }[]) => {
+        this._documents = documents;
+    }
+
+    @action
+    displayRecommendations(x: number, y: number) {
+        this._pageX = x;
+        this._pageY = y;
+        this._display = true;
+    }
+
+    static readonly buffer = 20;
+
+    get pageX() {
+        const x = this._pageX;
+        if (x < 0) {
+            return 0;
+        }
+        const width = this._width;
+        if (x + width > window.innerWidth - Recommendations.buffer) {
+            return window.innerWidth - Recommendations.buffer - width;
+        }
+        return x;
+    }
+
+    get pageY() {
+        const y = this._pageY;
+        if (y < 0) {
+            return 0;
+        }
+        const height = this._height;
+        if (y + height > window.innerHeight - Recommendations.buffer) {
+            return window.innerHeight - Recommendations.buffer - height;
+        }
+        return y;
+    }
+
+    render() {
+        if (!this._display) {
+            return null;
+        }
+        let style = { left: this.pageX, top: this.pageY };
+        //const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px"
+        return (
+            <Measure offset onResize={action((r: any) => { this._width = r.offset.width; this._height = r.offset.height; })}>
+                {({ measureRef }) => (
+                    <div className="rec-scroll" style={style} ref={measureRef}>
+                        <p>Recommendations</p>
+                        {this._documents.map(doc => {
+                            return (
+                                <div className="content">
+                                    <span className="image-background">
+                                        {this.DocumentIcon(doc.preview)}
+                                    </span>
+                                    <span className="score">{doc.score}</span>
+                                </div>
+                            );
+                        })}
+
+                    </div>
+                )
+                }
+
+            </Measure>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d1e8031fd..50f7e2dc8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -896,10 +896,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 allDocs.forEach(doc => console.log(doc.title));
                 // clears internal representation of documents as vectors
                 ClientRecommender.Instance.reset_docs();
-                await Promise.all(activedocs.map((doc: Doc) => {
-                    //console.log(StrCast(doc.title));
-                    const extdoc = doc.data_ext as Doc;
-                    return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+                await Promise.all(allDocs.map((doc: Doc) => {
+                    console.log(StrCast(doc.title));
+                    if (doc.type === DocumentType.IMG) {
+                        console.log(doc.title);
+                        const extdoc = doc.data_ext as Doc;
+                        return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+                    }
                 }));
                 console.log(ClientRecommender.Instance.createDistanceMatrix());
             },
@@ -1013,7 +1016,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
         return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
             {this.props.children}
-            <ClientRecommender title="Distance Matrix" />
+            {/* <ClientRecommender title="Distance Matrix" /> */}
         </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 24bcc0217..3ce4dbd4f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -40,9 +40,13 @@ import React = require("react");
 import { DictationManager } from '../../util/DictationManager';
 import { MainView } from '../MainView';
 import requestPromise = require('request-promise');
-import { Recommendations } from '../../../Recommendations';
+import { Recommendations } from '../Recommendations';
+import { SearchUtil } from '../../util/SearchUtil';
+import { ClientRecommender } from '../../ClientRecommender';
+import { DocumentType } from '../../documents/Documents';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
+library.add(fa.faBrain);
 library.add(fa.faTrash);
 library.add(fa.faShare);
 library.add(fa.faDownload);
@@ -610,6 +614,33 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 a.click();
             }
         });
+        cm.addItem({
+            description: "Recommender System",
+            event: async () => {
+                if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
+                let documents: Doc[] = [];
+                let allDocs = await SearchUtil.GetAllDocs();
+                allDocs.forEach(doc => console.log(doc.title));
+                // clears internal representation of documents as vectors
+                ClientRecommender.Instance.reset_docs();
+                await Promise.all(allDocs.map((doc: Doc) => {
+                    if (doc.type === DocumentType.IMG) {
+                        console.log(StrCast(doc.title));
+                        documents.push(doc);
+                        const extdoc = doc.data_ext as Doc;
+                        return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+                    }
+                }));
+                console.log(ClientRecommender.Instance.createDistanceMatrix());
+                let recDocs: { preview: Doc, score: number }[] = [];
+                for (let i = 0; i < documents.length; i++) {
+                    recDocs.push({ preview: documents[i], score: i });
+                }
+                Recommendations.Instance.addDocuments(recDocs);
+                Recommendations.Instance.displayRecommendations(e.pageX + 100, e.pageY);
+            },
+            icon: "brain"
+        });
         cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         type User = { email: string, userDocumentId: string };
         let usersMenu: ContextMenuProps[] = [];
@@ -758,7 +789,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         </div>
                     }
                 </div>
-                <Recommendations documents={documents} node={this.props.Document}></Recommendations>
             </div>
         );
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 73b892e26..45d389ba6 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -28,6 +28,9 @@ import FaceRectangles from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
+import { SearchUtil } from '../../util/SearchUtil';
+import { ClientRecommender } from '../../ClientRecommender';
+import { DocumentType } from '../../documents/Documents';
 var requestImageSize = require('../../util/request-image-size');
 var path = require('path');
 const { Howl } = require('howler');
@@ -240,10 +243,20 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         }
     }
 
-    extractText = () => {
-        //Recommender.Instance.extractText(this.dataDoc, this.extensionDoc);
-        // request recommender 
-        //fetch(Utils.prepend("/recommender"), { body: body, method: "POST", headers: { "content-type": "application/json" } }).then((value) => console.log(value));
+    extractText = async () => {
+        //let activedocs = this.getActiveDocuments();
+        let allDocs = await SearchUtil.GetAllDocs();
+        allDocs.forEach(doc => console.log(doc.title));
+        // clears internal representation of documents as vectors
+        ClientRecommender.Instance.reset_docs();
+        await Promise.all(allDocs.map((doc: Doc) => {
+            //console.log(StrCast(doc.title));
+            if (doc.type === DocumentType.IMG) {
+                const extdoc = doc.data_ext as Doc;
+                return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+            }
+        }));
+        console.log(ClientRecommender.Instance.createDistanceMatrix());
     }
 
     generateMetadata = (threshold: Confidence = Confidence.Excellent) => {
-- 
cgit v1.2.3-70-g09d2


From e0bfe978e029268b3901b5d098f946b1a6fc7d0d Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Thu, 29 Aug 2019 18:43:32 -0400
Subject: ui fixes, datadoc resolved

---
 src/client/ClientRecommender.tsx                   | 83 +++++++++++++++-------
 src/client/cognitive_services/CognitiveServices.ts | 24 ++++---
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/Recommendations.tsx               | 21 ++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 21 ------
 src/client/views/nodes/DocumentView.tsx            | 26 ++++---
 src/client/views/nodes/ImageBox.tsx                | 16 -----
 7 files changed, 104 insertions(+), 88 deletions(-)

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

diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index 63f85c737..a6d1a32b3 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -1,5 +1,5 @@
 import { Doc } from "../new_fields/Doc";
-import { StrCast } from "../new_fields/Types";
+import { StrCast, Cast } from "../new_fields/Types";
 import { List } from "../new_fields/List";
 import { CognitiveServices } from "./cognitive_services/CognitiveServices";
 import React = require("react");
@@ -8,30 +8,42 @@ import { observable, action, computed, reaction } from "mobx";
 var assert = require('assert');
 import "./ClientRecommender.scss";
 import { JSXElement } from "babel-types";
+import { ToPlainText, RichTextField } from "../new_fields/RichTextField";
 
 export interface RecommenderProps {
     title: string;
 }
 
+export interface RecommenderDocument {
+    actualDoc: Doc;
+    vectorDoc: number[];
+    score: number;
+}
+
 @observer
 export class ClientRecommender extends React.Component<RecommenderProps> {
 
     static Instance: ClientRecommender;
-    private docVectors: Set<number[]>;
+    private mainDoc?: RecommenderDocument;
+    private docVectors: Set<RecommenderDocument> = new Set();
     @observable private corr_matrix = [[0, 0], [0, 0]];
 
     constructor(props: RecommenderProps) {
         //console.log("creating client recommender...");
         super(props);
         if (!ClientRecommender.Instance) ClientRecommender.Instance = this;
-        this.docVectors = new Set<number[]>();
-        //this.corr_matrix = [[0, 0], [0, 0]];
+        ClientRecommender.Instance.docVectors = new Set();
+        //ClientRecommender.Instance.corr_matrix = [[0, 0], [0, 0]];
     }
 
     @action
     public reset_docs() {
-        this.docVectors = new Set();
-        this.corr_matrix = [[0, 0], [0, 0]];
+        ClientRecommender.Instance.docVectors = new Set();
+        ClientRecommender.Instance.corr_matrix = [[0, 0], [0, 0]];
+    }
+
+    public deleteDocs() {
+        console.log("deleting previews...");
     }
 
     /***
@@ -67,11 +79,24 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
         }
     }
 
+    public computeSimilarities() {
+        ClientRecommender.Instance.docVectors.forEach((doc: RecommenderDocument) => {
+            if (ClientRecommender.Instance.mainDoc) {
+                const distance = ClientRecommender.Instance.distance(ClientRecommender.Instance.mainDoc.vectorDoc, doc.vectorDoc, "euclidian");
+                doc.score = distance;
+            }
+        }
+        );
+        let doclist = Array.from(ClientRecommender.Instance.docVectors);
+        doclist.sort((a: RecommenderDocument, b: RecommenderDocument) => a.score - b.score);
+        return doclist;
+    }
+
     /***
      * Computes the mean of a set of vectors
      */
 
-    public mean(paragraph: Set<number[]>) {
+    public mean(paragraph: Set<number[]>, dataDoc: Doc, mainDoc: boolean) {
         const n = 200;
         const num_words = paragraph.size;
         let meanVector = new Array<number>(n).fill(0); // mean vector
@@ -82,14 +107,16 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
                 }
             });
             meanVector = meanVector.map(x => x / num_words);
-            this.addToDocSet(meanVector);
+            const internalDoc: RecommenderDocument = { actualDoc: dataDoc, vectorDoc: meanVector, score: 0 };
+            if (mainDoc) ClientRecommender.Instance.mainDoc = internalDoc;
+            ClientRecommender.Instance.addToDocSet(internalDoc);
         }
         return meanVector;
     }
 
-    private addToDocSet(vector: number[]) {
-        if (this.docVectors) {
-            this.docVectors.add(vector);
+    private addToDocSet(internalDoc: RecommenderDocument) {
+        if (ClientRecommender.Instance.docVectors) {
+            ClientRecommender.Instance.docVectors.add(internalDoc);
         }
     }
 
@@ -97,9 +124,11 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
      * Uses Cognitive Services to extract keywords from a document
      */
 
-    public async extractText(dataDoc: Doc, extDoc: Doc) {
-        let data = StrCast(dataDoc.title);
-        //console.log(data);
+    public async extractText(dataDoc: Doc, extDoc: Doc, mainDoc: boolean = false) {
+        let fielddata = Cast(dataDoc.data, RichTextField);
+        let data: string;
+        fielddata ? data = fielddata[ToPlainText]() : data = "";
+        console.log(data);
         let converter = (results: any) => {
             let keyterms = new List<string>();
             results.documents.forEach((doc: any) => {
@@ -108,7 +137,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             });
             return keyterms;
         };
-        await CognitiveServices.Text.Appliers.analyzer(extDoc, ["key words"], data, converter);
+        await CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, mainDoc);
     }
 
     /***
@@ -116,7 +145,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
      */
 
     @action
-    public createDistanceMatrix(documents: Set<number[]> = this.docVectors) {
+    public createDistanceMatrix(documents: Set<RecommenderDocument> = ClientRecommender.Instance.docVectors) {
         const documents_list = Array.from(documents);
         const n = documents_list.length;
         var matrix = new Array<number>(n).fill(0).map(() => new Array<number>(n).fill(0));
@@ -124,22 +153,22 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             var doc1 = documents_list[i];
             for (let j = 0; j < n; j++) {
                 var doc2 = documents_list[j];
-                matrix[i][j] = this.distance(doc1, doc2, "euclidian");
+                matrix[i][j] = ClientRecommender.Instance.distance(doc1.vectorDoc, doc2.vectorDoc, "euclidian");
             }
         }
-        this.corr_matrix = matrix;
+        ClientRecommender.Instance.corr_matrix = matrix;
         return matrix;
     }
 
     @computed
     private get generateRows() {
-        const n = this.corr_matrix.length;
+        const n = ClientRecommender.Instance.corr_matrix.length;
         let rows: JSX.Element[] = [];
         for (let i = 0; i < n; i++) {
             let children: JSX.Element[] = [];
             for (let j = 0; j < n; j++) {
-                //let cell = React.createElement("td", this.corr_matrix[i][j]);
-                let cell = <td>{this.corr_matrix[i][j].toFixed(4)}</td>;
+                //let cell = React.createElement("td", ClientRecommender.Instance.corr_matrix[i][j]);
+                let cell = <td>{ClientRecommender.Instance.corr_matrix[i][j].toFixed(4)}</td>;
                 children.push(cell);
             }
             //let row = React.createElement("tr", { children: children, key: i });
@@ -151,22 +180,22 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
 
     render() {
         return (<div className="wrapper">
-            <h3 >{this.props.title ? this.props.title : "hello"}</h3>
+            <h3 >{ClientRecommender.Instance.props.title ? ClientRecommender.Instance.props.title : "hello"}</h3>
             {/* <table className="space" >
                 <tbody>
                     <tr key="1">
-                        <td key="1">{this.corr_matrix[0][0].toFixed(4)}</td>
-                        <td key="2">{this.corr_matrix[0][1].toFixed(4)}</td>
+                        <td key="1">{ClientRecommender.Instance.corr_matrix[0][0].toFixed(4)}</td>
+                        <td key="2">{ClientRecommender.Instance.corr_matrix[0][1].toFixed(4)}</td>
                     </tr>
                     <tr key="2">
-                        <td key="1">{this.corr_matrix[1][0].toFixed(4)}</td>
-                        <td key="2">{this.corr_matrix[1][1].toFixed(4)}</td>
+                        <td key="1">{ClientRecommender.Instance.corr_matrix[1][0].toFixed(4)}</td>
+                        <td key="2">{ClientRecommender.Instance.corr_matrix[1][1].toFixed(4)}</td>
                     </tr>
                 </tbody>
             </table> */}
             <table className="space">
                 <tbody>
-                    {this.generateRows}
+                    {ClientRecommender.Instance.generateRows}
                 </tbody>
             </table>
         </div>);
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 75d0760ed..874ee433d 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -263,29 +263,35 @@ export namespace CognitiveServices {
 
         export namespace Appliers {
 
-            export async function vectorize(keyterms: any) {
+            export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false) {
                 console.log("vectorizing...");
                 //keyterms = ["father", "king"];
                 let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
                 await requestPromise.post(args).then(async (wordvecs) => {
-                    var vectorValues = new Set<number[]>();
-                    wordvecs.forEach((wordvec: any) => {
-                        //console.log(wordvec.word);
-                        vectorValues.add(wordvec.values as number[]);
-                    });
-                    ClientRecommender.Instance.mean(vectorValues);
+                    if (wordvecs.length > 0) {
+                        console.log("successful vectorization!");
+                        var vectorValues = new Set<number[]>();
+                        wordvecs.forEach((wordvec: any) => {
+                            //console.log(wordvec.word);
+                            vectorValues.add(wordvec.values as number[]);
+                        });
+                        ClientRecommender.Instance.mean(vectorValues, dataDoc, mainDoc);
+                    } // adds document to internal doc set
+                    else {
+                        console.log("unsuccessful :( word(s) not in vocabulary");
+                    }
                     //console.log(vectorValues.size);
                 });
             }
 
-            export const analyzer = async (target: Doc, keys: string[], data: string, converter: Converter) => {
+            export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: Converter, mainDoc: boolean = false) => {
                 let results = await ExecuteQuery(Service.Text, Manager, data);
                 console.log(results);
                 let keyterms = converter(results);
                 //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis");
                 target[keys[0]] = keyterms;
                 console.log("analyzed!");
-                await vectorize(keyterms);
+                await vectorize(keyterms, dataDoc, mainDoc);
             };
         }
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 57eb30439..3a5795077 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -204,6 +204,7 @@ export class MainView extends React.Component {
         const targets = document.elementsFromPoint(e.x, e.y);
         if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) {
             ContextMenu.Instance.closeMenu();
+            Recommendations.Instance.closeMenu();
         }
     });
 
diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx
index 8569996b3..cf1974c69 100644
--- a/src/client/views/Recommendations.tsx
+++ b/src/client/views/Recommendations.tsx
@@ -10,8 +10,10 @@ import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../
 import { Transform } from "../util/Transform";
 import { ObjectField } from "../../new_fields/ObjectField";
 import { DocumentView } from "./nodes/DocumentView";
-import { DocumentType } from "../documents/Documents";
-
+import { DocumentType } from '../documents/DocumentTypes';
+import { ClientRecommender } from "../ClientRecommender";
+import { DocServer } from "../DocServer";
+import { Id } from "../../new_fields/FieldSymbols";
 
 export interface RecProps {
     documents: { preview: Doc, similarity: number }[];
@@ -28,6 +30,7 @@ export class Recommendations extends React.Component<{}> {
     @observable private _width: number = 0;
     @observable private _height: number = 0;
     @observable private _documents: { preview: Doc, score: number }[] = [];
+    private previewDocs: Doc[] = [];
 
     constructor(props: {}) {
         super(props);
@@ -52,7 +55,8 @@ export class Recommendations extends React.Component<{}> {
         let returnXDimension = () => 50;
         let returnYDimension = () => 50;
         let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension());
-        let newRenderDoc = Doc.MakeDelegate(renderDoc); ///   newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
+        //let scale = () => 1;
+        //let newRenderDoc = Doc.MakeDelegate(renderDoc); ///   newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
         const docview = <div>
             {/* onPointerDown={action(() => {
                 this._useIcons = !this._useIcons;
@@ -62,7 +66,7 @@ export class Recommendations extends React.Component<{}> {
             onPointerLeave={action(() => this._displayDim = 50)} > */}
             <DocumentView
                 fitToBox={StrCast(doc.type).indexOf(DocumentType.COL) !== -1}
-                Document={doc}
+                Document={renderDoc}
                 addDocument={returnFalse}
                 removeDocument={returnFalse}
                 ScreenToLocalTransform={Transform.Identity}
@@ -82,9 +86,10 @@ export class Recommendations extends React.Component<{}> {
                 ContentScaling={scale}
             />
         </div>;
-        const data = renderDoc.data;
-        if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data);
-        newRenderDoc.preview = true;
+        // const data = renderDoc.data;
+        // if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data);
+        // newRenderDoc.preview = true;
+        // this.previewDocs.push(newRenderDoc);
         return docview;
 
     }
@@ -92,6 +97,8 @@ export class Recommendations extends React.Component<{}> {
     @action
     closeMenu = () => {
         this._display = false;
+        this.previewDocs.forEach(doc => DocServer.DeleteDocument(doc[Id]));
+        this.previewDocs = [];
     }
 
     @action
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2d4775070..3cef93383 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -857,27 +857,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 input.click();
             }
         });
-        ContextMenu.Instance.addItem({
-            description: "Recommender System",
-            event: async () => {
-                // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
-                let activedocs = this.getActiveDocuments();
-                let allDocs = await SearchUtil.GetAllDocs();
-                allDocs.forEach(doc => console.log(doc.title));
-                // clears internal representation of documents as vectors
-                ClientRecommender.Instance.reset_docs();
-                await Promise.all(allDocs.map((doc: Doc) => {
-                    console.log(StrCast(doc.title));
-                    if (doc.type === DocumentType.IMG) {
-                        console.log(doc.title);
-                        const extdoc = doc.data_ext as Doc;
-                        return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
-                    }
-                }));
-                console.log(ClientRecommender.Instance.createDistanceMatrix());
-            },
-            icon: "brain"
-        });
         layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
         layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
         layoutItems.push({
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2a6e91272..f708a7a3a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -648,21 +648,31 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
                 let documents: Doc[] = [];
                 let allDocs = await SearchUtil.GetAllDocs();
-                allDocs.forEach(doc => console.log(doc.title));
+                //allDocs.forEach(doc => console.log(doc.title));
                 // clears internal representation of documents as vectors
                 ClientRecommender.Instance.reset_docs();
                 await Promise.all(allDocs.map((doc: Doc) => {
-                    if (doc.type === DocumentType.IMG) {
-                        console.log(StrCast(doc.title));
-                        documents.push(doc);
-                        const extdoc = doc.data_ext as Doc;
-                        return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+                    let mainDoc: boolean = false;
+                    const dataDoc = Doc.GetDataDoc(doc);
+                    if (doc.type === DocumentType.TEXT) {
+                        if (dataDoc === Doc.GetDataDoc(this.props.Document)) {
+                            mainDoc = true;
+                            console.log(StrCast(doc.title));
+                        }
+                        if (!documents.includes(dataDoc)) {
+                            documents.push(dataDoc);
+                            const extdoc = doc.data_ext as Doc;
+                            return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, mainDoc);
+                        }
                     }
                 }));
                 console.log(ClientRecommender.Instance.createDistanceMatrix());
+                const doclist = ClientRecommender.Instance.computeSimilarities();
                 let recDocs: { preview: Doc, score: number }[] = [];
-                for (let i = 0; i < documents.length; i++) {
-                    recDocs.push({ preview: documents[i], score: i });
+                // tslint:disable-next-line: prefer-for-of
+                for (let i = 0; i < doclist.length; i++) {
+                    console.log(doclist[i].score);
+                    recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score });
                 }
                 Recommendations.Instance.addDocuments(recDocs);
                 Recommendations.Instance.displayRecommendations(e.pageX + 100, e.pageY);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index ec35465eb..d94e92847 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -240,22 +240,6 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         }
     }
 
-    extractText = async () => {
-        //let activedocs = this.getActiveDocuments();
-        let allDocs = await SearchUtil.GetAllDocs();
-        allDocs.forEach(doc => console.log(doc.title));
-        // clears internal representation of documents as vectors
-        ClientRecommender.Instance.reset_docs();
-        await Promise.all(allDocs.map((doc: Doc) => {
-            //console.log(StrCast(doc.title));
-            if (doc.type === DocumentType.IMG) {
-                const extdoc = doc.data_ext as Doc;
-                return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
-            }
-        }));
-        console.log(ClientRecommender.Instance.createDistanceMatrix());
-    }
-
     generateMetadata = (threshold: Confidence = Confidence.Excellent) => {
         let converter = (results: any) => {
             let tagDoc = new Doc;
-- 
cgit v1.2.3-70-g09d2


From c6e2eee8e5b035ed4cab1e7bb1315a36d43255c5 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Tue, 8 Oct 2019 16:02:25 -0400
Subject: switching out

---
 src/client/views/MainView.tsx                           |  1 -
 src/client/views/Recommendations.tsx                    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx       |  3 ---
 src/client/views/nodes/DocumentContentsView.tsx         |  6 ------
 src/client/views/nodes/DocumentView.tsx                 |  4 ++--
 src/client/views/nodes/ImageBox.tsx                     |  1 -
 src/new_fields/RichTextField.ts                         | 17 ++++++++++++++++-
 7 files changed, 19 insertions(+), 15 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 17de708a2..e6f500e75 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -41,7 +41,6 @@ import { FilterBox } from './search/FilterBox';
 import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField';
 //import { DocumentManager } from '../util/DocumentManager';
 import { RecommendationsBox } from './Recommendations';
-import PresModeMenu from './presentationview/PresentationModeMenu';
 import { PresBox } from './nodes/PresBox';
 import { OverlayView } from './OverlayView';
 
diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx
index b7b1d84d0..c44dfc032 100644
--- a/src/client/views/Recommendations.tsx
+++ b/src/client/views/Recommendations.tsx
@@ -174,7 +174,7 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
                                 {this.DocumentIcon(doc)}
                             </span>
                             <span className="score">{NumCast(doc.score).toFixed(4)}</span>
-                            <div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, true, undefined, undefined, undefined, this.props.Document.sourceDocContext as Doc)}>
+                            <div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, false)}>
                                 <FontAwesomeIcon className="documentdecorations-icon" icon={"bullseye"} size="sm" />
                             </div>
                             <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink(this.props.Document.sourceDoc as Doc, doc, undefined, "User Selected Link", "Generated from Recommender", undefined)}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3ee069e4c..d2b8afa02 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -12,7 +12,6 @@ import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fiel
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 import { aggregateBounds, emptyFunction, intersectRect, returnEmptyString, returnOne, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
-import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
 import { DocumentType } from "../../../documents/DocumentTypes";
 import { DocumentManager } from "../../../util/DocumentManager";
@@ -28,7 +27,6 @@ import { InkingCanvas } from "../../InkingCanvas";
 import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentContentsView } from "../../nodes/DocumentContentsView";
 import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView";
-import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
 import PDFMenu from "../../pdf/PDFMenu";
 import { CollectionSubView } from "../CollectionSubView";
@@ -41,7 +39,6 @@ import React = require("react");
 import v5 = require("uuid/v5");
 import { ClientRecommender } from "../../../ClientRecommender";
 import { SearchUtil } from "../../../util/SearchUtil";
-import { SearchBox } from "../../SearchBox";
 import { RouteStore } from "../../../../server/RouteStore";
 import { string, number, elementType } from "prop-types";
 import { DocServer } from "../../../DocServer";
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index e5eb8dbce..919c13055 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -2,8 +2,6 @@ import { computed } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../../new_fields/Doc";
 import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast } from "../../../new_fields/Types";
-import { OmitKeys, Without } from "../../../Utils";
 import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
@@ -30,14 +28,10 @@ import { PresElementBox } from "../presentationview/PresElementBox";
 import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
 import React = require("react");
-import { FieldViewProps } from "./FieldView";
 import { Without, OmitKeys } from "../../../Utils";
 import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
-import { Doc } from "../../../new_fields/Doc";
-import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
 import { RecommendationsBox } from "../../views/Recommendations";
-import { ScriptField } from "../../../new_fields/ScriptField";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0064b98c3..070b1f426 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -621,7 +621,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         recommendations.documentIconHeight = 150;
         recommendations.sourceDoc = this.props.Document;
         recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document;
-        CollectionDockingView.Instance.AddRightSplit(recommendations, undefined);
+        CollectionDockingView.AddRightSplit(recommendations, undefined);
 
         // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY);
     }
@@ -641,7 +641,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             body.href = urls[i];
             bodies.push(body);
         }
-        CollectionDockingView.Instance.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined);
+        CollectionDockingView.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined);
     }
 
     onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); };
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f36b9895f..7ffe64b9b 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -29,7 +29,6 @@ import "./ImageBox.scss";
 import React = require("react");
 import { SearchUtil } from '../../util/SearchUtil';
 import { ClientRecommender } from '../../ClientRecommender';
-import { DocumentType } from '../../documents/Documents';
 var requestImageSize = require('../../util/request-image-size');
 var path = require('path');
 const { Howl } = require('howler');
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index 390045ee1..f41ea0350 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -4,6 +4,9 @@ import { Deserializable } from "../client/util/SerializationHelper";
 import { Copy, ToScriptString, ToPlainText } from "./FieldSymbols";
 import { scriptingGlobal } from "../client/util/Scripting";
 
+const delimiter = "\n";
+const joiner = "";
+
 @scriptingGlobal
 @Deserializable("RichTextField")
 export class RichTextField extends ObjectField {
@@ -24,7 +27,19 @@ export class RichTextField extends ObjectField {
     }
 
     [ToPlainText]() {
-        return this.Data;
+        // Because we're working with plain text, just concatenate all paragraphs
+        let content = JSON.parse(this.Data).doc.content;
+        let paragraphs = content.filter((item: any) => item.type === "paragraph");
+
+        // Functions to flatten ProseMirror paragraph objects (and their components) to plain text
+        // While this function already exists in state.doc.textBeteen(), it doesn't account for newlines 
+        let blockText = (block: any) => block.text;
+        let concatenateParagraph = (p: any) => (p.content ? p.content.map(blockText).join(joiner) : "") + delimiter;
+
+        // Concatentate paragraphs and string the result together
+        let textParagraphs: string[] = paragraphs.map(concatenateParagraph);
+        let plainText = textParagraphs.join(joiner);
+        return plainText.substring(0, plainText.length - 1);
     }
 
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From e103be2d58da2a6809dd4ad5f0b5f445d8d6c96b Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Sat, 16 Nov 2019 16:45:41 -0500
Subject: ibm integrated, bing search -> server next time

---
 package.json                                       |  7 ++-
 src/client/ClientRecommender.tsx                   | 66 ++++++++++++++++++++--
 src/client/apis/IBM_Recommender.ts                 |  6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  5 --
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/server/index.ts                                |  2 +-
 6 files changed, 70 insertions(+), 18 deletions(-)

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

diff --git a/package.json b/package.json
index 80b65cc68..d41119a47 100644
--- a/package.json
+++ b/package.json
@@ -57,9 +57,9 @@
         "@hig/theme-context": "^2.1.3",
         "@hig/theme-data": "^2.3.3",
         "@tensorflow-models/universal-sentence-encoder": "^1.2.0",
-        "@tensorflow/tfjs": "^1.3.1",
+        "@tensorflow/tfjs-converter": "^1.3.2",
         "@tensorflow/tfjs-core": "^1.3.1",
-        "@tensorflow/tfjs-node": "^1.3.1",
+        "@tensorflow/tfjs-node": "1.3.1",
         "@trendmicro/react-dropdown": "^1.3.0",
         "@types/adm-zip": "^0.4.32",
         "@types/animejs": "^2.0.2",
@@ -238,8 +238,9 @@
         "url-loader": "^1.1.2",
         "uuid": "^3.3.2",
         "wikijs": "^6.0.1",
+        "word2vec": "^1.1.4",
         "words-to-numbers": "^1.5.1",
         "xoauth2": "^1.2.0",
         "youtube": "^0.1.0"
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index 364ec0fe0..a37434c0d 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -8,6 +8,7 @@ import { observable, action, computed, reaction } from "mobx";
 var assert = require('assert');
 var sw = require('stopword');
 var FeedParser = require('feedparser');
+var https = require('https');
 import "./ClientRecommender.scss";
 import { JSXElement } from "babel-types";
 import { RichTextField } from "../new_fields/RichTextField";
@@ -111,7 +112,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
         }
         );
         let doclist = Array.from(ClientRecommender.Instance.docVectors);
-        if (distance_metric == "euclidian") {
+        if (distance_metric === "euclidian") {
             doclist.sort((a: RecommenderDocument, b: RecommenderDocument) => a.score - b.score);
         }
         else {
@@ -245,7 +246,27 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             if (kp_string.length > 2) kp_string = kp_string.substring(0, kp_string.length - 2);
             console.log("kp string: ", kp_string);
             let values = "";
-            if (!internal) values = await this.sendRequest(highKP);
+            if (!internal) {
+                const parameters: any = {
+                    'language': 'en',
+                    'text': data,
+                    'features': {
+                        'keywords': {
+                            'sentiment': true,
+                            'emotion': true,
+                            'limit': 3
+                        }
+                    }
+                };
+                await Identified.PostToServer("/IBMAnalysis", parameters).then(response => {
+                    const sorted_keywords = response.result.keywords;
+                    if (sorted_keywords.length > 0) {
+                        console.log("IBM keyphrase", sorted_keywords[0]);
+                        highKP = [sorted_keywords[0].text];
+                    }
+                });
+                values = await this.sendRequest(highKP, "bing");
+            }
             return { keyterms: keyterms, external_recommendations: values, kp_string: [kp_string] };
         };
         if (data !== "") {
@@ -290,11 +311,46 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
      * API for sending arXiv request.
      */
 
-    private async sendRequest(keywords: string[]) {
+    private async sendRequest(keywords: string[], api: string) {
         let query = "";
         keywords.forEach((kp: string) => query += " " + kp);
-        return new Promise<any>(resolve => {
-            this.arxivrequest(query).then(resolve);
+        if (api === "arxiv") {
+            return new Promise<any>(resolve => {
+                this.arxivrequest(query).then(resolve);
+            });
+        }
+        else if (api === "bing") {
+            await this.bingWebSearch(query);
+        }
+        else {
+            return new Promise<any>(resolve => {
+                this.arxivrequest(query).then(resolve);
+            });
+        }
+
+    }
+
+    bingWebSearch = async (query: string) => {
+        https.get({
+            hostname: 'api.cognitive.microsoft.com',
+            path: '/bing/v5.0/search?q=' + encodeURIComponent(query),
+            headers: { 'Ocp-Apim-Subscription-Key': process.env.BING },
+        }, (res: any) => {
+            let body = '';
+            res.on('data', (part: any) => body += part);
+            res.on('end', () => {
+                for (var header in res.headers) {
+                    if (header.startsWith("bingapis-") || header.startsWith("x-msedge-")) {
+                        console.log(header + ": " + res.headers[header])
+                    }
+                }
+                console.log('\nJSON Response:\n');
+                console.dir(JSON.parse(body), { colors: false, depth: null });
+            })
+            res.on('error', (e: any) => {
+                console.log('Error: ' + e.message);
+                throw e;
+            });
         });
     }
 
diff --git a/src/client/apis/IBM_Recommender.ts b/src/client/apis/IBM_Recommender.ts
index 5f80f5126..da6257f28 100644
--- a/src/client/apis/IBM_Recommender.ts
+++ b/src/client/apis/IBM_Recommender.ts
@@ -16,19 +16,19 @@ export namespace IBM_Recommender {
     });
 
     const analyzeParams = {
-        'url': 'www.ibm.com',
+        'text': 'this is a test of the keyword extraction feature I am integrating into the program',
         'features': {
             'keywords': {
                 'sentiment': true,
                 'emotion': true,
                 'limit': 3
-            }
+            },
         }
     };
 
     export const analyze = async (_parameters: any): Promise<Opt<string>> => {
         try {
-            const response = await naturalLanguageUnderstanding.analyze(analyzeParams);
+            const response = await naturalLanguageUnderstanding.analyze(_parameters);
             console.log(response);
             return (JSON.stringify(response, null, 2));
         } catch (err) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bb5d99a28..bfec545c6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -34,11 +34,6 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
 import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import v5 = require("uuid/v5");
-import { ClientRecommender } from "../../../ClientRecommender";
-import { SearchUtil } from "../../../util/SearchUtil";
-import { RouteStore } from "../../../../server/RouteStore";
-import { string, number, elementType } from "prop-types";
 import { DocServer } from "../../../DocServer";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { DocumentViewProps } from "../../nodes/DocumentView";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 55063a52c..c6ad2f9d7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -772,7 +772,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
     render() {
         if (!this.props.Document) return (null);
-        trace();
+        //trace();
         const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined;
         const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
         const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
diff --git a/src/server/index.ts b/src/server/index.ts
index 2a669f147..45fc7fc07 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1086,7 +1086,7 @@ addSecureRoute({
         const batched = BatchedArray.from<GooglePhotosUploadUtils.MediaInput>(media, { batchSize: 25 });
         const newMediaItems = await batched.batchedMapPatientInterval<NewMediaItem>(
             { magnitude: 100, unit: TimeUnit.Milliseconds },
-            async (batch, collector) => {
+            async (batch: any, collector: any): Promise<any> => {
                 for (let index = 0; index < batch.length; index++) {
                     const { url, description } = batch[index];
                     const uploadToken = await GooglePhotosUploadUtils.DispatchGooglePhotosUpload(url);
-- 
cgit v1.2.3-70-g09d2


From b4c2d2add2b863060ce559e49b3953f24050f162 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Tue, 14 Jan 2020 14:47:44 -0500
Subject: can add strokes to mobile ink

---
 src/client/views/collections/CollectionSubView.tsx |   3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 +
 src/mobile/MobileInterface.tsx                     | 108 +++++++++++++++------
 .../authentication/models/current_user_utils.ts    |   4 +
 4 files changed, 87 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 73dc7edc6..d8b575092 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -53,7 +53,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer && this.dropDisposer();
             this.gestureDisposer && this.gestureDisposer();
+            console.log("create drop", ele);
             if (ele) {
+                console.log("create drop 2", ele);
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
             }
@@ -135,7 +137,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
         @undoBatch
         protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
-
         }
 
         @undoBatch
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 84945c6e6..c3e131184 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -356,6 +356,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @undoBatch
     onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
+        console.log("on gesture");
         switch (ge.gesture) {
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 1e0920686..8d342d749 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -10,8 +10,10 @@ import { DocumentView } from '../client/views/nodes/DocumentView';
 import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils';
 import { Transform } from '../client/util/Transform';
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPenNib, faHighlighter, faEraser, faMousePointer } from '@fortawesome/free-solid-svg-icons';
+import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice } from '@fortawesome/free-solid-svg-icons';
 import { Scripting } from '../client/util/Scripting';
+import { CollectionFreeFormView } from '../client/views/collections/collectionFreeForm/CollectionFreeFormView';
+import GestureOverlay from '../client/views/GestureOverlay';
 
 @observer
 export default class MobileInterface extends React.Component {
@@ -35,52 +37,102 @@ export default class MobileInterface extends React.Component {
         }
     }
 
-    @action switchCurrentView = (view: "main" | "ink" | "library") => { this.currentView = view; }
+    @action
+    switchCurrentView = (view: "main" | "ink" | "library") => {
+        this.currentView = view;
+
+        if (this.userDoc) {
+            switch (view) {
+                case "main": {
+                    const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+                    this.userDoc.activeMobile = doc;
+                    break;
+                }
+                case "ink": {
+                    const doc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
+                    this.userDoc.activeMobile = doc;
+                    break;
+                }
+            }
+        }
+    }
 
     @computed
     get mainContent() {
         if (this.mainContainer) {
-            switch (this.currentView) {
-                case "main":
-                    return <DocumentView
+            return <DocumentView
+                Document={this.mainContainer}
+                DataDoc={undefined}
+                LibraryPath={emptyPath}
+                addDocument={undefined}
+                addDocTab={returnFalse}
+                pinToPres={emptyFunction}
+                removeDocument={undefined}
+                ruleProvider={undefined}
+                onClick={undefined}
+                ScreenToLocalTransform={Transform.Identity}
+                ContentScaling={returnOne}
+                PanelWidth={() => window.screen.width}
+                PanelHeight={() => window.screen.height}
+                renderDepth={0}
+                focus={emptyFunction}
+                backgroundColor={returnEmptyString}
+                parentActive={returnTrue}
+                whenActiveChanged={emptyFunction}
+                bringToFront={emptyFunction}
+                ContainingCollectionView={undefined}
+                ContainingCollectionDoc={undefined}
+                zoomToScale={emptyFunction}
+                getScale={returnOne}>
+            </DocumentView>;
+        }
+        return "hello";
+    }
+
+    @computed
+    get inkContent() {
+        // return <div>INK</div>;
+        if (this.mainContainer) {
+            return (
+                <GestureOverlay>
+                    <CollectionFreeFormView
                         Document={this.mainContainer}
                         DataDoc={undefined}
                         LibraryPath={emptyPath}
-                        addDocument={undefined}
+                        fieldKey={""}
+                        addDocument={returnFalse}
+                        removeDocument={returnFalse}
+                        moveDocument={returnFalse}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
-                        removeDocument={undefined}
-                        ruleProvider={undefined}
-                        onClick={undefined}
-                        ScreenToLocalTransform={Transform.Identity}
-                        ContentScaling={returnOne}
-                        PanelWidth={() => window.screen.width}
                         PanelHeight={() => window.screen.height}
-                        renderDepth={0}
+                        PanelWidth={() => window.screen.width}
+                        annotationsKey={""}
+                        isAnnotationOverlay={false}
                         focus={emptyFunction}
-                        backgroundColor={returnEmptyString}
-                        parentActive={returnTrue}
-                        whenActiveChanged={emptyFunction}
-                        bringToFront={emptyFunction}
+                        isSelected={returnTrue} //
+                        select={emptyFunction}
+                        active={returnTrue} //
+                        ContentScaling={returnOne}
+                        whenActiveChanged={returnFalse}
+                        CollectionView={undefined}
+                        ScreenToLocalTransform={Transform.Identity}
+                        ruleProvider={undefined}
+                        renderDepth={0}
                         ContainingCollectionView={undefined}
                         ContainingCollectionDoc={undefined}
-                        zoomToScale={emptyFunction}
-                        getScale={returnOne}>
-                    </DocumentView>;
-                case "ink":
-                    return <div>INK</div>;
-                case "library":
-                    return <div>LIBRARY</div>;
-            }
+                        chromeCollapsed={true}>
+                    </CollectionFreeFormView>
+                </GestureOverlay>
+            );
         }
-        return "hello";
     }
 
     render() {
-        console.log("rendering mobile");
+        const content = this.currentView === "main" ? this.mainContent : this.currentView === "ink" ? this.inkContent : <></>;
         return (
             <div className="mobile-container">
-                {this.mainContent}
+                {content}
             </div>
         );
     }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 71369b625..b40179fbc 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -140,6 +140,10 @@ export class CurrentUserUtils {
         });
     }
 
+    static setupMobileInkingDoc(userDoc: Doc) {
+        return Docs.Create.FreeformDocument([], { x: 0, y: 0, width: 10, height: 20, title: "Mobile Inking", backgroundColor: "red" });
+    }
+
     // setup the Creator button which will display the creator panel.  This panel will include the drag creators and the color picker.  when clicked, this panel will be displayed in the target container (ie, sidebarContainer)  
     static setupToolsPanel(sidebarContainer: Doc, doc: Doc) {
         // setup a masonry view of all he creators
-- 
cgit v1.2.3-70-g09d2


From 3cca58612cde96a3082ca8e190fe2166d531d556 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Tue, 14 Jan 2020 17:40:50 -0500
Subject: drawn strokes get added to mobile ink collection

---
 src/client/views/GestureOverlay.tsx                |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  2 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  8 +--
 src/mobile/MobileInterface.scss                    |  7 +++
 src/mobile/MobileInterface.tsx                     | 62 ++++++++++++++--------
 .../authentication/models/current_user_utils.ts    |  2 +-
 6 files changed, 53 insertions(+), 30 deletions(-)
 create mode 100644 src/mobile/MobileInterface.scss

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

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 632f2f0d6..4d487c032 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -231,9 +231,9 @@ export default class GestureOverlay extends Touchable {
     render() {
         return (
             <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart}>
+                {this.currentStroke}
                 {this.props.children}
                 {this._palette}
-                {this.currentStroke}
             </div>);
     }
 }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d8b575092..e94f24f2c 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -53,9 +53,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer && this.dropDisposer();
             this.gestureDisposer && this.gestureDisposer();
-            console.log("create drop", ele);
             if (ele) {
-                console.log("create drop 2", ele);
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
             }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c3e131184..8d376fb57 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -114,6 +114,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     private addDocument = (newBox: Doc) => {
         const added = this.props.addDocument(newBox);
+        console.log("adding doc from freeform", added);
         added && this.bringToFront(newBox);
         added && this.updateCluster(newBox);
         return added;
@@ -356,12 +357,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @undoBatch
     onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
-        console.log("on gesture");
         switch (ge.gesture) {
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
                 const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
                 const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, width: B.width, height: B.height });
+                console.log("make stroke", inkDoc);
                 this.addDocument(inkDoc);
                 e.stopPropagation();
                 break;
@@ -403,14 +404,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
         // I think it makes sense for the marquee menu to go away when panned. -syip2
-        MarqueeOptionsMenu.Instance.fadeOut(true);
+        MarqueeOptionsMenu.Instance && MarqueeOptionsMenu.Instance.fadeOut(true);
 
         let x = this.Document.panX || 0;
         let y = this.Document.panY || 0;
         const docs = this.childLayoutPairs.map(pair => pair.layout);
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         if (!this.isAnnotationOverlay) {
-            PDFMenu.Instance.fadeOut(true);
+            PDFMenu.Instance && PDFMenu.Instance.fadeOut(true);
             const minx = docs.length ? NumCast(docs[0].x) : 0;
             const maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
             const miny = docs.length ? NumCast(docs[0].y) : 0;
@@ -990,6 +991,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         if (!this.extensionDoc) return (null);
         // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
+        console.log("height freeform", this.isAnnotationOverlay, this.Document.scrollHeight, this.props.PanelHeight());
         return <div className={"collectionfreeformview-container"} ref={this.createDropAndGestureTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
             style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
             onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss
new file mode 100644
index 000000000..e4cc919a5
--- /dev/null
+++ b/src/mobile/MobileInterface.scss
@@ -0,0 +1,7 @@
+.mobileInterface-topButtons {
+    position: absolute;
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    z-index: 9999;
+}
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 8d342d749..3cb08afa7 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -10,10 +10,16 @@ import { DocumentView } from '../client/views/nodes/DocumentView';
 import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils';
 import { Transform } from '../client/util/Transform';
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice } from '@fortawesome/free-solid-svg-icons';
+import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck } from '@fortawesome/free-solid-svg-icons';
 import { Scripting } from '../client/util/Scripting';
 import { CollectionFreeFormView } from '../client/views/collections/collectionFreeForm/CollectionFreeFormView';
 import GestureOverlay from '../client/views/GestureOverlay';
+import { InkingControl } from '../client/views/InkingControl';
+import { InkTool } from '../new_fields/InkField';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import "./MobileInterface.scss";
+
+library.add(faTrash, faCheck);
 
 @observer
 export default class MobileInterface extends React.Component {
@@ -22,6 +28,9 @@ export default class MobileInterface extends React.Component {
     @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
     @observable private currentView: "main" | "ink" | "library" = "main";
 
+    private mainDoc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+    private inkDoc?: Doc;
+
     constructor(props: Readonly<{}>) {
         super(props);
         MobileInterface.Instance = this;
@@ -32,8 +41,8 @@ export default class MobileInterface extends React.Component {
         library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]);
 
         if (this.userDoc && !this.mainContainer) {
-            const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
-            this.userDoc.activeMobile = doc;
+            // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+            this.userDoc.activeMobile = this.mainDoc;
         }
     }
 
@@ -44,13 +53,14 @@ export default class MobileInterface extends React.Component {
         if (this.userDoc) {
             switch (view) {
                 case "main": {
-                    const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
-                    this.userDoc.activeMobile = doc;
+                    // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+                    this.userDoc.activeMobile = this.mainDoc;
                     break;
                 }
                 case "ink": {
-                    const doc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
-                    this.userDoc.activeMobile = doc;
+                    this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
+                    this.userDoc.activeMobile = this.inkDoc;
+                    InkingControl.Instance.switchTool(InkTool.Pen);
                     break;
                 }
             }
@@ -64,7 +74,7 @@ export default class MobileInterface extends React.Component {
                 Document={this.mainContainer}
                 DataDoc={undefined}
                 LibraryPath={emptyPath}
-                addDocument={undefined}
+                addDocument={returnFalse}
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
                 removeDocument={undefined}
@@ -89,40 +99,46 @@ export default class MobileInterface extends React.Component {
         return "hello";
     }
 
+    onClick = (e: React.MouseEvent) => {
+        // insert ink as collection into active displayed doc view
+        // reset ink collection
+        this.inkDoc = undefined;
+        console.log("clicked");
+        this.switchCurrentView("main");
+        InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool
+    }
+
     @computed
     get inkContent() {
-        // return <div>INK</div>;
+        // TODO: get props from active collection and pass to the new freeform
         if (this.mainContainer) {
             return (
                 <GestureOverlay>
-                    <CollectionFreeFormView
+                    <div className="mobileInterface-topButtons">
+                        <button className="mobileInterface-button cancel" onClick={this.onClick} title="Cancel drawing"><FontAwesomeIcon icon="trash" /></button>
+                        <button className="mobileInterface-button cancel" onClick={this.onClick} title="Insert drawing"><FontAwesomeIcon icon="check" /></button>
+                    </div>
+                    <CollectionView
                         Document={this.mainContainer}
                         DataDoc={undefined}
                         LibraryPath={emptyPath}
                         fieldKey={""}
-                        addDocument={returnFalse}
-                        removeDocument={returnFalse}
-                        moveDocument={returnFalse}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
-                        PanelHeight={() => window.screen.height}
-                        PanelWidth={() => window.screen.width}
-                        annotationsKey={""}
-                        isAnnotationOverlay={false}
+                        PanelHeight={() => window.innerHeight}
+                        PanelWidth={() => window.innerWidth}
                         focus={emptyFunction}
-                        isSelected={returnTrue} //
+                        isSelected={returnTrue}
                         select={emptyFunction}
-                        active={returnTrue} //
+                        active={returnTrue}
                         ContentScaling={returnOne}
                         whenActiveChanged={returnFalse}
-                        CollectionView={undefined}
                         ScreenToLocalTransform={Transform.Identity}
                         ruleProvider={undefined}
                         renderDepth={0}
                         ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
-                        chromeCollapsed={true}>
-                    </CollectionFreeFormView>
+                        ContainingCollectionDoc={undefined}>
+                    </CollectionView>
                 </GestureOverlay>
             );
         }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index b40179fbc..98092ddfe 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -141,7 +141,7 @@ export class CurrentUserUtils {
     }
 
     static setupMobileInkingDoc(userDoc: Doc) {
-        return Docs.Create.FreeformDocument([], { x: 0, y: 0, width: 10, height: 20, title: "Mobile Inking", backgroundColor: "red" });
+        return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white" });
     }
 
     // setup the Creator button which will display the creator panel.  This panel will include the drag creators and the color picker.  when clicked, this panel will be displayed in the target container (ie, sidebarContainer)  
-- 
cgit v1.2.3-70-g09d2


From 65e164eaec42d1850de7f5e1eba1d4302c3e8230 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Wed, 15 Jan 2020 16:08:48 -0500
Subject: mobile interface emits events when switched to inking view and when
 strokes are drawn, currently with dummy callbacks

---
 src/client/DocServer.ts                            | 22 ++++++++++++++++++-
 src/client/views/GestureOverlay.tsx                | 21 ++++++++++++++++++
 src/client/views/MainView.tsx                      | 13 +++++++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  3 ---
 src/mobile/MobileInterface.tsx                     | 25 +++++++++++++++-------
 src/server/Message.ts                              | 13 +++++++++++
 src/server/Websocket/Websocket.ts                  | 12 ++++++++++-
 7 files changed, 94 insertions(+), 15 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index ed7fbd7ba..cfd9612ed 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,10 +1,11 @@
 import * as OpenSocket from 'socket.io-client';
-import { MessageStore, YoutubeQueryTypes } from "./../server/Message";
+import { MessageStore, YoutubeQueryTypes, GestureContent } from "./../server/Message";
 import { Opt, Doc } from '../new_fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
 import { RefField } from '../new_fields/RefField';
 import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
+import GestureOverlay from './views/GestureOverlay';
 
 /**
  * This class encapsulates the transfer and cross-client synchronization of
@@ -64,6 +65,19 @@ export namespace DocServer {
         }
     }
 
+    export namespace Mobile {
+
+        export function dispatchGesturePoints(content: GestureContent) {
+            Utils.Emit(_socket, MessageStore.GesturePoints, content);
+        }
+
+        export function dispatchBoxTrigger(enableBox: boolean) {
+            // _socket.emit("dispatchBoxTrigger");
+            Utils.Emit(_socket, MessageStore.MobileInkBoxTrigger, enableBox);
+        }
+
+    }
+
     export function init(protocol: string, hostname: string, port: number, identifier: string) {
         _cache = {};
         GUID = identifier;
@@ -85,6 +99,12 @@ export namespace DocServer {
         Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, () => {
             alert("Your connection to the server has been terminated.");
         });
+        _socket.addEventListener("receiveGesturePoints", (content: GestureContent) => {
+            GestureOverlay.Instance.manualDispatch(content);
+        });
+        _socket.addEventListener("receiveBoxTrigger", (enableBox: boolean) => {
+            GestureOverlay.Instance.showBox(enableBox);
+        });
     }
 
     function errorFunc(): never {
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 4d487c032..4b8dfa119 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -16,6 +16,10 @@ import { Scripting } from "../util/Scripting";
 import { FieldValue, Cast } from "../../new_fields/Types";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 import Palette from "./Palette";
+import MobileInterface from "../../mobile/MobileInterface";
+import { MainView } from "./MainView";
+import { DocServer } from "../DocServer";
+import { GestureContent } from "../../server/Message";
 
 @observer
 export default class GestureOverlay extends Touchable {
@@ -35,6 +39,14 @@ export default class GestureOverlay extends Touchable {
         GestureOverlay.Instance = this;
     }
 
+    manualDispatch = (content: GestureContent) => {
+        console.log(content);
+    }
+
+    showBox = (enableBox: boolean) => {
+        console.log("enable box?", enableBox);
+    }
+
     @action
     handleHandDown = (e: React.TouchEvent) => {
         const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
@@ -139,6 +151,15 @@ export default class GestureOverlay extends Touchable {
             const B = this.svgBounds;
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
 
+            if (MobileInterface.Instance.drawingInk) {
+                const { selectedColor, selectedWidth } = InkingControl.Instance;
+                DocServer.Mobile.dispatchGesturePoints({
+                    points: this._points,
+                    color: selectedColor,
+                    width: selectedWidth
+                });
+            }
+
             const result = GestureUtils.GestureRecognizer.Recognize(new Array(points));
             let actionPerformed = false;
             if (result && result.Score > 0.7) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 93fb0a07c..f1f7c37a3 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -49,6 +49,7 @@ export class MainView extends React.Component {
     private _flyoutSizeOnDown = 0;
     private _urlState: HistoryUtil.DocUrl;
     private _docBtnRef = React.createRef<HTMLDivElement>();
+    private _mainViewRef = React.createRef<HTMLDivElement>();
 
     @observable private _panelWidth: number = 0;
     @observable private _panelHeight: number = 0;
@@ -507,8 +508,16 @@ export class MainView extends React.Component {
         return (null);
     }
 
+    get mainViewElement() {
+        return document.getElementById("mainView-container");
+    }
+
+    get mainViewRef() {
+        return this._mainViewRef;
+    }
+
     render() {
-        return (<div id="mainView-container">
+        return (<div id="mainView-container" ref={this._mainViewRef}>
             <DictationOverlay />
             <SharingManager />
             <GoogleAuthenticationManager />
@@ -518,7 +527,7 @@ export class MainView extends React.Component {
             </GestureOverlay>
             <PreviewCursor />
             <ContextMenu />
-            <RadialMenu/>
+            <RadialMenu />
             <PDFMenu />
             <MarqueeOptionsMenu />
             <OverlayView />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8d376fb57..bbfd22f50 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -114,7 +114,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     private addDocument = (newBox: Doc) => {
         const added = this.props.addDocument(newBox);
-        console.log("adding doc from freeform", added);
         added && this.bringToFront(newBox);
         added && this.updateCluster(newBox);
         return added;
@@ -362,7 +361,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 const points = ge.points;
                 const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
                 const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, width: B.width, height: B.height });
-                console.log("make stroke", inkDoc);
                 this.addDocument(inkDoc);
                 e.stopPropagation();
                 break;
@@ -991,7 +989,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         if (!this.extensionDoc) return (null);
         // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
-        console.log("height freeform", this.isAnnotationOverlay, this.Document.scrollHeight, this.props.PanelHeight());
         return <div className={"collectionfreeformview-container"} ref={this.createDropAndGestureTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
             style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
             onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 3cb08afa7..1429226b4 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -2,7 +2,7 @@ import React = require('react');
 import { observer } from 'mobx-react';
 import { computed, action, observable } from 'mobx';
 import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
-import { FieldValue, Cast } from '../new_fields/Types';
+import { FieldValue, Cast, StrCast } from '../new_fields/Types';
 import { Doc } from '../new_fields/Doc';
 import { Docs } from '../client/documents/Documents';
 import { CollectionView } from '../client/views/collections/CollectionView';
@@ -18,6 +18,10 @@ import { InkingControl } from '../client/views/InkingControl';
 import { InkTool } from '../new_fields/InkField';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import "./MobileInterface.scss";
+import { SelectionManager } from '../client/util/SelectionManager';
+import { DateField } from '../new_fields/DateField';
+import { GestureUtils } from '../pen-gestures/GestureUtils';
+import { DocServer } from '../client/DocServer';
 
 library.add(faTrash, faCheck);
 
@@ -30,6 +34,7 @@ export default class MobileInterface extends React.Component {
 
     private mainDoc = CurrentUserUtils.setupMobileDoc(this.userDoc);
     private inkDoc?: Doc;
+    public drawingInk: boolean = false;
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -61,6 +66,10 @@ export default class MobileInterface extends React.Component {
                     this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
                     this.userDoc.activeMobile = this.inkDoc;
                     InkingControl.Instance.switchTool(InkTool.Pen);
+                    this.drawingInk = true;
+
+                    DocServer.Mobile.dispatchBoxTrigger(true);
+
                     break;
                 }
             }
@@ -100,17 +109,17 @@ export default class MobileInterface extends React.Component {
     }
 
     onClick = (e: React.MouseEvent) => {
-        // insert ink as collection into active displayed doc view
-        // reset ink collection
-        this.inkDoc = undefined;
-        console.log("clicked");
         this.switchCurrentView("main");
         InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool
+
+        DocServer.Mobile.dispatchBoxTrigger(false);
+
+        this.inkDoc = undefined;
+        this.drawingInk = false;
     }
 
     @computed
     get inkContent() {
-        // TODO: get props from active collection and pass to the new freeform
         if (this.mainContainer) {
             return (
                 <GestureOverlay>
@@ -128,9 +137,9 @@ export default class MobileInterface extends React.Component {
                         PanelHeight={() => window.innerHeight}
                         PanelWidth={() => window.innerWidth}
                         focus={emptyFunction}
-                        isSelected={returnTrue}
+                        isSelected={returnFalse}
                         select={emptyFunction}
-                        active={returnTrue}
+                        active={returnFalse}
                         ContentScaling={returnOne}
                         whenActiveChanged={returnFalse}
                         ScreenToLocalTransform={Transform.Identity}
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 621abfd1e..fe74dafa5 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,4 +1,5 @@
 import { Utils } from "../Utils";
+import { Point } from "../pen-gestures/ndollar";
 
 export class Message<T> {
     private _name: string;
@@ -42,6 +43,16 @@ export interface Diff extends Reference {
     readonly diff: any;
 }
 
+export interface GestureContent {
+    readonly points: Array<Point>;
+    readonly width?: string;
+    readonly color?: string;
+}
+
+export interface MobileInkBoxTriggerContent {
+    readonly enableBox: boolean;
+}
+
 export namespace MessageStore {
     export const Foo = new Message<string>("Foo");
     export const Bar = new Message<string>("Bar");
@@ -51,6 +62,8 @@ export namespace MessageStore {
     export const GetDocument = new Message<string>("Get Document");
     export const DeleteAll = new Message<any>("Delete All");
     export const ConnectionTerminated = new Message<string>("Connection Terminated");
+    export const GesturePoints = new Message<GestureContent>("Gesture Points");
+    export const MobileInkBoxTrigger = new Message<boolean>("Trigger Mobile Ink Box");
 
     export const GetRefField = new Message<string>("Get Ref Field");
     export const GetRefFields = new Message<string[]>("Get Ref Fields");
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 578147d60..67777f023 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -1,5 +1,5 @@
 import { Utils } from "../../Utils";
-import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes } from "../Message";
+import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent } from "../Message";
 import { Client } from "../Client";
 import { Socket } from "socket.io";
 import { Database } from "../database";
@@ -54,6 +54,8 @@ export namespace WebSocket {
             Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));
             Utils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id));
             Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids));
+            Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content));
+            Utils.AddServerHandler(socket, MessageStore.MobileInkBoxTrigger, enableBox => processBoxTrigger(socket, enableBox));
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
 
@@ -68,6 +70,14 @@ export namespace WebSocket {
         logPort("websocket", socketPort);
     }
 
+    function processGesturePoints(socket: Socket, content: GestureContent) {
+        socket.broadcast.emit("receiveGesturePoints", content);
+    }
+
+    function processBoxTrigger(socket: Socket, enableBox: boolean) {
+        socket.broadcast.emit("receiveBoxTrigger", enableBox);
+    }
+
     function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) {
         const { ProjectCredentials } = GoogleCredentialsLoader;
         switch (query.type) {
-- 
cgit v1.2.3-70-g09d2


From d1c1e9f10b8523270a872c6f67df25ebb1d9aa25 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Mon, 27 Jan 2020 20:34:27 -0500
Subject: switching event type, some bugs :(

---
 src/client/views/Touchable.tsx                     | 65 +++++++++++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 src/client/views/nodes/DocumentView.tsx            | 94 +++++++++-------------
 3 files changed, 100 insertions(+), 60 deletions(-)

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

diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index 7800c4019..eed72e3c5 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -8,9 +8,12 @@ const HOLD_DURATION = 1000;
 
 export abstract class Touchable<T = {}> extends React.Component<T> {
     //private holdTimer: NodeJS.Timeout | undefined;
-    private holdTimer: NodeJS.Timeout | undefined;
+    holdTimer: NodeJS.Timeout | undefined;
     private moveDisposer?: InteractionUtils.MultiTouchEventDisposer;
     private endDisposer?: InteractionUtils.MultiTouchEventDisposer;
+    private holdMoveDisposer?: InteractionUtils.MultiTouchEventDisposer;
+    private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer;
+
 
     protected abstract multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     protected _touchDrag: boolean = false;
@@ -65,8 +68,10 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
                     //     clearTimeout(this.holdTimer)
                     //     this.holdTimer = undefined;
                     // }
-                    this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(te, me), HOLD_DURATION);
-                    // e.stopPropagation();
+                    console.log(this.holdTimer);
+                    if (!this.holdTimer) {
+                        this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(e, me), HOLD_DURATION);
+                    }
                     // console.log(this.holdTimer);
                     break;
                 case 2:
@@ -128,7 +133,12 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
             }
         }
         if (this.holdTimer) {
+            console.log(this.holdTimer);
             clearTimeout(this.holdTimer);
+            console.log(this.holdTimer);
+
+            this.holdTimer = undefined;
+            console.log(this.holdTimer);
             console.log("clear");
         }
         this._touchDrag = false;
@@ -174,10 +184,17 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         this.addEndListeners();
     }
 
-    handle1PointerHoldStart = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
+    handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
         e.stopPropagation();
-        e.preventDefault();
+        me.touchEvent.stopPropagation();
+        this.holdTimer = undefined;
         this.removeMoveListeners();
+        this.removeEndListeners();
+        this.removeHoldMoveListeners();
+        this.removeHoldEndListeners();
+        this.addHoldMoveListeners();
+        this.addHoldEndListeners();
+
     }
 
     addMoveListeners = () => {
@@ -200,6 +217,44 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         this.endDisposer && this.endDisposer();
     }
 
+    addHoldMoveListeners = () => {
+        const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
+        document.addEventListener("dashOnTouchHoldMove", handler);
+        this.holdMoveDisposer = () => document.removeEventListener("dashOnTouchHoldMove", handler);
+    }
+
+    addHoldEndListeners = () => {
+        const handler = (e: Event) => this.handle1PointerHoldEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
+        document.addEventListener("dashOnTouchHoldEnd", handler);
+        this.holdEndDisposer = () => document.removeEventListener("dashOnTouchHoldEnd", handler);
+    }
+
+    removeHoldMoveListeners = () => {
+        this.holdMoveDisposer && this.holdMoveDisposer();
+    }
+
+    removeHoldEndListeners = () => {
+        this.holdEndDisposer && this.holdEndDisposer();
+    }
+
+
+    handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+        e.stopPropagation();
+        me.touchEvent.stopPropagation();
+    }
+
+
+    handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+        e.stopPropagation();
+        me.touchEvent.stopPropagation();
+        this.removeHoldMoveListeners();
+        this.removeHoldEndListeners();
+
+        me.touchEvent.stopPropagation();
+        me.touchEvent.preventDefault();
+    }
+
+
     handleHandDown = (e: React.TouchEvent) => {
         // e.stopPropagation();
         // e.preventDefault();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index aaa585b55..2622d9b28 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -494,6 +494,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
             const pt1 = myTouches[0];
             const pt2 = myTouches[1];
+            console.log(myTouches);
 
             if (this.prevPoints.size === 2) {
                 const oldPoint1 = this.prevPoints.get(pt1.identifier);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0b6a284d6..ae2ee9e41 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -119,61 +119,45 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private _firstY: number = 0;
 
 
-    // handle1PointerHoldStart = (e: React.TouchEvent): any => {
-    //     this.onRadialMenu(e);
-    //     const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0];
-    //     this._firstX = pt.pageX;
-    //     this._firstY = pt.pageY;
-    //     e.stopPropagation();
-    //     e.preventDefault();
-
-    //     document.removeEventListener("touchmove", this.onTouch);
-    //     document.removeEventListener("touchmove", this.handle1PointerHoldMove);
-    //     document.addEventListener("touchmove", this.handle1PointerHoldMove);
-    //     document.removeEventListener("touchend", this.handle1PointerHoldEnd);
-    //     document.addEventListener("touchend", this.handle1PointerHoldEnd);
-    // }
-
-    // handle1PointerHoldMove = (e: TouchEvent): void => {
-    //     const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
-    //     if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
-    //         this.handleRelease();
-    //     }
-    //     document.removeEventListener("touchmove", this.handle1PointerHoldMove);
-    //     document.addEventListener("touchmove", this.handle1PointerHoldMove);
-    //     document.removeEventListener("touchend", this.handle1PointerHoldEnd);
-    //     document.addEventListener("touchend", this.handle1PointerHoldEnd);
-    // }
-
-    // handleRelease() {
-    //     RadialMenu.Instance.closeMenu();
-    //     document.removeEventListener("touchmove", this.handle1PointerHoldMove);
-    //     document.removeEventListener("touchend", this.handle1PointerHoldEnd);
-    // }
-
-    // handle1PointerHoldEnd = (e: TouchEvent): void => {
-    //     RadialMenu.Instance.closeMenu();
-    //     document.removeEventListener("touchmove", this.handle1PointerHoldMove);
-    //     document.removeEventListener("touchend", this.handle1PointerHoldEnd);
-    // }
-
-    // @action
-    // onRadialMenu = (e: React.TouchEvent): void => {
-    //     const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
-
-    //     RadialMenu.Instance.openMenu();
-
-    //     RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group", selected: -1 });
-    //     RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView?.removeDocument(this.props.Document), icon: "trash", selected: -1 });
-    //     RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, undefined, "onRight"), icon: "folder", selected: -1 });
-    //     RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
-
-    //     RadialMenu.Instance.displayMenu(pt.pageX - 15, pt.pageY - 15);
-    //     if (!SelectionManager.IsSelected(this, true)) {
-    //         SelectionManager.SelectDoc(this, false);
-    //     }
-    //     e.stopPropagation();
-    // }
+    handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
+        console.log("S");
+        this.onRadialMenu(e, me);
+        const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
+        this._firstX = pt.pageX;
+        this._firstY = pt.pageY;
+
+    }
+
+    handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+        console.log("K");
+        const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
+        if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
+            this.handle1PointerHoldEnd(e, me);
+        }
+    }
+
+    handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+        console.log("E");
+        RadialMenu.Instance.closeMenu();
+    }
+
+    @action
+    onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
+        const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
+
+        RadialMenu.Instance.openMenu();
+
+        RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group", selected: -1 });
+        RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView?.removeDocument(this.props.Document), icon: "trash", selected: -1 });
+        RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, undefined, "onRight"), icon: "folder", selected: -1 });
+        RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
+
+        RadialMenu.Instance.displayMenu(pt.pageX - 15, pt.pageY - 15);
+        if (!SelectionManager.IsSelected(this, true)) {
+            SelectionManager.SelectDoc(this, false);
+        }
+        e.stopPropagation();
+    }
 
     @action
     componentDidMount() {
-- 
cgit v1.2.3-70-g09d2


From ecf0f5b8f426db9e66c05e759f61294811b15fca Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Sat, 1 Feb 2020 14:56:19 -0500
Subject: mobile ink overlay is draggable from desktop

---
 src/client/DocServer.ts                            |  19 ++-
 src/client/util/InteractionUtils.ts                | 165 -------------------
 src/client/util/InteractionUtils.tsx               | 182 +++++++++++++++++++++
 src/client/views/GestureOverlay.scss               |   4 -
 src/client/views/GestureOverlay.tsx                |  25 +--
 src/client/views/InkingStroke.tsx                  |  17 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 -
 src/client/views/nodes/DocumentView.tsx            |   1 +
 src/mobile/MobileInkOverlay.scss                   |  38 +++++
 src/mobile/MobileInkOverlay.tsx                    |  91 +++++++++--
 src/mobile/MobileInterface.scss                    |   2 +-
 src/mobile/MobileInterface.tsx                     |  90 ++++++----
 src/server/Message.ts                              |  14 +-
 src/server/Websocket/Websocket.ts                  |   8 +-
 14 files changed, 393 insertions(+), 264 deletions(-)
 delete mode 100644 src/client/util/InteractionUtils.ts
 create mode 100644 src/client/util/InteractionUtils.tsx
 create mode 100644 src/mobile/MobileInkOverlay.scss

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index a1cb42df2..b20cd3521 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,5 +1,5 @@
 import * as OpenSocket from 'socket.io-client';
-import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkBoxContent } from "./../server/Message";
+import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPosition } from "./../server/Message";
 import { Opt, Doc } from '../new_fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
@@ -72,9 +72,13 @@ export namespace DocServer {
             Utils.Emit(_socket, MessageStore.GesturePoints, content);
         }
 
-        export function dispatchBoxTrigger(content: MobileInkBoxContent) {
+        export function dispatchOverlayTrigger(content: MobileInkOverlayContent) {
             // _socket.emit("dispatchBoxTrigger");
-            Utils.Emit(_socket, MessageStore.MobileInkBoxTrigger, content);
+            Utils.Emit(_socket, MessageStore.MobileInkOverlayTrigger, content);
+        }
+
+        export function dispatchOverlayPositionUpdate(content: UpdateMobileInkOverlayPosition) {
+            Utils.Emit(_socket, MessageStore.UpdateMobileInkOverlayPosition, content);
         }
 
     }
@@ -100,13 +104,18 @@ export namespace DocServer {
         Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, () => {
             alert("Your connection to the server has been terminated.");
         });
+
+        // mobile ink overlay socket events to communicate between mobile view and desktop view
         _socket.addEventListener("receiveGesturePoints", (content: GestureContent) => {
             MobileInkOverlay.Instance.drawStroke(content);
         });
-        _socket.addEventListener("receiveBoxTrigger", (content: MobileInkBoxContent) => {
-            GestureOverlay.Instance.enableMobileInkBox(content);
+        _socket.addEventListener("receiveOverlayTrigger", (content: MobileInkOverlayContent) => {
+            GestureOverlay.Instance.enableMobileInkOverlay(content);
             MobileInkOverlay.Instance.initMobileInkOverlay(content);
         });
+        _socket.addEventListener("updateMobileOverlayPosition", (content: UpdateMobileInkOverlayPosition) => {
+            MobileInkOverlay.Instance.updatePosition(content);
+        });
     }
 
     function errorFunc(): never {
diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts
deleted file mode 100644
index 76b43da3c..000000000
--- a/src/client/util/InteractionUtils.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-export namespace InteractionUtils {
-    export const MOUSETYPE = "mouse";
-    export const TOUCHTYPE = "touch";
-    export const PENTYPE = "pen";
-    export const ERASERTYPE = "eraser";
-
-    const POINTER_PEN_BUTTON = -1;
-    const REACT_POINTER_PEN_BUTTON = 0;
-    const ERASER_BUTTON = 5;
-
-    export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] {
-        const myTouches = new Array<React.Touch>();
-        for (let i = 0; i < e.targetTouches.length; i++) {
-            const pt: any = e.targetTouches.item(i);
-            if (pt && prevPoints.has(pt.identifier)) {
-                if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) {
-                    myTouches.push(pt);
-                }
-            }
-        }
-        return myTouches;
-    }
-
-    export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean {
-        switch (type) {
-            // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
-            case PENTYPE:
-                return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
-            case ERASERTYPE:
-                return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
-            default:
-                return e.pointerType === type;
-        }
-    }
-
-    export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number {
-        return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2));
-    }
-
-    /**
-     * Returns the centroid of an n-arbitrary long list of points (takes the average the x and y components of each point)
-     * @param pts - n-arbitrary long list of points
-     */
-    export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } {
-        const centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length;
-        const centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length;
-        return { X: centerX, Y: centerY };
-    }
-
-    /**
-     * Returns -1 if pinching out, 0 if not pinching, and 1 if pinching in
-     * @param pt1 - new point that corresponds to oldPoint1
-     * @param pt2 - new point that corresponds to oldPoint2
-     * @param oldPoint1 - previous point 1
-     * @param oldPoint2 - previous point 2
-     */
-    export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
-        const threshold = 4;
-        const oldDist = TwoPointEuclidist(oldPoint1, oldPoint2);
-        const newDist = TwoPointEuclidist(pt1, pt2);
-
-        /** if they have the same sign, then we are either pinching in or out.
-          * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch)
-          * so that it can still pan without freaking out
-          */
-        if (Math.sign(oldDist) === Math.sign(newDist) && Math.abs(oldDist - newDist) > threshold) {
-            return Math.sign(oldDist - newDist);
-        }
-        return 0;
-    }
-
-    /**
-     * Returns -1 if pinning and pinching out, 0 if not pinning, and 1 if pinching in
-     * @param pt1 - new point that corresponds to oldPoint1
-     * @param pt2 - new point that corresponds to oldPoint2
-     * @param oldPoint1 - previous point 1
-     * @param oldPoint2 - previous point 2
-     */
-    export function Pinning(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
-        const threshold = 4;
-
-        const pt1Dist = TwoPointEuclidist(oldPoint1, pt1);
-        const pt2Dist = TwoPointEuclidist(oldPoint2, pt2);
-
-        const pinching = Pinching(pt1, pt2, oldPoint1, oldPoint2);
-
-        if (pinching !== 0) {
-            if ((pt1Dist < threshold && pt2Dist > threshold) || (pt1Dist > threshold && pt2Dist < threshold)) {
-                return pinching;
-            }
-        }
-        return 0;
-    }
-
-    export function IsDragging(oldTouches: Map<number, React.Touch>, newTouches: React.Touch[], leniency: number): boolean {
-        for (const touch of newTouches) {
-            if (touch) {
-                const oldTouch = oldTouches.get(touch.identifier);
-                if (oldTouch) {
-                    if (TwoPointEuclidist(touch, oldTouch) >= leniency) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    // These might not be very useful anymore, but I'll leave them here for now -syip2
-    {
-
-
-        /**
-         * Returns the type of Touch Interaction from a list of points.
-         * Also returns any data that is associated with a Touch Interaction
-         * @param pts - List of points
-         */
-        // export function InterpretPointers(pts: React.Touch[]): { type: Opt<TouchInteraction>, data?: any } {
-        //     const leniency = 200;
-        //     switch (pts.length) {
-        //         case 1:
-        //             return { type: OneFinger };
-        //         case 2:
-        //             return { type: TwoSeperateFingers };
-        //         case 3:
-        //             let pt1 = pts[0];
-        //             let pt2 = pts[1];
-        //             let pt3 = pts[2];
-        //             if (pt1 && pt2 && pt3) {
-        //                 let dist12 = TwoPointEuclidist(pt1, pt2);
-        //                 let dist23 = TwoPointEuclidist(pt2, pt3);
-        //                 let dist13 = TwoPointEuclidist(pt1, pt3);
-        //                 console.log(`distances: ${dist12}, ${dist23}, ${dist13}`);
-        //                 let dist12close = dist12 < leniency;
-        //                 let dist23close = dist23 < leniency;
-        //                 let dist13close = dist13 < leniency;
-        //                 let xor2313 = dist23close ? !dist13close : dist13close;
-        //                 let xor = dist12close ? !xor2313 : xor2313;
-        //                 // three input xor because javascript doesn't have logical xor's
-        //                 if (xor) {
-        //                     let points: number[] = [];
-        //                     let min = Math.min(dist12, dist23, dist13);
-        //                     switch (min) {
-        //                         case dist12:
-        //                             points = [0, 1, 2];
-        //                             break;
-        //                         case dist23:
-        //                             points = [1, 2, 0];
-        //                             break;
-        //                         case dist13:
-        //                             points = [0, 2, 1];
-        //                             break;
-        //                     }
-        //                     return { type: TwoToOneFingers, data: points };
-        //                 }
-        //                 else {
-        //                     return { type: ThreeSeperateFingers, data: null };
-        //                 }
-        //             }
-        //         default:
-        //             return { type: undefined };
-        //     }
-        // }
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
new file mode 100644
index 000000000..34c7cfa74
--- /dev/null
+++ b/src/client/util/InteractionUtils.tsx
@@ -0,0 +1,182 @@
+import React = require("react");
+
+export namespace InteractionUtils {
+    export const MOUSETYPE = "mouse";
+    export const TOUCHTYPE = "touch";
+    export const PENTYPE = "pen";
+    export const ERASERTYPE = "eraser";
+
+    const POINTER_PEN_BUTTON = -1;
+    const REACT_POINTER_PEN_BUTTON = 0;
+    const ERASER_BUTTON = 5;
+
+    export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] {
+        const myTouches = new Array<React.Touch>();
+        for (let i = 0; i < e.targetTouches.length; i++) {
+            const pt: any = e.targetTouches.item(i);
+            if (pt && prevPoints.has(pt.identifier)) {
+                if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) {
+                    myTouches.push(pt);
+                }
+            }
+        }
+        return myTouches;
+    }
+
+    // TODO: find a way to reference this function from InkingStroke instead of copy pastign here. copied bc of weird error when on mobile view
+    export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number) {
+        const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, "");
+        return (
+            <polyline
+                points={pts}
+                style={{
+                    fill: "none",
+                    stroke: color,
+                    strokeWidth: width
+                }}
+            />
+        );
+    }
+
+    export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean {
+        switch (type) {
+            // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
+            case PENTYPE:
+                return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
+            case ERASERTYPE:
+                return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
+            default:
+                return e.pointerType === type;
+        }
+    }
+
+    export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number {
+        return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2));
+    }
+
+    /**
+     * Returns the centroid of an n-arbitrary long list of points (takes the average the x and y components of each point)
+     * @param pts - n-arbitrary long list of points
+     */
+    export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } {
+        const centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length;
+        const centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length;
+        return { X: centerX, Y: centerY };
+    }
+
+    /**
+     * Returns -1 if pinching out, 0 if not pinching, and 1 if pinching in
+     * @param pt1 - new point that corresponds to oldPoint1
+     * @param pt2 - new point that corresponds to oldPoint2
+     * @param oldPoint1 - previous point 1
+     * @param oldPoint2 - previous point 2
+     */
+    export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
+        const threshold = 4;
+        const oldDist = TwoPointEuclidist(oldPoint1, oldPoint2);
+        const newDist = TwoPointEuclidist(pt1, pt2);
+
+        /** if they have the same sign, then we are either pinching in or out.
+          * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch)
+          * so that it can still pan without freaking out
+          */
+        if (Math.sign(oldDist) === Math.sign(newDist) && Math.abs(oldDist - newDist) > threshold) {
+            return Math.sign(oldDist - newDist);
+        }
+        return 0;
+    }
+
+    /**
+     * Returns -1 if pinning and pinching out, 0 if not pinning, and 1 if pinching in
+     * @param pt1 - new point that corresponds to oldPoint1
+     * @param pt2 - new point that corresponds to oldPoint2
+     * @param oldPoint1 - previous point 1
+     * @param oldPoint2 - previous point 2
+     */
+    export function Pinning(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
+        const threshold = 4;
+
+        const pt1Dist = TwoPointEuclidist(oldPoint1, pt1);
+        const pt2Dist = TwoPointEuclidist(oldPoint2, pt2);
+
+        const pinching = Pinching(pt1, pt2, oldPoint1, oldPoint2);
+
+        if (pinching !== 0) {
+            if ((pt1Dist < threshold && pt2Dist > threshold) || (pt1Dist > threshold && pt2Dist < threshold)) {
+                return pinching;
+            }
+        }
+        return 0;
+    }
+
+    export function IsDragging(oldTouches: Map<number, React.Touch>, newTouches: React.Touch[], leniency: number): boolean {
+        for (const touch of newTouches) {
+            if (touch) {
+                const oldTouch = oldTouches.get(touch.identifier);
+                if (oldTouch) {
+                    if (TwoPointEuclidist(touch, oldTouch) >= leniency) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    // These might not be very useful anymore, but I'll leave them here for now -syip2
+    {
+
+
+        /**
+         * Returns the type of Touch Interaction from a list of points.
+         * Also returns any data that is associated with a Touch Interaction
+         * @param pts - List of points
+         */
+        // export function InterpretPointers(pts: React.Touch[]): { type: Opt<TouchInteraction>, data?: any } {
+        //     const leniency = 200;
+        //     switch (pts.length) {
+        //         case 1:
+        //             return { type: OneFinger };
+        //         case 2:
+        //             return { type: TwoSeperateFingers };
+        //         case 3:
+        //             let pt1 = pts[0];
+        //             let pt2 = pts[1];
+        //             let pt3 = pts[2];
+        //             if (pt1 && pt2 && pt3) {
+        //                 let dist12 = TwoPointEuclidist(pt1, pt2);
+        //                 let dist23 = TwoPointEuclidist(pt2, pt3);
+        //                 let dist13 = TwoPointEuclidist(pt1, pt3);
+        //                 console.log(`distances: ${dist12}, ${dist23}, ${dist13}`);
+        //                 let dist12close = dist12 < leniency;
+        //                 let dist23close = dist23 < leniency;
+        //                 let dist13close = dist13 < leniency;
+        //                 let xor2313 = dist23close ? !dist13close : dist13close;
+        //                 let xor = dist12close ? !xor2313 : xor2313;
+        //                 // three input xor because javascript doesn't have logical xor's
+        //                 if (xor) {
+        //                     let points: number[] = [];
+        //                     let min = Math.min(dist12, dist23, dist13);
+        //                     switch (min) {
+        //                         case dist12:
+        //                             points = [0, 1, 2];
+        //                             break;
+        //                         case dist23:
+        //                             points = [1, 2, 0];
+        //                             break;
+        //                         case dist13:
+        //                             points = [0, 2, 1];
+        //                             break;
+        //                     }
+        //                     return { type: TwoToOneFingers, data: points };
+        //                 }
+        //                 else {
+        //                     return { type: ThreeSeperateFingers, data: null };
+        //                 }
+        //             }
+        //         default:
+        //             return { type: undefined };
+        //     }
+        // }
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index 2996b7073..31601efd4 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -5,8 +5,4 @@
     top: 0;
     left: 0;
     touch-action: none;
-}
-
-.mobileInkOverlay {
-    border: 5px dashed red;
 }
\ No newline at end of file
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 81284b543..a01a86b53 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -19,7 +19,7 @@ import Palette from "./Palette";
 import MobileInterface from "../../mobile/MobileInterface";
 import { MainView } from "./MainView";
 import { DocServer } from "../DocServer";
-import { GestureContent, MobileInkBoxContent } from "../../server/Message";
+import { GestureContent, MobileInkOverlayContent } from "../../server/Message";
 import { Point } from "../northstar/model/idea/idea";
 import MobileInkOverlay from "../../mobile/MobileInkOverlay";
 
@@ -217,21 +217,6 @@ export default class GestureOverlay extends Touchable {
         return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top };
     }
 
-    // TODO: find a way to reference this function from InkingStroke instead of copy pastign here. copied bc of weird error when on mobile view
-    CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color?: string, width?: number) {
-        const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, "");
-        return (
-            <polyline
-                points={pts}
-                style={{
-                    fill: "none",
-                    stroke: color ?? InkingControl.Instance.selectedColor,
-                    strokeWidth: width ?? InkingControl.Instance.selectedWidth
-                }}
-            />
-        );
-    }
-
     @computed get currentStroke() {
         if (this._points.length <= 1) {
             return (null);
@@ -241,23 +226,23 @@ export default class GestureOverlay extends Touchable {
 
         return (
             <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
-                {this.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)}
+                {InteractionUtils.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)}
             </svg>
         );
     }
 
     @action
-    enableMobileInkBox = (content: MobileInkBoxContent) => {
-        this.showMobileInkOverlay = content.enableBox;
+    enableMobileInkOverlay = (content: MobileInkOverlayContent) => {
+        this.showMobileInkOverlay = content.enableOverlay;
     }
 
     render() {
         return (
             <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart}>
+                {this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
                 {this.currentStroke}
                 {this.props.children}
                 {this._palette}
-                {this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
             </div>);
     }
 }
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 8b346d5d9..aca507147 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -10,24 +10,11 @@ import "./InkingStroke.scss";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
 import React = require("react");
 import { TraceMobx } from "../../new_fields/util";
+import { InteractionUtils } from "../util/InteractionUtils";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
 
-export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color?: string, width?: number) {
-    const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, "");
-    return (
-        <polyline
-            points={pts}
-            style={{
-                fill: "none",
-                stroke: color ?? InkingControl.Instance.selectedColor,
-                strokeWidth: width ?? InkingControl.Instance.selectedWidth
-            }}
-        />
-    );
-}
-
 @observer
 export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocument>(InkDocument) {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
@@ -44,7 +31,7 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
         const top = Math.min(...ys);
         const right = Math.max(...xs);
         const bottom = Math.max(...ys);
-        const points = CreatePolyline(data, left, top, this.Document.color, this.Document.strokeWidth);
+        const points = InteractionUtils.CreatePolyline(data, left, top, this.Document.color ?? InkingControl.Instance.selectedColor, this.Document.strokeWidth ?? parseInt(InkingControl.Instance.selectedWidth));
         const width = right - left;
         const height = bottom - top;
         const scaleX = this.PanelWidth / width;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bbfd22f50..4d216cb2a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -26,7 +26,6 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"
 import { ContextMenu } from "../../ContextMenu";
 import { ContextMenuProps } from "../../ContextMenuItem";
 import { InkingControl } from "../../InkingControl";
-import { CreatePolyline } from "../../InkingStroke";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { FormattedTextBox } from "../../nodes/FormattedTextBox";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2e0ae09ba..03f37595f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -443,6 +443,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
             if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
                 e.stopPropagation();
+                // TODO: check here for panning/inking
             }
             return;
         }
diff --git a/src/mobile/MobileInkOverlay.scss b/src/mobile/MobileInkOverlay.scss
new file mode 100644
index 000000000..2e45d0441
--- /dev/null
+++ b/src/mobile/MobileInkOverlay.scss
@@ -0,0 +1,38 @@
+.mobileInkOverlay {
+    border: 5px dashed red;
+}
+
+.mobileInkOverlay-border {
+    background-color: rgba(0, 255, 0, .4);
+    position: absolute;
+    pointer-events: auto;
+    cursor: pointer;
+
+    &.top {
+        width: calc(100% + 20px);
+        height: 10px;
+        top: -10px;
+        left: -10px;
+    }
+
+    &.left {
+        width: 10px;
+        height: calc(100% + 20px);
+        top: -10px;
+        left: -10px;
+    }
+
+    &.right {
+        width: 10px;
+        height: calc(100% + 20px);
+        top: -10px;
+        right: -10px;
+    }
+
+    &.bottom {
+        width: calc(100% + 20px);
+        height: 10px;
+        bottom: -10px;
+        left: -10px;
+    }
+}
\ No newline at end of file
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index 71dd20c51..5efc7b83a 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -1,8 +1,9 @@
 import React = require('react');
 import { observer } from "mobx-react";
-import { MobileInkBoxContent, GestureContent } from "../server/Message";
+import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPosition } from "../server/Message";
 import { observable, action } from "mobx";
 import { GestureUtils } from "../pen-gestures/GestureUtils";
+import "./MobileInkOverlay.scss";
 
 
 @observer
@@ -15,6 +16,11 @@ export default class MobileInkOverlay extends React.Component {
     @observable private _x: number = -300;
     @observable private _y: number = -300;
 
+    @observable private _offsetX: number = 0;
+    @observable private _offsetY: number = 0;
+    @observable private _isDragging: boolean = false;
+    private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+
     constructor(props: Readonly<{}>) {
         super(props);
         MobileInkOverlay.Instance = this;
@@ -28,18 +34,27 @@ export default class MobileInkOverlay extends React.Component {
     }
 
     @action
-    initMobileInkOverlay(content: MobileInkBoxContent) {
+    initMobileInkOverlay(content: MobileInkOverlayContent) {
         const { width, height } = content;
         const scaledSize = this.initialSize(width ? width : 0, height ? height : 0);
-        this._width = scaledSize.width;
-        this._height = scaledSize.height;
-        this._scale = scaledSize.scale;
+        this._width = scaledSize.width * .5;
+        this._height = scaledSize.height * .5;
+        this._scale = .5; //scaledSize.scale;
         this._x = 300; // TODO: center on screen
         this._y = 25; // TODO: center on screen
     }
 
+    @action
+    updatePosition(content: UpdateMobileInkOverlayPosition) {
+        const { dx, dy, dsize } = content;
+        console.log(dx, dy, dsize);
+    }
+
     drawStroke = (content: GestureContent) => {
+        // TODO: figure out why strokes drawn in corner of mobile interface dont get inserted
+
         const { points, bounds } = content;
+        console.log("received points", points, bounds);
 
         const B = {
             right: (bounds.right * this._scale) + this._x,
@@ -65,16 +80,66 @@ export default class MobileInkOverlay extends React.Component {
         );
     }
 
+    @action
+    dragStart = (e: React.PointerEvent) => {
+        console.log("pointer down");
+        document.removeEventListener("pointermove", this.dragging);
+        document.removeEventListener("pointerup", this.dragEnd);
+        document.addEventListener("pointermove", this.dragging);
+        document.addEventListener("pointerup", this.dragEnd);
+
+        this._isDragging = true;
+        this._offsetX = e.pageX - this._mainCont.current!.getBoundingClientRect().left;
+        this._offsetY = e.pageY - this._mainCont.current!.getBoundingClientRect().top;
+
+        e.preventDefault();
+        e.stopPropagation();
+    }
+
+    @action
+    dragging = (e: PointerEvent) => {
+        const x = e.pageX - this._offsetX;
+        const y = e.pageY - this._offsetY;
+
+        // TODO: don't allow drag over library?
+        this._x = Math.min(Math.max(x, 0), window.innerWidth - this._width);
+        this._y = Math.min(Math.max(y, 0), window.innerHeight - this._height);
+
+        e.preventDefault();
+        e.stopPropagation();
+    }
+
+    @action
+    dragEnd = (e: PointerEvent) => {
+        document.removeEventListener("pointermove", this.dragging);
+        document.removeEventListener("pointerup", this.dragEnd);
+
+        this._isDragging = false;
+
+        e.preventDefault();
+        e.stopPropagation();
+    }
+
     render() {
+
         return (
-            <div className="mobileInkOverlay" style={{
-                width: this._width,
-                height: this._height,
-                position: "absolute",
-                transform: `translate(${this._x}px, ${this._y}px)`,
-                zIndex: 30000,
-                pointerEvents: "none"
-            }}></div>
+            <div className="mobileInkOverlay"
+                style={{
+                    width: this._width,
+                    height: this._height,
+                    position: "absolute",
+                    transform: `translate(${this._x}px, ${this._y}px)`,
+                    zIndex: 30000,
+                    pointerEvents: "none",
+                    borderStyle: this._isDragging ? "solid" : "dashed"
+                }}
+                ref={this._mainCont}
+            >
+                <div className="mobileInkOverlay-border top" onPointerDown={this.dragStart}></div>
+                <div className="mobileInkOverlay-border bottom" onPointerDown={this.dragStart}></div>
+                <div className="mobileInkOverlay-border left" onPointerDown={this.dragStart}></div>
+                <div className="mobileInkOverlay-border right" onPointerDown={this.dragStart}></div>
+            </div>
         );
     }
 }
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss
index e4cc919a5..8abe5a40d 100644
--- a/src/mobile/MobileInterface.scss
+++ b/src/mobile/MobileInterface.scss
@@ -1,4 +1,4 @@
-.mobileInterface-topButtons {
+.mobileInterface-inkInterfaceButtons {
     position: absolute;
     display: flex;
     justify-content: space-between;
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 4840ea374..b191b3afb 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -10,7 +10,7 @@ import { DocumentView } from '../client/views/nodes/DocumentView';
 import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils';
 import { Transform } from '../client/util/Transform';
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck } from '@fortawesome/free-solid-svg-icons';
+import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck, faLongArrowAltLeft } from '@fortawesome/free-solid-svg-icons';
 import { Scripting } from '../client/util/Scripting';
 import { CollectionFreeFormView } from '../client/views/collections/collectionFreeForm/CollectionFreeFormView';
 import GestureOverlay from '../client/views/GestureOverlay';
@@ -23,7 +23,7 @@ import { DateField } from '../new_fields/DateField';
 import { GestureUtils } from '../pen-gestures/GestureUtils';
 import { DocServer } from '../client/DocServer';
 
-library.add(faTrash, faCheck);
+library.add(faLongArrowAltLeft);
 
 @observer
 export default class MobileInterface extends React.Component {
@@ -68,8 +68,8 @@ export default class MobileInterface extends React.Component {
                     InkingControl.Instance.switchTool(InkTool.Pen);
                     this.drawingInk = true;
 
-                    DocServer.Mobile.dispatchBoxTrigger({
-                        enableBox: true,
+                    DocServer.Mobile.dispatchOverlayTrigger({
+                        enableOverlay: true,
                         width: window.innerWidth,
                         height: window.innerHeight
                     });
@@ -112,12 +112,12 @@ export default class MobileInterface extends React.Component {
         return "hello";
     }
 
-    onClick = (e: React.MouseEvent) => {
+    onBack = (e: React.MouseEvent) => {
         this.switchCurrentView("main");
         InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool
 
-        DocServer.Mobile.dispatchBoxTrigger({
-            enableBox: false,
+        DocServer.Mobile.dispatchOverlayTrigger({
+            enableOverlay: false,
             width: window.innerWidth,
             height: window.innerHeight
         });
@@ -126,37 +126,61 @@ export default class MobileInterface extends React.Component {
         this.drawingInk = false;
     }
 
+    shiftLeft = (e: React.MouseEvent) => {
+        DocServer.Mobile.dispatchOverlayPositionUpdate({
+            dx: -10
+        });
+    }
+
+    shiftRight = (e: React.MouseEvent) => {
+        DocServer.Mobile.dispatchOverlayPositionUpdate({
+            dx: 10
+        });
+    }
+
     @computed
     get inkContent() {
+        // TODO: support panning and zooming
+        // TODO: handle moving of ink strokes
         if (this.mainContainer) {
             return (
-                <GestureOverlay>
-                    <div className="mobileInterface-topButtons">
-                        <button className="mobileInterface-button cancel" onClick={this.onClick} title="Cancel drawing"><FontAwesomeIcon icon="trash" /></button>
-                        <button className="mobileInterface-button cancel" onClick={this.onClick} title="Insert drawing"><FontAwesomeIcon icon="check" /></button>
+                <div className="mobileInterface">
+                    <div className="mobileInterface-inkInterfaceButtons">
+                        <div className="navButtons">
+                            <button className="mobileInterface-button cancel" onClick={this.onBack} title="Cancel drawing"><FontAwesomeIcon icon="long-arrow-alt-left" /></button>
+                        </div>
+                        <div className="inkSettingButtons">
+                            <button className="mobileInterface-button cancel" onClick={this.onBack} title="Cancel drawing"><FontAwesomeIcon icon="long-arrow-alt-left" /></button>
+                        </div>
+                        <div className="navButtons">
+                            <button className="mobileInterface-button" onClick={this.shiftLeft} title="Shift left">left</button>
+                            <button className="mobileInterface-button" onClick={this.shiftRight} title="Shift right">right</button>
+                        </div>
                     </div>
-                    <CollectionView
-                        Document={this.mainContainer}
-                        DataDoc={undefined}
-                        LibraryPath={emptyPath}
-                        fieldKey={""}
-                        addDocTab={returnFalse}
-                        pinToPres={emptyFunction}
-                        PanelHeight={() => window.innerHeight}
-                        PanelWidth={() => window.innerWidth}
-                        focus={emptyFunction}
-                        isSelected={returnFalse}
-                        select={emptyFunction}
-                        active={returnFalse}
-                        ContentScaling={returnOne}
-                        whenActiveChanged={returnFalse}
-                        ScreenToLocalTransform={Transform.Identity}
-                        ruleProvider={undefined}
-                        renderDepth={0}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}>
-                    </CollectionView>
-                </GestureOverlay>
+                    <GestureOverlay>
+                        <CollectionView
+                            Document={this.mainContainer}
+                            DataDoc={undefined}
+                            LibraryPath={emptyPath}
+                            fieldKey={""}
+                            addDocTab={returnFalse}
+                            pinToPres={emptyFunction}
+                            PanelHeight={() => window.innerHeight}
+                            PanelWidth={() => window.innerWidth}
+                            focus={emptyFunction}
+                            isSelected={returnFalse}
+                            select={emptyFunction}
+                            active={returnFalse}
+                            ContentScaling={returnOne}
+                            whenActiveChanged={returnFalse}
+                            ScreenToLocalTransform={Transform.Identity}
+                            ruleProvider={undefined}
+                            renderDepth={0}
+                            ContainingCollectionView={undefined}
+                            ContainingCollectionDoc={undefined}>
+                        </CollectionView>
+                    </GestureOverlay>
+                </div>
             );
         }
     }
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 1958286df..064a19653 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -50,12 +50,18 @@ export interface GestureContent {
     readonly color?: string;
 }
 
-export interface MobileInkBoxContent {
-    readonly enableBox: boolean;
+export interface MobileInkOverlayContent {
+    readonly enableOverlay: boolean;
     readonly width?: number;
     readonly height?: number;
 }
 
+export interface UpdateMobileInkOverlayPosition {
+    readonly dx?: number;
+    readonly dy?: number;
+    readonly dsize?: number;
+}
+
 export namespace MessageStore {
     export const Foo = new Message<string>("Foo");
     export const Bar = new Message<string>("Bar");
@@ -65,8 +71,10 @@ export namespace MessageStore {
     export const GetDocument = new Message<string>("Get Document");
     export const DeleteAll = new Message<any>("Delete All");
     export const ConnectionTerminated = new Message<string>("Connection Terminated");
+
     export const GesturePoints = new Message<GestureContent>("Gesture Points");
-    export const MobileInkBoxTrigger = new Message<MobileInkBoxContent>("Trigger Mobile Ink Box");
+    export const MobileInkOverlayTrigger = new Message<MobileInkOverlayContent>("Trigger Mobile Ink Overlay");
+    export const UpdateMobileInkOverlayPosition = new Message<UpdateMobileInkOverlayPosition>("Update Mobile Ink Overlay Position");
 
     export const GetRefField = new Message<string>("Get Ref Field");
     export const GetRefFields = new Message<string[]>("Get Ref Fields");
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 16e34bdfc..fe253400c 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -1,5 +1,5 @@
 import { Utils } from "../../Utils";
-import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkBoxContent } from "../Message";
+import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent } from "../Message";
 import { Client } from "../Client";
 import { Socket } from "socket.io";
 import { Database } from "../database";
@@ -55,7 +55,7 @@ export namespace WebSocket {
             Utils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id));
             Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids));
             Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content));
-            Utils.AddServerHandler(socket, MessageStore.MobileInkBoxTrigger, content => processBoxTrigger(socket, content));
+            Utils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content));
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
 
@@ -74,8 +74,8 @@ export namespace WebSocket {
         socket.broadcast.emit("receiveGesturePoints", content);
     }
 
-    function processBoxTrigger(socket: Socket, content: MobileInkBoxContent) {
-        socket.broadcast.emit("receiveBoxTrigger", content);
+    function processOverlayTrigger(socket: Socket, content: MobileInkOverlayContent) {
+        socket.broadcast.emit("receiveOverlayTrigger", content);
     }
 
     function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) {
-- 
cgit v1.2.3-70-g09d2


From 1007cfb325f2dcddc4365538e4b354d06eb85f2f Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Tue, 4 Feb 2020 18:12:47 -0500
Subject: ok so the toolglass is working, but it's super slow... i'll fix that
 later lol

---
 src/client/views/GestureOverlay.scss               |  13 +-
 src/client/views/GestureOverlay.tsx                | 151 ++++++++++++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   6 +-
 src/pen-gestures/GestureUtils.ts                   |  11 +-
 .../authentication/models/current_user_utils.ts    |   2 +-
 5 files changed, 109 insertions(+), 74 deletions(-)

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

diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index 60b53c528..f425c438e 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -16,11 +16,13 @@
 .inkToTextDoc-cont {
     position: absolute;
     width: 300px;
-    height: 300px;
     overflow: hidden;
 
     .inkToTextDoc-scroller {
         overflow: visible;
+        position: absolute;
+        width: 100%;
+        left: -24px;
 
         .collectionView {
             overflow: visible;
@@ -30,6 +32,15 @@
             }
         }
     }
+
+    .shadow {
+        width: 100%;
+        height: calc(100% - 25px);
+        position: absolute;
+        top: 25px;
+        background-color: black;
+        opacity: 0.2;
+    }
 }
 
 .filter-cont {
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 0c2e9e1bc..0fd6f3dba 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -2,7 +2,7 @@ import React = require("react");
 import { Touchable } from "./Touchable";
 import { observer } from "mobx-react";
 import "./GestureOverlay.scss";
-import { computed, observable, action, runInAction, IReactionDisposer, reaction, flow } from "mobx";
+import { computed, observable, action, runInAction, IReactionDisposer, reaction, flow, trace } from "mobx";
 import { GestureUtils } from "../../pen-gestures/GestureUtils";
 import { InteractionUtils } from "../util/InteractionUtils";
 import { InkingControl } from "./InkingControl";
@@ -25,6 +25,8 @@ import htmlToImage from "html-to-image";
 import { ScriptField } from "../../new_fields/ScriptField";
 import { listSpec } from "../../new_fields/Schema";
 import { List } from "../../new_fields/List";
+import { CollectionViewType } from "./collections/CollectionView";
+import InkCanvas from "./InkCanvas";
 
 @observer
 export default class GestureOverlay extends Touchable {
@@ -63,6 +65,11 @@ export default class GestureOverlay extends Touchable {
         GestureOverlay.Instance = this;
     }
 
+    componentDidMount = () => {
+        this._thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc));
+        this._inkToTextDoc = FieldValue(Cast(this._thumbDoc?.inkToTextDoc, Doc));
+    }
+
     getNewTouches(e: React.TouchEvent | TouchEvent) {
         const ntt: (React.Touch | Touch)[] = Array.from(e.targetTouches);
         const nct: (React.Touch | Touch)[] = Array.from(e.changedTouches);
@@ -313,14 +320,23 @@ export default class GestureOverlay extends Touchable {
             this._palette = undefined;
             this.thumbIdentifier = undefined;
             this._thumbDoc = undefined;
-            this._strokes.forEach(s => {
-                this.dispatchGesture(GestureUtils.Gestures.Stroke, s);
-            });
-            this._strokes = [];
-            if (NumCast(this._inkToTextDoc?.selectedIndex) > 0) {
-                const selectedButton = this._possibilities[NumCast(this._inkToTextDoc?.selectedIndex) - 1];
-                Cast(selectedButton?.proto?.onClick, ScriptField)?.script.run({ this: selectedButton.proto }, console.log);
+
+            let scriptWorked = false;
+            if (NumCast(this._inkToTextDoc?.selectedIndex) > -1) {
+                const selectedButton = this._possibilities[NumCast(this._inkToTextDoc?.selectedIndex)];
+                if (Cast(selectedButton?.proto?.onClick, ScriptField)?.script.run({ this: selectedButton }, console.log).success) {
+                    scriptWorked = true;
+                    console.log("success");
+                };
+                console.log(Cast(selectedButton?.proto?.onClick, ScriptField)?.script);
+            }
+
+            if (!scriptWorked) {
+                this._strokes.forEach(s => {
+                    this.dispatchGesture(GestureUtils.Gestures.Stroke, s);
+                });
             }
+            this._strokes = [];
             this._possibilities = [];
             document.removeEventListener("touchend", this.handleHandUp);
         }
@@ -409,14 +425,14 @@ export default class GestureOverlay extends Touchable {
                         const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
                         const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
                         runInAction(() => {
-                            console.log(possibilities);
                             const buttons = possibilities.map(p => Docs.Create.ButtonDocument({
-                                _height: r - l, _width: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", x: l, y: t,
+                                _height: 25, _width: r - l, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", x: l, y: t, _viewType: CollectionViewType.Linear,
+                                isExpanded: true,
                                 title: p, fontSize: 10, letterSpacing: "0px", textTransform: "unset", boxShadow: ".5px .5px 0px rgb(34, 34, 34)",
-                                onClick: ScriptField.MakeScript('Docs.Create.TextDocument(this.title, {_width: 200, _height: 35, x: this.x, y: this.y})')
+                                onClick: ScriptField.MakeScript('createText(this.proto.title, this.x, this.y)')
                             }));
-                            if (this._inkToTextDoc) {
-                                this._inkToTextDoc.data = new List<Doc>(buttons);
+                            if (this._inkToTextDoc && this._thumbDoc) {
+                                this._inkToTextDoc = this._thumbDoc.inkToTextDoc = Docs.Create.LinearDocument(buttons, { _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", isExpanded: true, flexDirection: "column" });
                                 this._inkToTextDoc.selectedIndex = 0;
                             }
                             this._possibilities = buttons;
@@ -459,7 +475,7 @@ export default class GestureOverlay extends Touchable {
         document.removeEventListener("pointerup", this.onPointerUp);
     }
 
-    dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData) => {
+    dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, data?: any) => {
         const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y);
         target?.dispatchEvent(
             new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
@@ -468,7 +484,8 @@ export default class GestureOverlay extends Touchable {
                     detail: {
                         points: stroke ?? this._points,
                         gesture: gesture,
-                        bounds: this.getBounds(stroke ?? this._points)
+                        bounds: this.getBounds(stroke ?? this._points),
+                        text: data
                     }
                 }
             )
@@ -489,27 +506,21 @@ export default class GestureOverlay extends Touchable {
         return this.getBounds(this._points);
     }
 
-    @computed get currentStrokes() {
+    @computed get elements() {
         const B = this.svgBounds;
+        return [
+            this.props.children,
+            this._palette,
 
-        return (
             [this._strokes.map(l => {
                 const b = this.getBounds(l);
                 return <svg width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
-                    {InteractionUtils.CreatePolyline(l, b.left, b.top, this.Color, this.Width)}
+                    {InteractionUtils.CreatePolyline(l, b.left, b.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)}
                 </svg>;
             }),
             this._points.length <= 1 ? (null) : <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
-                {InteractionUtils.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)}
+                {InteractionUtils.CreatePolyline(this._points, B.left, B.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)}
             </svg>]
-        );
-    }
-
-    @computed get elements() {
-        return [
-            this.props.children,
-            this._palette,
-            this.currentStrokes
         ];
     }
 
@@ -548,53 +559,59 @@ export default class GestureOverlay extends Touchable {
     }
 
     @computed
+    private get suggestionContent() {
+        const b = Math.max(this.svgBounds.bottom, ...this._strokes.map(s => this.getBounds(s).bottom));
+        const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right));
+        const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
+        const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
+        return (
+            <div className="inkToTextDoc-cont" style={{
+                transform: `translate(${l}px, ${b}px)`,
+                width: r - l,
+                height: this._possibilities.length * 25
+            }}>
+                <div className="inkToTextDoc-scroller" style={{ transform: `translate(0, ${-NumCast(this._inkToTextDoc!.selectedIndex) * 25}px)` }}>
+                    <DocumentView
+                        Document={this._inkToTextDoc!}
+                        DataDoc={undefined}
+                        LibraryPath={emptyPath}
+                        addDocument={undefined}
+                        addDocTab={returnFalse}
+                        pinToPres={emptyFunction}
+                        onClick={undefined}
+                        removeDocument={undefined}
+                        ScreenToLocalTransform={() => new Transform(-l, -(b + NumCast(this._inkToTextDoc?.selectedIndex, 0) * 25), 1)}
+                        ContentScaling={returnOne}
+                        PanelWidth={() => 300}
+                        PanelHeight={() => 300}
+                        renderDepth={0}
+                        backgroundColor={returnEmptyString}
+                        focus={emptyFunction}
+                        parentActive={returnTrue}
+                        whenActiveChanged={emptyFunction}
+                        bringToFront={emptyFunction}
+                        ContainingCollectionView={undefined}
+                        ContainingCollectionDoc={undefined}
+                        zoomToScale={emptyFunction}
+                        getScale={returnOne}
+                    />;
+                    </div>
+                <div className="shadow" style={{ height: `calc(100% - 25px - ${NumCast(this._inkToTextDoc!.selectedIndex) * 25}px)` }}>
+                </div>
+            </div>)
+    }
+
     private get inkToTextSuggestions() {
-        console.log(this._possibilities.length);
         if (this._inkToTextDoc && this._possibilities.length) {
-            const b = Math.max(this.svgBounds.bottom, ...this._strokes.map(s => this.getBounds(s).bottom));
-            const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right));
-            const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
-            const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
-
             return (
-                <div className="inkToTextDoc-cont" style={{
-                    transform: `translate(${l}px, ${b}px)`,
-                    width: r - l,
-                    height: 25,
-                }}>
-                    <div className="inkToTextDoc-scroller" style={{ transform: `translate(0, ${-NumCast(this._inkToTextDoc.selectedIndex) * 25}px)` }}>
-                        <DocumentView
-                            Document={this._inkToTextDoc}
-                            DataDoc={undefined}
-                            LibraryPath={emptyPath}
-                            addDocument={undefined}
-                            addDocTab={returnFalse}
-                            pinToPres={emptyFunction}
-                            onClick={undefined}
-                            removeDocument={undefined}
-                            ScreenToLocalTransform={() => new Transform(-l, -(b + NumCast(this._inkToTextDoc?.selectedIndex, 0) * 25), 1)}
-                            ContentScaling={returnOne}
-                            PanelWidth={() => 300}
-                            PanelHeight={() => 300}
-                            renderDepth={0}
-                            backgroundColor={returnEmptyString}
-                            focus={emptyFunction}
-                            parentActive={returnTrue}
-                            whenActiveChanged={emptyFunction}
-                            bringToFront={emptyFunction}
-                            ContainingCollectionView={undefined}
-                            ContainingCollectionDoc={undefined}
-                            zoomToScale={emptyFunction}
-                            getScale={returnOne}
-                        />;
-                    </div>
-                </div>
+                this.suggestionContent
             );
         }
         return null;
     }
 
     render() {
+        trace();
         return (
             <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
                 {this.elements}
@@ -647,4 +664,8 @@ Scripting.addGlobal(function resetPen() {
         GestureOverlay.Instance.Color = GestureOverlay.Instance.SavedColor ?? "rgb(244, 67, 54)";
         GestureOverlay.Instance.Width = GestureOverlay.Instance.SavedWidth ?? 5;
     });
+});
+Scripting.addGlobal(function createText(text: any, x: any, y: any) {
+    console.log("creating");
+    GestureOverlay.Instance.dispatchGesture("text", [{ X: x, Y: y }], text);
 });
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 41ef8c2a6..17139d9d9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -412,7 +412,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
                 sel.forEach(d => this.props.removeDocument(d));
                 break;
-
+            case GestureUtils.Gestures.Text:
+                if (ge.text) {
+                    const B = this.getTransform().transformPoint(ge.points[0].X, ge.points[0].Y);
+                    this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
+                }
         }
     }
 
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 4b5ad6684..4e3493c1c 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -6,18 +6,16 @@ import { Doc, WidthSym, HeightSym } from "../new_fields/Doc";
 import { NumCast } from "../new_fields/Types";
 import { CollectionFreeFormView } from "../client/views/collections/collectionFreeForm/CollectionFreeFormView";
 import { Rect } from "react-measure";
+import { Scripting } from "../client/util/Scripting";
 
 export namespace GestureUtils {
-    namespace GestureDataTypes {
-        export type BoxData = Array<Doc>;
-    }
-
     export class GestureEvent {
         constructor(
             readonly gesture: Gestures,
             readonly points: PointData[],
             readonly bounds: Rect,
-            readonly callbackFn?: Function
+            readonly callbackFn?: Function,
+            readonly text?: any
         ) { }
     }
 
@@ -38,7 +36,8 @@ export namespace GestureUtils {
         Box = "box",
         Line = "line",
         Stroke = "stroke",
-        Scribble = "scribble"
+        Scribble = "scribble",
+        Text = "text"
     }
 
     export const GestureRecognizer = new NDollarRecognizer(false);
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 6c37380f3..b0ea2f9ad 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -137,7 +137,7 @@ export class CurrentUserUtils {
             thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", isExpanded: true, flexDirection: "column" });
             userDoc.thumbDoc = thumbDoc;
         }
-        return userDoc.thumbDoc;
+        return Cast(userDoc.thumbDoc, Doc);
     }
 
     static setupMobileDoc(userDoc: Doc) {
-- 
cgit v1.2.3-70-g09d2


From 8767ec49fbb927ccde96f9f89562109703535d4e Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Thu, 6 Feb 2020 12:40:57 -0500
Subject: asdjklf

---
 src/client/documents/Documents.ts                  |   1 +
 src/client/views/GestureOverlay.scss               |  13 +--
 src/client/views/GestureOverlay.tsx                | 127 ++++++---------------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 +
 .../collectionFreeForm/MarqueeOptionsMenu.tsx      |   8 ++
 .../collections/collectionFreeForm/MarqueeView.tsx |  29 ++++-
 6 files changed, 78 insertions(+), 101 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 64583ae55..1c13eb079 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -138,6 +138,7 @@ export interface DocumentOptions {
     textTransform?: string; // is linear view expanded
     letterSpacing?: string; // is linear view expanded
     flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse";
+    selectedIndex?: number;
 }
 
 class EmptyBox {
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index f425c438e..2fad87ee0 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -17,19 +17,18 @@
     position: absolute;
     width: 300px;
     overflow: hidden;
+    pointer-events: none;
 
     .inkToTextDoc-scroller {
         overflow: visible;
         position: absolute;
         width: 100%;
-        left: -24px;
 
-        .collectionView {
-            overflow: visible;
-
-            .collectionLinearView-outer {
-                overflow: visible;
-            }
+        .menuItem-cont {
+            width: 100%;
+            height: 20px;
+            padding: 2.5px;
+            border-bottom: .5px solid black;
         }
     }
 
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 0fd6f3dba..e8262af1b 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -26,7 +26,7 @@ import { ScriptField } from "../../new_fields/ScriptField";
 import { listSpec } from "../../new_fields/Schema";
 import { List } from "../../new_fields/List";
 import { CollectionViewType } from "./collections/CollectionView";
-import InkCanvas from "./InkCanvas";
+import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
 
 @observer
 export default class GestureOverlay extends Touchable {
@@ -40,12 +40,15 @@ export default class GestureOverlay extends Touchable {
 
     @observable private _thumbX?: number;
     @observable private _thumbY?: number;
+    @observable private _selectedIndex: number = -1;
+    @observable private _menuX: number = -300;
+    @observable private _menuY: number = -300;
     @observable private _pointerY?: number;
     @observable private _points: { X: number, Y: number }[] = [];
     @observable private _strokes: InkData[] = [];
     @observable private _palette?: JSX.Element;
     @observable private _clipboardDoc?: JSX.Element;
-    @observable private _possibilities: Doc[] = [];
+    @observable private _possibilities: JSX.Element[] = [];
 
     @computed private get height(): number { return Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); }
     @computed private get showBounds() { return this.Tool !== ToolglassTools.None; }
@@ -254,6 +257,8 @@ export default class GestureOverlay extends Touchable {
                 this._thumbDoc = thumbDoc;
                 this._thumbX = thumb.clientX;
                 this._thumbY = thumb.clientY;
+                this._menuX = thumb.clientX + 50;
+                this._menuY = thumb.clientY;
                 this._palette = <HorizontalPalette x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />;
             });
         }
@@ -294,15 +299,14 @@ export default class GestureOverlay extends Touchable {
             const pt = e.changedTouches.item(i);
             if (pt && pt.identifier === this.thumbIdentifier && this._thumbY) {
                 if (this._thumbX && this._thumbDoc) {
-                    if (Math.abs(pt.clientX - this._thumbX) > 20) {
+                    if (Math.abs(pt.clientX - this._thumbX) > 30) {
                         this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX));
                         this._thumbX = pt.clientX;
                     }
                 }
                 if (this._thumbY && this._inkToTextDoc) {
                     if (Math.abs(pt.clientY - this._thumbY) > 20) {
-                        this._inkToTextDoc.selectedIndex = Math.max(0, NumCast(this._inkToTextDoc.selectedIndex) - Math.sign(pt.clientY - this._thumbY));
-                        this._thumbY = pt.clientY;
+                        this._selectedIndex = Math.max(0, -Math.floor((pt.clientY - this._thumbY) / 20));
                     }
                 }
             }
@@ -324,11 +328,10 @@ export default class GestureOverlay extends Touchable {
             let scriptWorked = false;
             if (NumCast(this._inkToTextDoc?.selectedIndex) > -1) {
                 const selectedButton = this._possibilities[NumCast(this._inkToTextDoc?.selectedIndex)];
-                if (Cast(selectedButton?.proto?.onClick, ScriptField)?.script.run({ this: selectedButton }, console.log).success) {
+                if (selectedButton) {
+                    selectedButton.props.onClick();
                     scriptWorked = true;
-                    console.log("success");
-                };
-                console.log(Cast(selectedButton?.proto?.onClick, ScriptField)?.script);
+                }
             }
 
             if (!scriptWorked) {
@@ -337,6 +340,7 @@ export default class GestureOverlay extends Touchable {
                 });
             }
             this._strokes = [];
+            this._points = [];
             this._possibilities = [];
             document.removeEventListener("touchend", this.handleHandUp);
         }
@@ -398,12 +402,12 @@ export default class GestureOverlay extends Touchable {
     }
 
     @action
-    onPointerUp = async (e: PointerEvent) => {
+    onPointerUp = (e: PointerEvent) => {
         if (this._points.length > 1) {
             const B = this.svgBounds;
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
 
-            const initialPoint = this._points[0];
+            const initialPoint = this._points[0.];
             const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
             const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
 
@@ -414,28 +418,21 @@ export default class GestureOverlay extends Touchable {
                         document.removeEventListener("pointerup", this.onPointerUp);
                         this._strokes.push(new Array(...this._points));
                         this._points = [];
-                        const results = await CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes);
-                        const wordResults = results.filter((r: any) => r.category === "inkWord");
-                        const possibilities: string[] = [];
-                        if (wordResults[0]?.recognizedText) {
-                            possibilities.push(wordResults[0]?.recognizedText)
-                        }
-                        possibilities.push(...wordResults[0]?.alternates?.map((a: any) => a.recognizedString));
-                        const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right));
-                        const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
-                        const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
-                        runInAction(() => {
-                            const buttons = possibilities.map(p => Docs.Create.ButtonDocument({
-                                _height: 25, _width: r - l, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", x: l, y: t, _viewType: CollectionViewType.Linear,
-                                isExpanded: true,
-                                title: p, fontSize: 10, letterSpacing: "0px", textTransform: "unset", boxShadow: ".5px .5px 0px rgb(34, 34, 34)",
-                                onClick: ScriptField.MakeScript('createText(this.proto.title, this.x, this.y)')
-                            }));
-                            if (this._inkToTextDoc && this._thumbDoc) {
-                                this._inkToTextDoc = this._thumbDoc.inkToTextDoc = Docs.Create.LinearDocument(buttons, { _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", isExpanded: true, flexDirection: "column" });
-                                this._inkToTextDoc.selectedIndex = 0;
+                        CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => {
+                            const wordResults = results.filter((r: any) => r.category === "inkWord" || r.category === "paragraph");
+                            const possibilities: string[] = [];
+                            if (wordResults[0]?.recognizedText) {
+                                possibilities.push(wordResults[0]?.recognizedText)
                             }
-                            this._possibilities = buttons;
+                            possibilities.push(...wordResults[0]?.alternates?.map((a: any) => a.recognizedString));
+                            console.log(possibilities);
+                            const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right));
+                            const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
+                            const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
+                            runInAction(() => {
+                                this._possibilities = possibilities.map(p =>
+                                    <TouchScrollableMenuItem text={p} onClick={() => GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: l, Y: t }], p)} />);
+                            });
                         });
                         break;
                     case ToolglassTools.IgnoreGesture:
@@ -511,10 +508,9 @@ export default class GestureOverlay extends Touchable {
         return [
             this.props.children,
             this._palette,
-
             [this._strokes.map(l => {
                 const b = this.getBounds(l);
-                return <svg width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
+                return <svg key={b.left} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
                     {InteractionUtils.CreatePolyline(l, b.left, b.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)}
                 </svg>;
             }),
@@ -558,58 +554,6 @@ export default class GestureOverlay extends Touchable {
         this._clipboardDoc = undefined;
     }
 
-    @computed
-    private get suggestionContent() {
-        const b = Math.max(this.svgBounds.bottom, ...this._strokes.map(s => this.getBounds(s).bottom));
-        const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right));
-        const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
-        const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
-        return (
-            <div className="inkToTextDoc-cont" style={{
-                transform: `translate(${l}px, ${b}px)`,
-                width: r - l,
-                height: this._possibilities.length * 25
-            }}>
-                <div className="inkToTextDoc-scroller" style={{ transform: `translate(0, ${-NumCast(this._inkToTextDoc!.selectedIndex) * 25}px)` }}>
-                    <DocumentView
-                        Document={this._inkToTextDoc!}
-                        DataDoc={undefined}
-                        LibraryPath={emptyPath}
-                        addDocument={undefined}
-                        addDocTab={returnFalse}
-                        pinToPres={emptyFunction}
-                        onClick={undefined}
-                        removeDocument={undefined}
-                        ScreenToLocalTransform={() => new Transform(-l, -(b + NumCast(this._inkToTextDoc?.selectedIndex, 0) * 25), 1)}
-                        ContentScaling={returnOne}
-                        PanelWidth={() => 300}
-                        PanelHeight={() => 300}
-                        renderDepth={0}
-                        backgroundColor={returnEmptyString}
-                        focus={emptyFunction}
-                        parentActive={returnTrue}
-                        whenActiveChanged={emptyFunction}
-                        bringToFront={emptyFunction}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
-                        zoomToScale={emptyFunction}
-                        getScale={returnOne}
-                    />;
-                    </div>
-                <div className="shadow" style={{ height: `calc(100% - 25px - ${NumCast(this._inkToTextDoc!.selectedIndex) * 25}px)` }}>
-                </div>
-            </div>)
-    }
-
-    private get inkToTextSuggestions() {
-        if (this._inkToTextDoc && this._possibilities.length) {
-            return (
-                this.suggestionContent
-            );
-        }
-        return null;
-    }
-
     render() {
         trace();
         return (
@@ -618,8 +562,8 @@ export default class GestureOverlay extends Touchable {
 
                 <div className="clipboardDoc-cont" style={{
                     transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
-                    height: this.height,
-                    width: this.height,
+                    height: this.height * 2,
+                    width: this.height * 2,
                     pointerEvents: this._clipboardDoc ? "unset" : "none",
                     touchAction: this._clipboardDoc ? "unset" : "none",
                 }}>
@@ -627,14 +571,14 @@ export default class GestureOverlay extends Touchable {
                 </div>
                 <div className="filter-cont" style={{
                     transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
-                    height: this.height,
-                    width: this.height,
+                    height: this.height * 2,
+                    width: this.height * 2,
                     pointerEvents: "none",
                     touchAction: "none",
                     display: this.showBounds ? "unset" : "none",
                 }}>
                 </div>
-                {this.inkToTextSuggestions}
+                <TouchScrollableMenu options={this._possibilities} bounds={this.svgBounds} selectedIndex={this._selectedIndex} x={this._menuX} y={this._menuY} />
             </div>);
     }
 }
@@ -666,6 +610,5 @@ Scripting.addGlobal(function resetPen() {
     });
 });
 Scripting.addGlobal(function createText(text: any, x: any, y: any) {
-    console.log("creating");
     GestureOverlay.Instance.dispatchGesture("text", [{ X: x, Y: y }], text);
 });
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 17139d9d9..2b160f2ff 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -984,6 +984,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             </CollectionFreeFormViewPannableContents>
         </MarqueeView>;
     }
+
     @computed get contentScaling() {
         if (this.props.annotationsKey) return 0;
         const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 71f265484..db4b674b5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -11,6 +11,7 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
     public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
     public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
     public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+    public inkToText: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
     public showMarquee: () => void = unimplementedFunction;
     public hideMarquee: () => void = unimplementedFunction;
 
@@ -43,6 +44,13 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
                 onPointerDown={this.delete}>
                 <FontAwesomeIcon icon="trash-alt" size="lg" />
             </button>,
+            <button
+                className="antimodeMenu-button"
+                title="Change to Text"
+                key="inkToText"
+                onPointerDown={this.inkToText}>
+                <FontAwesomeIcon icon="font" size="lg" />
+            </button>,
         ];
         return this.getElement(buttons);
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ef2fc2ad1..112df8f05 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,12 +1,12 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, DataSym, WidthSym, HeightSym } from "../../../../new_fields/Doc";
 import { InkField } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { listSpec } from "../../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
 import { ComputedField } from "../../../../new_fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { Cast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 import { Utils } from "../../../../Utils";
 import { Docs } from "../../../documents/Documents";
@@ -19,6 +19,7 @@ import "./MarqueeView.scss";
 import React = require("react");
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { SubCollectionViewProps } from "../CollectionSubView";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -204,6 +205,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             MarqueeOptionsMenu.Instance.createCollection = this.collection;
             MarqueeOptionsMenu.Instance.delete = this.delete;
             MarqueeOptionsMenu.Instance.summarize = this.summary;
+            MarqueeOptionsMenu.Instance.inkToText = this.syntaxHighlight;
             MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee;
             MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee;
             MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY);
@@ -361,6 +363,29 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         this.hideMarquee();
     }
 
+    @action
+    syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+        const selected = this.marqueeSelect(false);
+        if (e instanceof KeyboardEvent ? e.key === "i" : true) {
+            const inks = selected.filter(s => s.proto?.type === "ink");
+            const sets = selected.filter(s => s.proto?.type === "text")
+            const inkFields = inks.map(i => FieldValue(Cast(i.data, InkField)));
+            CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
+                const wordResults = results.filter((r: any) => r.category === "inkWord");
+                console.log(wordResults);
+                for (const word of wordResults) {
+                    const indices: number[] = word.strokeIds;
+                    const r = Math.floor(Math.random() * 256);
+                    const g = Math.floor(Math.random() * 256);
+                    const b = Math.floor(Math.random() * 256);
+                    indices.forEach(i => {
+                        inks[i].color = `rgb(${r}, ${g}, ${b})`;
+                    })
+                }
+            });
+        }
+    }
+
     @action
     summary = (e: KeyboardEvent | React.PointerEvent | undefined) => {
         const bounds = this.Bounds;
-- 
cgit v1.2.3-70-g09d2


From 593016303351f365d6ae13413b72d77495ea6a03 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Sat, 8 Feb 2020 14:35:20 -0500
Subject: syntax highlighting :)

---
 src/client/documents/Documents.ts                  |  1 +
 src/client/views/GestureOverlay.tsx                | 22 +++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 74 +++++++++++++++++++++-
 .../collections/collectionFreeForm/MarqueeView.tsx | 24 +++++--
 src/pen-gestures/GestureUtils.ts                   |  2 +
 src/pen-gestures/ndollar.ts                        | 11 +++-
 6 files changed, 119 insertions(+), 15 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1c13eb079..81a6ff802 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -139,6 +139,7 @@ export interface DocumentOptions {
     letterSpacing?: string; // is linear view expanded
     flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse";
     selectedIndex?: number;
+    syntaxColor?: string; // can be applied to text for syntax highlighting all matches in the text
 }
 
 class EmptyBox {
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index e8262af1b..0b77b4b86 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -50,7 +50,7 @@ export default class GestureOverlay extends Touchable {
     @observable private _clipboardDoc?: JSX.Element;
     @observable private _possibilities: JSX.Element[] = [];
 
-    @computed private get height(): number { return Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); }
+    @computed private get height(): number { return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); }
     @computed private get showBounds() { return this.Tool !== ToolglassTools.None; }
 
     private _d1: Doc | undefined;
@@ -408,8 +408,8 @@ export default class GestureOverlay extends Touchable {
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
 
             const initialPoint = this._points[0.];
-            const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
-            const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
+            const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height);
+            const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - (this.height) && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
 
             if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
                 switch (this.Tool) {
@@ -450,6 +450,14 @@ export default class GestureOverlay extends Touchable {
                             this.dispatchGesture(GestureUtils.Gestures.Box);
                             actionPerformed = true;
                             break;
+                        case GestureUtils.Gestures.StartBracket:
+                            this.dispatchGesture(GestureUtils.Gestures.StartBracket);
+                            actionPerformed = true;
+                            break;
+                        case GestureUtils.Gestures.EndBracket:
+                            this.dispatchGesture(GestureUtils.Gestures.EndBracket);
+                            actionPerformed = true;
+                            break;
                         case GestureUtils.Gestures.Line:
                             actionPerformed = this.handleLineGesture();
                             break;
@@ -562,8 +570,8 @@ export default class GestureOverlay extends Touchable {
 
                 <div className="clipboardDoc-cont" style={{
                     transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
-                    height: this.height * 2,
-                    width: this.height * 2,
+                    height: this.height,
+                    width: this.height,
                     pointerEvents: this._clipboardDoc ? "unset" : "none",
                     touchAction: this._clipboardDoc ? "unset" : "none",
                 }}>
@@ -571,8 +579,8 @@ export default class GestureOverlay extends Touchable {
                 </div>
                 <div className="filter-cont" style={{
                     transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
-                    height: this.height * 2,
-                    width: this.height * 2,
+                    height: this.height,
+                    width: this.height,
                     pointerEvents: "none",
                     touchAction: "none",
                     display: this.showBounds ? "unset" : "none",
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2b160f2ff..7b73050ab 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -9,7 +9,7 @@ import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../new_fields/Types";
+import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast, FieldValue } from "../../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
@@ -42,6 +42,8 @@ import { computedFn } from "mobx-utils";
 import { TraceMobx } from "../../../../new_fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { RichTextField } from "../../../../new_fields/RichTextField";
+import { List } from "../../../../new_fields/List";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -70,6 +72,9 @@ const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSch
 export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _lastX: number = 0;
     private _lastY: number = 0;
+    private _inkToTextStartX: number | undefined;
+    private _inkToTextStartY: number | undefined;
+    private _wordPalette: Map<string, string> = new Map<string, string>();
     private _clusterDistance: number = 75;
     private _hitCluster = false;
     private _layoutComputeReaction: IReactionDisposer | undefined;
@@ -411,11 +416,78 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 });
                 this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
                 sel.forEach(d => this.props.removeDocument(d));
+                e.stopPropagation();
+                break;
+            case GestureUtils.Gestures.StartBracket:
+                const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
+                this._inkToTextStartX = start[0];
+                this._inkToTextStartY = start[1];
+                console.log("start");
+                break;
+            case GestureUtils.Gestures.EndBracket:
+                console.log("end");
+                if (this._inkToTextStartX && this._inkToTextStartY) {
+                    const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
+                    const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "text" && s.color);
+                    const sets = setDocs.map((sd) => {
+                        return Cast(sd.data, RichTextField)?.Text as string;
+                    });
+                    if (sets.length && sets[0]) {
+                        this._wordPalette.clear();
+                        const colors = setDocs.map(sd => FieldValue(sd.color) as string);
+                        sets.forEach((st: string, i: number) => {
+                            const words = st.split(",");
+                            words.forEach(word => {
+                                this._wordPalette.set(word, colors[i]);
+                            });
+                        });
+                    }
+                    const inks = this.getActiveDocuments().filter(doc => {
+                        if (doc.type === "ink") {
+                            const l = NumCast(doc.x);
+                            const r = l + doc[WidthSym]();
+                            const t = NumCast(doc.y);
+                            const b = t + doc[HeightSym]();
+                            const pass = !(this._inkToTextStartX! > r || end[0] < l || this._inkToTextStartY! > b || end[1] < t);
+                            return pass;
+                        }
+                        return false;
+                    });
+                    const inkFields = inks.map(i => Cast(i.data, InkField));
+                    CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
+                        const wordResults = results.filter((r: any) => r.category === "inkWord");
+                        console.log(wordResults);
+                        for (const word of wordResults) {
+                            const indices: number[] = word.strokeIds;
+                            indices.forEach(i => {
+                                const otherInks: Doc[] = [];
+                                indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2]));
+                                inks[i].relatedInks = new List<Doc>(otherInks);
+                                const uniqueColors: string[] = [];
+                                Array.from(this._wordPalette.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c));
+                                inks[i].alternativeColors = new List<string>(uniqueColors);
+                                if (this._wordPalette.has(word.recognizedText)) {
+                                    inks[i].color = this._wordPalette.get(word.recognizedText);
+                                }
+                                else {
+                                    for (const alt of word.alternates) {
+                                        if (this._wordPalette.has(alt.recognizedString)) {
+                                            inks[i].color = this._wordPalette.get(alt.recognizedString);
+                                            break;
+                                        }
+                                    }
+                                }
+                            });
+                        }
+                    });
+                    this._inkToTextStartX = end[0];
+                }
                 break;
             case GestureUtils.Gestures.Text:
                 if (ge.text) {
                     const B = this.getTransform().transformPoint(ge.points[0].X, ge.points[0].Y);
                     this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
+                    e.stopPropagation();
                 }
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 112df8f05..19a71012a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -20,6 +20,7 @@ import React = require("react");
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { SubCollectionViewProps } from "../CollectionSubView";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { RichTextField } from "../../../../new_fields/RichTextField";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -368,18 +369,29 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const selected = this.marqueeSelect(false);
         if (e instanceof KeyboardEvent ? e.key === "i" : true) {
             const inks = selected.filter(s => s.proto?.type === "ink");
-            const sets = selected.filter(s => s.proto?.type === "text")
-            const inkFields = inks.map(i => FieldValue(Cast(i.data, InkField)));
+            const setDocs = selected.filter(s => s.proto?.type === "text" && s.color);
+            const sets = setDocs.map((sd) => {
+                return Cast(sd.data, RichTextField)?.Text as string;
+            });
+            const colors = setDocs.map(sd => FieldValue(sd.color) as string);
+            const wordToColor = new Map<string, string>();
+            console.log(sets);
+            sets.forEach((st: string, i: number) => {
+                const words = st.split(",");
+                words.forEach(word => {
+                    wordToColor.set(word, colors[i]);
+                });
+            });
+            const inkFields = inks.map(i => Cast(i.data, InkField));
             CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
                 const wordResults = results.filter((r: any) => r.category === "inkWord");
                 console.log(wordResults);
                 for (const word of wordResults) {
                     const indices: number[] = word.strokeIds;
-                    const r = Math.floor(Math.random() * 256);
-                    const g = Math.floor(Math.random() * 256);
-                    const b = Math.floor(Math.random() * 256);
                     indices.forEach(i => {
-                        inks[i].color = `rgb(${r}, ${g}, ${b})`;
+                        if (wordToColor.has(word.recognizedText)) {
+                            inks[i].color = wordToColor.get(word.recognizedText);
+                        }
                     })
                 }
             });
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 4e3493c1c..f14c573c3 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -35,6 +35,8 @@ export namespace GestureUtils {
     export enum Gestures {
         Box = "box",
         Line = "line",
+        StartBracket = "startbracket",
+        EndBracket = "endbracket",
         Stroke = "stroke",
         Scribble = "scribble",
         Text = "text"
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 9e15ada2d..643d58591 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -142,7 +142,7 @@ export class Result {
 //
 // NDollarRecognizer constants
 //
-const NumMultistrokes = 2;
+const NumMultistrokes = 4;
 const NumPoints = 96;
 const SquareSize = 250.0;
 const OneDThreshold = 0.25; // customize to desired gesture set (usually 0.20 - 0.35)
@@ -178,6 +178,15 @@ export class NDollarRecognizer {
         this.Multistrokes[1] = new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(
             new Array(new Point(12, 347), new Point(119, 347))
         ));
+        this.Multistrokes[2] = new Multistroke(GestureUtils.Gestures.StartBracket, useBoundedRotationInvariance, new Array(
+            // new Array(new Point(145, 20), new Point(30, 21), new Point(34, 150))
+            new Array(new Point(31, 25), new Point(145, 20), new Point(31, 25), new Point(34, 150))
+        ));
+        this.Multistrokes[3] = new Multistroke(GestureUtils.Gestures.EndBracket, useBoundedRotationInvariance, new Array(
+            // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152))
+            // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150))
+            new Array(new Point(10, 100), new Point(50, 12), new Point(100, 103))
+        ));
 
         //
         // PREDEFINED STROKES
-- 
cgit v1.2.3-70-g09d2


From 05c429412d36531bed4e8ece889479fcf93faea6 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Sun, 9 Feb 2020 16:43:01 -0500
Subject: functioning

---
 package-lock.json                                  |  4 +-
 package.json                                       |  2 +-
 .../views/collections/CollectionDockingView.tsx    | 69 ++++++++++++++++++++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 40 ++++++++++++-
 4 files changed, 110 insertions(+), 5 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 833710fe7..049c91fcf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16239,7 +16239,7 @@
         },
         "string_decoder": {
             "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+            "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
             "requires": {
                 "safe-buffer": "~5.1.0"
@@ -18560,7 +18560,7 @@
         },
         "wrap-ansi": {
             "version": "2.1.0",
-            "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
             "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
             "requires": {
                 "string-width": "^1.0.1",
diff --git a/package.json b/package.json
index 67b9b1630..7e0adfba6 100644
--- a/package.json
+++ b/package.json
@@ -253,4 +253,4 @@
         "xoauth2": "^1.2.0",
         "youtube": "^0.1.0"
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 82cb3bc88..7a6d54ac2 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -249,6 +249,75 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         return true;
     }
 
+
+    //
+    //  Creates a split on the any side of the docking view, based on the passed input pullSide and then adds the Document to the requested side
+    //
+    @undoBatch
+    @action
+    public static AddSplit(document: Doc, pullSide: string, dataDoc: Doc | undefined, libraryPath?: Doc[]) {
+        if (!CollectionDockingView.Instance) return false;
+        const instance = CollectionDockingView.Instance;
+        const newItemStackConfig = {
+            type: 'stack',
+            content: [CollectionDockingView.makeDocumentConfig(document, dataDoc, undefined, libraryPath)]
+        };
+
+        const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+
+        if (instance._goldenLayout.root.contentItems.length === 0) { // if no rows / columns
+            instance._goldenLayout.root.addChild(newContentItem);
+        } else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row
+            if (pullSide === "left") {
+                instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0);
+            } else if (pullSide === "right") {
+                instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
+            } else if (pullSide === "top" || pullSide === "bottom") {
+                // if not going in a row layout, must add already existing content into column
+                const rowlayout = instance._goldenLayout.root.contentItems[0];
+                const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout);
+                rowlayout.parent.replaceChild(rowlayout, newColumn);
+                if (pullSide === "top") {
+                    newColumn.addChild(rowlayout, undefined, true);
+                    newColumn.addChild(newContentItem, 0, true);
+                } else if (pullSide === "bottom") {
+                    newColumn.addChild(newContentItem, undefined, true);
+                    newColumn.addChild(rowlayout, 0, true);
+                }
+
+                rowlayout.config.height = 50;
+                newContentItem.config.height = 50;
+            }
+        } else if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
+            if (pullSide === "top") {
+                instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0);
+            } else if (pullSide === "bottom") {
+                instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
+            } else if (pullSide === "left" || pullSide === "right") {
+                // if not going in a row layout, must add already existing content into column
+                const collayout = instance._goldenLayout.root.contentItems[0];
+                const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
+                collayout.parent.replaceChild(collayout, newRow);
+
+                if (pullSide === "left") {
+                    newRow.addChild(collayout, undefined, true);
+                    newRow.addChild(newContentItem, 0, true);
+                } else if (pullSide === "right") {
+                    newRow.addChild(newContentItem, undefined, true);
+                    newRow.addChild(collayout, 0, true);
+                }
+
+                collayout.config.width = 50;
+                newContentItem.config.width = 50;
+            }
+        }
+
+        newContentItem.callDownwards('_$init');
+        instance.layoutChanged();
+        return true;
+    }
+
+
     //
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
     //
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 53fe2b18c..30ef9eb77 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -45,6 +45,7 @@ import { CognitiveServices } from "../../../cognitive_services/CognitiveServices
 import { RichTextField } from "../../../../new_fields/RichTextField";
 import { List } from "../../../../new_fields/List";
 import { DocumentViewProps } from "../../nodes/DocumentView";
+import { CollectionDockingView } from "../CollectionDockingView";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -80,6 +81,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _hitCluster = false;
     private _layoutComputeReaction: IReactionDisposer | undefined;
     private _layoutPoolData = new ObservableMap<string, any>();
+    private _pullCoords: number[] = [0, 0];
 
     public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@@ -596,7 +598,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
             const pt1 = myTouches[0];
             const pt2 = myTouches[1];
-            console.log(myTouches);
 
             if (this.prevPoints.size === 2) {
                 const oldPoint1 = this.prevPoints.get(pt1.identifier);
@@ -625,7 +626,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         // use the centerx and centery as the "new mouse position"
                         const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                         const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-                        this.pan({ clientX: centerX, clientY: centerY });
+
+                        if (!this._pullCoords[0] && !this._pullCoords[1]) { // if we are not bezel movement
+                            this.pan({ clientX: centerX, clientY: centerY });
+                        }
+
                         this._lastX = centerX;
                         this._lastY = centerY;
                     }
@@ -650,6 +655,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
                 this._lastX = centerX;
                 this._lastY = centerY;
+
+                // determine if we are using a bezel movement
+                if ((this.props.PanelWidth() - this._lastX) < 100 || this._lastX < 100 || (this.props.PanelHeight() - this._lastY < 100) || this._lastY < 120) { // to account for header
+                    this._pullCoords = [this._lastX, this._lastY];
+                }
+
                 this.removeMoveListeners();
                 this.addMoveListeners();
                 this.removeEndListeners();
@@ -660,12 +671,37 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     cleanUpInteractions = () => {
+
+        if (this._pullCoords[0] !== 0 && this._pullCoords[1] !== 0) { // if bezel mvmt was activated
+            const xDiff = this._pullCoords[0] - this._lastX;
+            const yDiff = this._pullCoords[1] - this._lastY;
+
+            console.log('went thru', this._pullCoords);
+            if ((this._lastX < this._pullCoords[0]) && (yDiff < xDiff)) { // pull from right
+                console.log('pulled from right');
+                // CollectionDockingView.AddRightSplit(this.Document, undefined);
+                CollectionDockingView.AddSplit(this.Document, "right", undefined);
+            } else if ((this._lastY > this._pullCoords[1]) && (yDiff < xDiff)) { // pull from top
+                console.log('pulled from top');
+                CollectionDockingView.AddSplit(this.Document, "top", undefined);
+            } else if ((this._lastY < this._pullCoords[1]) && (yDiff > xDiff)) { // pull from bottom
+                console.log('pulled from bottom');
+                CollectionDockingView.AddSplit(this.Document, "bottom", undefined);
+            } else if ((this._lastX > this._pullCoords[0]) && (yDiff > xDiff)) { // pull from left
+                console.log('pulled from left');
+                CollectionDockingView.AddSplit(this.Document, "left", undefined);
+            }
+        }
+
+        this._pullCoords = [0, 0];
+
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         this.removeMoveListeners();
         this.removeEndListeners();
     }
 
+
     @action
     zoom = (pointX: number, pointY: number, deltaY: number): void => {
         let deltaScale = deltaY > 0 ? (1 / 1.1) : 1.1;
-- 
cgit v1.2.3-70-g09d2


From dc6453e27375a1b3d614a74b7fd1d83695f130d7 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Mon, 10 Feb 2020 20:02:43 -0500
Subject: some final stuff

---
 src/client/views/GestureOverlay.scss               | 15 ++++++++++++++
 src/client/views/GestureOverlay.tsx                | 23 +++++++++++++---------
 src/client/views/Palette.scss                      | 13 ++++++++++--
 src/client/views/Palette.tsx                       |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  9 ++++-----
 .../collections/collectionFreeForm/MarqueeView.tsx | 14 ++++++++++---
 src/pen-gestures/ndollar.ts                        |  2 +-
 src/server/Search.ts                               |  2 +-
 8 files changed, 58 insertions(+), 21 deletions(-)

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

diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index 7474ca839..107077792 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -5,6 +5,21 @@
     top: 0;
     left: 0;
     touch-action: none;
+
+    .pointerBubbles {
+        width: 100%;
+        height: 100%;
+        position: absolute;
+        pointer-events: none;
+
+        .bubble {
+            position: absolute;
+            width: 15px;
+            height: 15px;
+            border-radius: 100%;
+            border: .5px solid grey;
+        }
+    }
 }
 
 .clipboardDoc-cont {
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 4e9b87eea..5c4dd0c0a 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -32,8 +32,8 @@ import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableM
 export default class GestureOverlay extends Touchable {
     static Instance: GestureOverlay;
 
-    @observable public Color: string = "rgb(244, 67, 54)";
-    @observable public Width: number = 5;
+    @observable public Color: string = "rgb(0, 0, 0)";
+    @observable public Width: number = 2;
     @observable public SavedColor?: string;
     @observable public SavedWidth?: number;
     @observable public Tool: ToolglassTools = ToolglassTools.None;
@@ -394,9 +394,9 @@ export default class GestureOverlay extends Touchable {
             if (pt && pt.identifier === this.thumbIdentifier && this._thumbY) {
                 if (this._thumbX && this._thumbY) {
                     const yOverX = Math.abs(pt.clientX - this._thumbX) < Math.abs(pt.clientY - this._thumbY);
-                    if ((yOverX && this._inkToTextDoc) || this._selectedIndex > 0) {
+                    if ((yOverX && this._inkToTextDoc) || this._selectedIndex > -1) {
                         if (Math.abs(pt.clientY - this._thumbY) > (10 * window.devicePixelRatio)) {
-                            this._selectedIndex = Math.min(Math.max(-1, -Math.ceil((pt.clientY - this._thumbY) / 20)), this._possibilities.length - 1);
+                            this._selectedIndex = Math.min(Math.max(-1, (-Math.ceil((pt.clientY - this._thumbY) / (10 * window.devicePixelRatio)) - 1)), this._possibilities.length - 1);
                         }
                     }
                     else if (this._thumbDoc) {
@@ -436,7 +436,7 @@ export default class GestureOverlay extends Touchable {
 
             let scriptWorked = false;
             if (NumCast(this._inkToTextDoc?.selectedIndex) > -1) {
-                const selectedButton = this._possibilities[NumCast(this._inkToTextDoc?.selectedIndex)];
+                const selectedButton = this._possibilities[this._selectedIndex];
                 if (selectedButton) {
                     selectedButton.props.onClick();
                     scriptWorked = true;
@@ -507,8 +507,10 @@ export default class GestureOverlay extends Touchable {
                 this._d1 = doc;
             }
             else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
-                DocUtils.MakeLink({ doc: this._d1 }, { doc: doc });
-                actionPerformed = true;
+                if (this._d1.type !== "ink" && doc.type !== "ink") {
+                    DocUtils.MakeLink({ doc: this._d1 }, { doc: doc });
+                    actionPerformed = true;
+                }
             }
         };
         const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
@@ -716,6 +718,9 @@ export default class GestureOverlay extends Touchable {
                 }}>
                 </div>
                 <TouchScrollableMenu options={this._possibilities} bounds={this.svgBounds} selectedIndex={this._selectedIndex} x={this._menuX} y={this._menuY} />
+                {/* <div className="pointerBubbles">
+                {this._pointers.map(p => <div className="bubble" style={{ translate: `transform(${p.clientX}px, ${p.clientY}px)` }}></div>)}
+                </div> */}
             </div>);
     }
 }
@@ -743,8 +748,8 @@ Scripting.addGlobal(function setPen(width: any, color: any) {
 });
 Scripting.addGlobal(function resetPen() {
     runInAction(() => {
-        GestureOverlay.Instance.Color = GestureOverlay.Instance.SavedColor ?? "rgb(244, 67, 54)";
-        GestureOverlay.Instance.Width = GestureOverlay.Instance.SavedWidth ?? 5;
+        GestureOverlay.Instance.Color = GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)";
+        GestureOverlay.Instance.Width = GestureOverlay.Instance.SavedWidth ?? 2;
     });
 });
 Scripting.addGlobal(function createText(text: any, x: any, y: any) {
diff --git a/src/client/views/Palette.scss b/src/client/views/Palette.scss
index 4513de2b0..0ec879288 100644
--- a/src/client/views/Palette.scss
+++ b/src/client/views/Palette.scss
@@ -1,13 +1,14 @@
 .palette-container {
     .palette-thumb {
         touch-action: pan-x;
-        overflow: scroll;
         position: absolute;
-        width: 90px;
         height: 70px;
+        overflow: hidden;
 
         .palette-thumbContent {
             transition: transform .3s;
+            width: max-content;
+            overflow: hidden;
 
             .collectionView {
                 overflow: visible;
@@ -17,5 +18,13 @@
                 }
             }
         }
+
+        .palette-cover {
+            width: 50px;
+            height: 50px;
+            position: absolute;
+            bottom: 0;
+            border: 1px solid black;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 10aac96a0..e04f814d1 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -72,6 +72,7 @@ export default class Palette extends React.Component<PaletteProps> {
                             zoomToScale={emptyFunction}
                             getScale={returnOne}>
                         </DocumentView>
+                        <div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div>
                     </div>
                 </div>
             </div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 53fe2b18c..4b1bb09a9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -457,7 +457,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     const inkFields = inks.map(i => Cast(i.data, InkField));
                     CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
                         const wordResults = results.filter((r: any) => r.category === "inkWord");
-                        console.log(wordResults);
                         for (const word of wordResults) {
                             const indices: number[] = word.strokeIds;
                             indices.forEach(i => {
@@ -467,13 +466,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                                 const uniqueColors: string[] = [];
                                 Array.from(this._wordPalette.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c));
                                 inks[i].alternativeColors = new List<string>(uniqueColors);
-                                if (this._wordPalette.has(word.recognizedText)) {
-                                    inks[i].color = this._wordPalette.get(word.recognizedText);
+                                if (this._wordPalette.has(word.recognizedText.toLowerCase())) {
+                                    inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase());
                                 }
                                 else {
                                     for (const alt of word.alternates) {
-                                        if (this._wordPalette.has(alt.recognizedString)) {
-                                            inks[i].color = this._wordPalette.get(alt.recognizedString);
+                                        if (this._wordPalette.has(alt.recognizedString.toLowerCase())) {
+                                            inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase());
                                             break;
                                         }
                                     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 19a71012a..8591144c0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -375,7 +375,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             });
             const colors = setDocs.map(sd => FieldValue(sd.color) as string);
             const wordToColor = new Map<string, string>();
-            console.log(sets);
             sets.forEach((st: string, i: number) => {
                 const words = st.split(",");
                 words.forEach(word => {
@@ -386,11 +385,20 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
                 const wordResults = results.filter((r: any) => r.category === "inkWord");
                 console.log(wordResults);
+                console.log(results);
                 for (const word of wordResults) {
                     const indices: number[] = word.strokeIds;
                     indices.forEach(i => {
-                        if (wordToColor.has(word.recognizedText)) {
-                            inks[i].color = wordToColor.get(word.recognizedText);
+                        if (wordToColor.has(word.recognizedText.toLowerCase())) {
+                            inks[i].color = wordToColor.get(word.recognizedText.toLowerCase());
+                        }
+                        else {
+                            for (const alt of word.alternates) {
+                                if (wordToColor.has(alt.recognizedString.toLowerCase())) {
+                                    inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase());
+                                    break;
+                                }
+                            }
                         }
                     })
                 }
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 643d58591..365896197 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -185,7 +185,7 @@ export class NDollarRecognizer {
         this.Multistrokes[3] = new Multistroke(GestureUtils.Gestures.EndBracket, useBoundedRotationInvariance, new Array(
             // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152))
             // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150))
-            new Array(new Point(10, 100), new Point(50, 12), new Point(100, 103))
+            new Array(new Point(10, 100), new Point(50, 100), new Point(100, 12), new Point(150, 103), new Point(190, 100))
         ));
 
         //
diff --git a/src/server/Search.ts b/src/server/Search.ts
index 43aa7e49c..21064e520 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -13,7 +13,7 @@ export namespace Search {
             });
             return res;
         } catch (e) {
-            console.warn("Search error: " + e + document);
+            // console.warn("Search error: " + e + document);
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 4b09b59dc31b089eada895fa0eabac95fd3931bd Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Mon, 10 Feb 2020 22:48:54 -0500
Subject: finished

---
 .../collectionFreeForm/CollectionFreeFormView.scss |  7 +++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 72 ++++++++++++++++------
 2 files changed, 60 insertions(+), 19 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 58fb81453..b70697e9a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -34,6 +34,7 @@
         height: 100%;
         display: flex;
         align-items: center;
+
         .collectionfreeformview-placeholderSpan {
             font-size: 32;
             display: flex;
@@ -97,4 +98,10 @@
 
 #prevCursor {
     animation: blink 1s infinite;
+}
+
+.pullpane-indicator {
+    z-index: 999;
+    background-color: rgba($color: #000000, $alpha: .4);
+    position: absolute;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 30ef9eb77..85349b6e3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -81,7 +81,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _hitCluster = false;
     private _layoutComputeReaction: IReactionDisposer | undefined;
     private _layoutPoolData = new ObservableMap<string, any>();
-    private _pullCoords: number[] = [0, 0];
+    @observable private _pullCoords: number[] = [0, 0];
+    @observable private _pullDirection: string = "";
 
     public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@@ -627,8 +628,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                         const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
 
-                        if (!this._pullCoords[0] && !this._pullCoords[1]) { // if we are not bezel movement
+                        if (!this._pullDirection) { // if we are not bezel movement
                             this.pan({ clientX: centerX, clientY: centerY });
+                        } else {
+                            this._pullCoords = [centerX, centerY];
+                            console.log(this.layoutDoc._width);
                         }
 
                         this._lastX = centerX;
@@ -657,10 +661,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 this._lastY = centerY;
 
                 // determine if we are using a bezel movement
-                if ((this.props.PanelWidth() - this._lastX) < 100 || this._lastX < 100 || (this.props.PanelHeight() - this._lastY < 100) || this._lastY < 120) { // to account for header
+                if ((this.props.PanelWidth() - this._lastX) < 100) {
                     this._pullCoords = [this._lastX, this._lastY];
+                    this._pullDirection = "right";
+                } else if (this._lastX < 100) {
+                    this._pullCoords = [this._lastX, this._lastY];
+                    this._pullDirection = "left";
+                } else if (this.props.PanelHeight() - this._lastY < 100) {
+                    this._pullCoords = [this._lastX, this._lastY];
+                    this._pullDirection = "bottom";
+                } else if (this._lastY < 120) { // to account for header
+                    this._pullCoords = [this._lastX, this._lastY];
+                    this._pullDirection = "top";
                 }
 
+
                 this.removeMoveListeners();
                 this.addMoveListeners();
                 this.removeEndListeners();
@@ -672,27 +687,29 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     cleanUpInteractions = () => {
 
-        if (this._pullCoords[0] !== 0 && this._pullCoords[1] !== 0) { // if bezel mvmt was activated
-            const xDiff = this._pullCoords[0] - this._lastX;
-            const yDiff = this._pullCoords[1] - this._lastY;
+        switch (this._pullDirection) {
 
-            console.log('went thru', this._pullCoords);
-            if ((this._lastX < this._pullCoords[0]) && (yDiff < xDiff)) { // pull from right
+            case "left":
+                console.log('pulled from left');
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "left", undefined);
+                break;
+            case "right":
                 console.log('pulled from right');
-                // CollectionDockingView.AddRightSplit(this.Document, undefined);
-                CollectionDockingView.AddSplit(this.Document, "right", undefined);
-            } else if ((this._lastY > this._pullCoords[1]) && (yDiff < xDiff)) { // pull from top
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "right", undefined);
+                break;
+            case "top":
                 console.log('pulled from top');
-                CollectionDockingView.AddSplit(this.Document, "top", undefined);
-            } else if ((this._lastY < this._pullCoords[1]) && (yDiff > xDiff)) { // pull from bottom
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "top", undefined);
+                break;
+            case "bottom":
                 console.log('pulled from bottom');
-                CollectionDockingView.AddSplit(this.Document, "bottom", undefined);
-            } else if ((this._lastX > this._pullCoords[0]) && (yDiff > xDiff)) { // pull from left
-                console.log('pulled from left');
-                CollectionDockingView.AddSplit(this.Document, "left", undefined);
-            }
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "bottom", undefined);
+                break;
+            default:
+                break;
         }
 
+        this._pullDirection = "";
         this._pullCoords = [0, 0];
 
         document.removeEventListener("pointermove", this.onPointerMove);
@@ -1125,7 +1142,24 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
                 this.placeholder : this.marqueeView}
             <CollectionFreeFormOverlayView elements={this.elementFunc} />
-        </div>;
+
+            <div className={"pullpane-indicator"}
+                style={{
+                    display: this._pullDirection ? "block" : "none",
+                    // width: this._pullDirection === "left" || this._pullDirection === "right" ? Math.abs(this.props.PanelWidth() - this._pullCoords[0]) : this.props.PanelWidth(),
+                    // height: this._pullDirection === "top" || this._pullDirection === "bottom" ? Math.abs(this.props.PanelHeight() - this._pullCoords[1]) : this.props.PanelHeight(),
+                    // top: this._pullDirection === "bottom" ? this._pullCoords[0] : 0,
+                    // left: this._pullDirection === "right" ? this._pullCoords[1] : 0
+                    width: this._pullDirection === "left" ? this._pullCoords[0] : this._pullDirection === "right" ? this.props.PanelWidth() - this._pullCoords[0] : this.props.PanelWidth(),
+                    height: this._pullDirection === "top" ? this._pullCoords[1] : this._pullDirection === "bottom" ? this.props.PanelHeight() - this._pullCoords[1] : this.props.PanelHeight(),
+                    left: this._pullDirection === "right" ? undefined : 0,
+                    right: this._pullDirection === "right" ? 0 : undefined,
+                    top: this._pullDirection === "bottom" ? undefined : 0,
+                    bottom: this._pullDirection === "bottom" ? 0 : undefined
+                }}>
+            </div>
+
+        </div >;
     }
 }
 
-- 
cgit v1.2.3-70-g09d2


From 5a32a80e6f3026009522fdc3de614e85930efff2 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Mon, 10 Feb 2020 23:08:17 -0500
Subject: made slight qol improvements

---
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx      | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1f2cdaea4..8f555f315 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -46,6 +46,7 @@ import { RichTextField } from "../../../../new_fields/RichTextField";
 import { List } from "../../../../new_fields/List";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { CollectionDockingView } from "../CollectionDockingView";
+import { MainView } from "../../MainView";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -631,7 +632,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                             this.pan({ clientX: centerX, clientY: centerY });
                         } else {
                             this._pullCoords = [centerX, centerY];
-                            console.log(this.layoutDoc._width);
+                            console.log(MainView.Instance.flyoutWidth);
                         }
 
                         this._lastX = centerX;
@@ -1149,8 +1150,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     // height: this._pullDirection === "top" || this._pullDirection === "bottom" ? Math.abs(this.props.PanelHeight() - this._pullCoords[1]) : this.props.PanelHeight(),
                     // top: this._pullDirection === "bottom" ? this._pullCoords[0] : 0,
                     // left: this._pullDirection === "right" ? this._pullCoords[1] : 0
-                    width: this._pullDirection === "left" ? this._pullCoords[0] : this._pullDirection === "right" ? this.props.PanelWidth() - this._pullCoords[0] : this.props.PanelWidth(),
-                    height: this._pullDirection === "top" ? this._pullCoords[1] : this._pullDirection === "bottom" ? this.props.PanelHeight() - this._pullCoords[1] : this.props.PanelHeight(),
+                    width: this._pullDirection === "left" ? this._pullCoords[0] : this._pullDirection === "right" ? MainView.Instance.getPWidth() - this._pullCoords[0] + MainView.Instance.flyoutWidth : MainView.Instance.getPWidth(),
+                    height: this._pullDirection === "top" ? this._pullCoords[1] : this._pullDirection === "bottom" ? MainView.Instance.getPHeight() - this._pullCoords[1] : MainView.Instance.getPHeight(),
                     left: this._pullDirection === "right" ? undefined : 0,
                     right: this._pullDirection === "right" ? 0 : undefined,
                     top: this._pullDirection === "bottom" ? undefined : 0,
-- 
cgit v1.2.3-70-g09d2


From f689b3ae048941f23d67750ba488ca90bba578ee Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Tue, 11 Feb 2020 18:04:05 -0500
Subject: some more fixes

---
 src/client/util/InteractionUtils.tsx                   |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx      | 18 ++++++++++++++++--
 .../collections/collectionFreeForm/MarqueeView.tsx     |  1 +
 src/pen-gestures/ndollar.ts                            |  2 +-
 4 files changed, 19 insertions(+), 3 deletions(-)

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

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 8fc5e8098..184e37ba5 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -91,6 +91,7 @@ export namespace InteractionUtils {
     }
 
     export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean {
+        console.log(e.button);
         switch (type) {
             // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
             case PENTYPE:
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4b1bb09a9..bb34f3d27 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -454,8 +454,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         }
                         return false;
                     });
-                    const inkFields = inks.map(i => Cast(i.data, InkField));
-                    CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
+                    // const inkFields = inks.map(i => Cast(i.data, InkField));
+                    const strokes: InkData[] = [];
+                    inks.forEach(i => {
+                        const d = Cast(i.data, InkField);
+                        const x = NumCast(i.x);
+                        const y = NumCast(i.y);
+                        const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]);
+                        const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]);
+                        if (d) {
+                            strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top })));
+                        }
+                    });
+                    console.log(strokes)
+
+                    CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
+                        console.log(results);
                         const wordResults = results.filter((r: any) => r.category === "inkWord");
                         for (const word of wordResults) {
                             const indices: number[] = word.strokeIds;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 8591144c0..e82ca6bf2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -21,6 +21,7 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { SubCollectionViewProps } from "../CollectionSubView";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { RichTextField } from "../../../../new_fields/RichTextField";
+import { InteractionUtils } from "../../../util/InteractionUtils";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 365896197..bb92f62e1 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -185,7 +185,7 @@ export class NDollarRecognizer {
         this.Multistrokes[3] = new Multistroke(GestureUtils.Gestures.EndBracket, useBoundedRotationInvariance, new Array(
             // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152))
             // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150))
-            new Array(new Point(10, 100), new Point(50, 100), new Point(100, 12), new Point(150, 103), new Point(190, 100))
+            new Array(new Point(10, 100), new Point(100, 100), new Point(150, 12), new Point(200, 103), new Point(300, 100))
         ));
 
         //
-- 
cgit v1.2.3-70-g09d2


From 2131c829b8632a87b8ba52bc311d3b0346eed3f7 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Tue, 11 Feb 2020 21:36:20 -0500
Subject: did the maths :')

---
 src/client/views/collections/CollectionSubView.tsx |  2 +
 .../collectionFreeForm/CollectionFreeFormView.scss |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 74 +++++++++++++++-------
 3 files changed, 53 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8679c8bd1..12906ee83 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -51,11 +51,13 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
         private _childLayoutDisposer?: IReactionDisposer;
+        protected _mainCont?: HTMLDivElement;
         protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer?.();
             this.gestureDisposer?.();
             this.multiTouchDisposer?.();
             if (ele) {
+                this._mainCont = ele;
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
                 this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index b70697e9a..2213b7882 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -101,7 +101,7 @@
 }
 
 .pullpane-indicator {
-    z-index: 999;
+    z-index: 99999;
     background-color: rgba($color: #000000, $alpha: .4);
     position: absolute;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8f555f315..bf517e7c2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
+import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload, faTextHeight } from "@fortawesome/free-solid-svg-icons";
 import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc";
@@ -47,6 +47,7 @@ import { List } from "../../../../new_fields/List";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { CollectionDockingView } from "../CollectionDockingView";
 import { MainView } from "../../MainView";
+import { TouchScrollableMenuItem } from "../../TouchScrollableMenu";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -627,12 +628,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         // use the centerx and centery as the "new mouse position"
                         const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                         const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+                        // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
 
                         if (!this._pullDirection) { // if we are not bezel movement
                             this.pan({ clientX: centerX, clientY: centerY });
                         } else {
                             this._pullCoords = [centerX, centerY];
-                            console.log(MainView.Instance.flyoutWidth);
                         }
 
                         this._lastX = centerX;
@@ -657,22 +658,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (pt1 && pt2) {
                 const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                 const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+                // const screenPoint = this.getTransform().inverse().transformPoint(centerX, centerY);
                 this._lastX = centerX;
                 this._lastY = centerY;
+                const screenBox = this._mainCont?.getBoundingClientRect();
 
+
+                // console.log(this.props.PanelWidth(), transformed[0]);
                 // determine if we are using a bezel movement
-                if ((this.props.PanelWidth() - this._lastX) < 100) {
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "right";
-                } else if (this._lastX < 100) {
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "left";
-                } else if (this.props.PanelHeight() - this._lastY < 100) {
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "bottom";
-                } else if (this._lastY < 120) { // to account for header
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "top";
+                if (screenBox) {
+                    if ((screenBox.right - centerX) < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "right";
+                    } else if (centerX - screenBox.left < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "left";
+                    } else if (screenBox.bottom - centerY < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "bottom";
+                    } else if (centerY - screenBox.top < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "top";
+                    }
                 }
 
 
@@ -708,6 +715,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             default:
                 break;
         }
+        console.log("");
 
         this._pullDirection = "";
         this._pullCoords = [0, 0];
@@ -1120,6 +1128,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     render() {
         TraceMobx();
+        const clientRect = this._mainCont?.getBoundingClientRect();
+        // console.log('clientrect has upd8ed', clientRect, this._pullCoords);
+        console.log(
+            'left', clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - MainView.Instance.flyoutWidth : clientRect.x - MainView.Instance.flyoutWidth : "auto",
+            'top indicator', clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : clientRect.y - 20 : "auto",
+            'width', clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+            'height', clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0);
+        console.log(clientRect);
         // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
         // this.Document.fitX = this.contentBounds && this.contentBounds.x;
         // this.Document.fitY = this.contentBounds && this.contentBounds.y;
@@ -1146,16 +1162,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             <div className={"pullpane-indicator"}
                 style={{
                     display: this._pullDirection ? "block" : "none",
-                    // width: this._pullDirection === "left" || this._pullDirection === "right" ? Math.abs(this.props.PanelWidth() - this._pullCoords[0]) : this.props.PanelWidth(),
-                    // height: this._pullDirection === "top" || this._pullDirection === "bottom" ? Math.abs(this.props.PanelHeight() - this._pullCoords[1]) : this.props.PanelHeight(),
-                    // top: this._pullDirection === "bottom" ? this._pullCoords[0] : 0,
-                    // left: this._pullDirection === "right" ? this._pullCoords[1] : 0
-                    width: this._pullDirection === "left" ? this._pullCoords[0] : this._pullDirection === "right" ? MainView.Instance.getPWidth() - this._pullCoords[0] + MainView.Instance.flyoutWidth : MainView.Instance.getPWidth(),
-                    height: this._pullDirection === "top" ? this._pullCoords[1] : this._pullDirection === "bottom" ? MainView.Instance.getPHeight() - this._pullCoords[1] : MainView.Instance.getPHeight(),
-                    left: this._pullDirection === "right" ? undefined : 0,
-                    right: this._pullDirection === "right" ? 0 : undefined,
-                    top: this._pullDirection === "bottom" ? undefined : 0,
-                    bottom: this._pullDirection === "bottom" ? 0 : undefined
+                    // width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+                    // height: clientRect ? this._pullDirection === "top" ? this._pullCoords[0] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[0] : clientRect.height : 0,
+                    // left: clientRect ? this._pullDirection === "right" ? undefined : clientRect.left : 0,
+                    // right: clientRect ? this._pullDirection === "left" ? undefined : clientRect.right : 0,
+                    // top: clientRect ? this._pullDirection === "bottom" ? undefined : clientRect.top : 0,
+                    // bottom: clientRect ? this._pullDirection === "top" ? undefined : clientRect.bottom : 0,
+
+
+
+                    // hahahahahahahhahahahaa
+
+                    width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+                    height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
+                    // left: clientRect ? this._pullDirection === "top" || this._pullDirection === "left" || this._pullDirection === "bottom" ? clientRect.left - MainView.Instance.flyoutWidth : this._pullCoords[0] - clientRect.left : undefined,
+                    // top: clientRect ? this._pullDirection === "top" || this._pullDirection === "left" ? clientRect.top - 20 : this._pullCoords[1] - clientRect.top : undefined,
+
+                    // left: x-axis, top: y-axis
+                    left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
+                    top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
+
                 }}>
             </div>
 
-- 
cgit v1.2.3-70-g09d2


From ff268c1440cc4c5553d19524150d6f2d00cbd14c Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Tue, 11 Feb 2020 21:41:32 -0500
Subject: removed console logs + comments

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 32 ++--------------------
 1 file changed, 2 insertions(+), 30 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bf517e7c2..5fe5895a8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -658,13 +658,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (pt1 && pt2) {
                 const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                 const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-                // const screenPoint = this.getTransform().inverse().transformPoint(centerX, centerY);
                 this._lastX = centerX;
                 this._lastY = centerY;
                 const screenBox = this._mainCont?.getBoundingClientRect();
 
 
-                // console.log(this.props.PanelWidth(), transformed[0]);
                 // determine if we are using a bezel movement
                 if (screenBox) {
                     if ((screenBox.right - centerX) < 100) {
@@ -697,19 +695,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         switch (this._pullDirection) {
 
             case "left":
-                console.log('pulled from left');
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "left", undefined);
                 break;
             case "right":
-                console.log('pulled from right');
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "right", undefined);
                 break;
             case "top":
-                console.log('pulled from top');
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "top", undefined);
                 break;
             case "bottom":
-                console.log('pulled from bottom');
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "bottom", undefined);
                 break;
             default:
@@ -1129,13 +1123,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     render() {
         TraceMobx();
         const clientRect = this._mainCont?.getBoundingClientRect();
-        // console.log('clientrect has upd8ed', clientRect, this._pullCoords);
-        console.log(
-            'left', clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - MainView.Instance.flyoutWidth : clientRect.x - MainView.Instance.flyoutWidth : "auto",
-            'top indicator', clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : clientRect.y - 20 : "auto",
-            'width', clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
-            'height', clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0);
-        console.log(clientRect);
         // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
         // this.Document.fitX = this.contentBounds && this.contentBounds.x;
         // this.Document.fitY = this.contentBounds && this.contentBounds.y;
@@ -1162,25 +1149,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             <div className={"pullpane-indicator"}
                 style={{
                     display: this._pullDirection ? "block" : "none",
-                    // width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
-                    // height: clientRect ? this._pullDirection === "top" ? this._pullCoords[0] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[0] : clientRect.height : 0,
-                    // left: clientRect ? this._pullDirection === "right" ? undefined : clientRect.left : 0,
-                    // right: clientRect ? this._pullDirection === "left" ? undefined : clientRect.right : 0,
-                    // top: clientRect ? this._pullDirection === "bottom" ? undefined : clientRect.top : 0,
-                    // bottom: clientRect ? this._pullDirection === "top" ? undefined : clientRect.bottom : 0,
-
-
-
-                    // hahahahahahahhahahahaa
-
+                    top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
+                    left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
                     width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
                     height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
-                    // left: clientRect ? this._pullDirection === "top" || this._pullDirection === "left" || this._pullDirection === "bottom" ? clientRect.left - MainView.Instance.flyoutWidth : this._pullCoords[0] - clientRect.left : undefined,
-                    // top: clientRect ? this._pullDirection === "top" || this._pullDirection === "left" ? clientRect.top - 20 : this._pullCoords[1] - clientRect.top : undefined,
-
-                    // left: x-axis, top: y-axis
-                    left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
-                    top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
 
                 }}>
             </div>
-- 
cgit v1.2.3-70-g09d2


From 7dbcd2011ec08476151a08c113191a1676e757a2 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Tue, 11 Feb 2020 22:36:54 -0500
Subject: some small stuff

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  3 +--
 .../collections/collectionFreeForm/MarqueeView.tsx | 31 +++++++++++++++++++---
 2 files changed, 28 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cc6a2f4a5..41a22c558 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -470,7 +470,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                             strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top })));
                         }
                     });
-                    console.log(strokes)
 
                     CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
                         console.log(results);
@@ -487,7 +486,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                                 if (this._wordPalette.has(word.recognizedText.toLowerCase())) {
                                     inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase());
                                 }
-                                else {
+                                else if (word.alternates) {
                                     for (const alt of word.alternates) {
                                         if (this._wordPalette.has(alt.recognizedString.toLowerCase())) {
                                             inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase());
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e82ca6bf2..d4faa4dc1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -384,16 +384,39 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             });
             const inkFields = inks.map(i => Cast(i.data, InkField));
             CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
+                // const wordResults = results.filter((r: any) => r.category === "inkWord");
+                // console.log(wordResults);
+                // console.log(results);
+                // for (const word of wordResults) {
+                //     const indices: number[] = word.strokeIds;
+                //     indices.forEach(i => {
+                //         if (wordToColor.has(word.recognizedText.toLowerCase())) {
+                //             inks[i].color = wordToColor.get(word.recognizedText.toLowerCase());
+                //         }
+                //         else {
+                //             for (const alt of word.alternates) {
+                //                 if (wordToColor.has(alt.recognizedString.toLowerCase())) {
+                //                     inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase());
+                //                     break;
+                //                 }
+                //             }
+                //         }
+                //     })
+                // }
                 const wordResults = results.filter((r: any) => r.category === "inkWord");
-                console.log(wordResults);
-                console.log(results);
                 for (const word of wordResults) {
                     const indices: number[] = word.strokeIds;
                     indices.forEach(i => {
+                        const otherInks: Doc[] = [];
+                        indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2]));
+                        inks[i].relatedInks = new List<Doc>(otherInks);
+                        const uniqueColors: string[] = [];
+                        Array.from(wordToColor.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c));
+                        inks[i].alternativeColors = new List<string>(uniqueColors);
                         if (wordToColor.has(word.recognizedText.toLowerCase())) {
                             inks[i].color = wordToColor.get(word.recognizedText.toLowerCase());
                         }
-                        else {
+                        else if (word.alternates) {
                             for (const alt of word.alternates) {
                                 if (wordToColor.has(alt.recognizedString.toLowerCase())) {
                                     inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase());
@@ -401,7 +424,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                                 }
                             }
                         }
-                    })
+                    });
                 }
             });
         }
-- 
cgit v1.2.3-70-g09d2


From 33d5a12af14e1ed50e5c3164b363fbbc253506a0 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Tue, 11 Feb 2020 22:45:13 -0500
Subject: small fix

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

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ec01fc7a5..4b2ac94c1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1163,7 +1163,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 style={{
                     display: this._pullDirection ? "block" : "none",
                     top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
-                    left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
+                    // left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
+                    left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x : 0 : "auto",
                     width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
                     height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index aa07db2a0..a69de60df 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -339,8 +339,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         SelectionManager.DeselectAll();
         if (this.Document.onPointerDown) return;
         const touch = me.touchEvent.changedTouches.item(0);
-        console.log("DOWN", SelectionManager.SelectedDocuments());
-        console.log("down");
         if (touch) {
             this._downX = touch.clientX;
             this._downY = touch.clientY;
-- 
cgit v1.2.3-70-g09d2


From 5c139f0001cd10e5699a716c1e64d2c2c0b2d800 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 28 Feb 2020 12:17:03 -0500
Subject: allowing different text templates to be the default.  fixing
 transparency.  removing audio by default from notes.

---
 src/client/documents/Documents.ts                  |  5 +++-
 src/client/util/Import & Export/ImageUtils.ts      |  2 +-
 src/client/util/RichTextSchema.tsx                 |  8 ++---
 src/client/views/InkingControl.tsx                 |  3 +-
 src/client/views/TemplateMenu.tsx                  |  5 ++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |  3 +-
 src/client/views/nodes/DocumentView.tsx            |  6 ++--
 src/client/views/nodes/FormattedTextBox.scss       |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 34 +++++++++++++---------
 src/client/views/nodes/ImageBox.tsx                | 19 ++++++------
 src/new_fields/documentSchemas.ts                  | 13 +++++----
 12 files changed, 63 insertions(+), 41 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a0b8a6382..aab44f668 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -77,6 +77,8 @@ export interface DocumentOptions {
     _gridGap?: number; // gap between items in masonry view
     _xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts
     _yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts
+    _xPadding?: number;
+    _yPadding?: number;
     _itemIndex?: number; // which item index the carousel viewer is showing
     _showSidebar?: boolean;  //whether an annotationsidebar should be displayed for text docuemnts
     x?: number;
@@ -174,7 +176,7 @@ export namespace Docs {
         const TemplateMap: TemplateMap = new Map([
             [DocumentType.TEXT, {
                 layout: { view: FormattedTextBox, dataField: data },
-                options: { _height: 150 }
+                options: { _height: 150, _xMargin: 10, _yMargin: 10 }
             }],
             [DocumentType.HIST, {
                 layout: { view: HistogramBox, dataField: data },
@@ -407,6 +409,7 @@ export namespace Docs {
 
         const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "dropAction", "childDropAction", "_annotationOn",
             "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "_backgroundColor",
+            "_xMargin", "_yMargin", "_xPadding", "_yPadding",
             "_color", "isButton", "isBackground", "removeDropProperties", "treeViewOpen"];
 
         /**
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index ff909cc6b..ab8c73d15 100644
--- a/src/client/util/Import & Export/ImageUtils.ts	
+++ b/src/client/util/Import & Export/ImageUtils.ts	
@@ -24,7 +24,7 @@ export namespace ImageUtils {
         const proto = Doc.GetProto(document);
         proto["data-nativeWidth"] = nativeWidth;
         proto["data-nativeHeight"] = nativeHeight;
-        proto.contentSize = contentSize;
+        proto.contentSize = contentSize ? contentSize : undefined;
         return data !== undefined;
     };
 
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index a56aac9b1..4a80a1af8 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -910,11 +910,11 @@ export class DashFieldView {
                 }
                 e.preventDefault();
             }
-            if (e.key === "Enter" && e.ctrlKey) {
-                Doc.addEnumerationToTextField(self._textBoxDoc, node.attrs.fieldKey, [Docs.Create.TextDocument(self._fieldSpan.innerText, { title: self._fieldSpan.innerText })]);
-                e.preventDefault();
-            } else if (e.key === "Enter") {
+            if (e.key === "Enter") {
                 e.preventDefault();
+                if (e.ctrlKey) {
+                    Doc.addEnumerationToTextField(self._textBoxDoc, node.attrs.fieldKey, [Docs.Create.TextDocument(self._fieldSpan.innerText, { title: self._fieldSpan.innerText })]);
+                }
                 let newText = self._fieldSpan.innerText.startsWith(":=") ? ":=-computed-" : self._fieldSpan.innerText;
                 // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
                 // holds the different enumerated values for the field in the titles of its collected documents.
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 374c2df27..5cd3c265d 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -29,8 +29,7 @@ export class InkingControl {
         if (number < 0) {
             number = 0xFFFFFFFF + number + 1;
         }
-
-        return number.toString(16).toUpperCase();
+        return (number < 16 ? "0" : "") + number.toString(16).toUpperCase();
     }
 
     @undoBatch
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 595c3817e..5029b4074 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -61,6 +61,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         DocumentView.FloatDoc(topDocView, ex, ey);
     }
 
+    toggleAudio = (e: React.ChangeEvent<HTMLInputElement>): void => {
+        this.props.docViews.map(dv => dv.props.Document._showAudio = e.target.checked);
+    }
+
     @undoBatch
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
@@ -102,6 +106,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         const templateMenu: Array<JSX.Element> = [];
         this.props.templates.forEach((checked, template) =>
             templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
+        templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={this.props.docViews[0].Document._showAudio ? true : false} toggle={this.toggleAudio} />);
         templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={this.props.docViews[0].Document.z ? true : false} toggle={this.toggleFloat} />);
         templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
         this._addedKeys && Array.from(this._addedKeys).map(layout =>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4458c7dcf..ca8d5e18b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -51,8 +51,8 @@ export const panZoomSchema = createSchema({
     arrangeInit: ScriptField,
     useClusters: "boolean",
     fitToBox: "boolean",
-    xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
-    yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
+    _xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
+    _yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     panTransformType: "string",
     scrollHeight: "number",
     fitX: "number",
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b1cca027d..b8e9acf85 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -18,6 +18,7 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import "./MarqueeView.scss";
 import React = require("react");
 import { CollectionView } from "../CollectionView";
+import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -104,7 +105,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             });
         } else if (!e.ctrlKey) {
             this.props.addLiveTextDocument(
-                Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" }));
+                Docs.Create.TextDocument("", { _width: NumCast((FormattedTextBox.DefaultLayout as Doc)?._width) || 200, _height: 100, layout: FormattedTextBox.DefaultLayout, x: x, y: y, _autoHeight: true, title: "-typed text-" }));
         } else if (e.keyCode > 48 && e.keyCode <= 57) {
             const notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
             const text = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 06ca9b5f4..7683ad269 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -909,17 +909,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     render() {
         if (!(this.props.Document instanceof Doc)) return (null);
         const backgroundColor = StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
+        const finalColor = this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor;
         const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
         const borderRounding = this.layoutDoc.borderRounding;
         const localScale = fullDegree;
 
+        console.log("Background = " + this.props.Document.title + " " + finalColor);
         const highlightColors = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ?
             ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
             ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
         const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
         let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear;
         highlighting = highlighting && this.props.focus !== emptyFunction;  // bcz: hack to turn off highlighting onsidebar panel documents.  need to flag a document as not highlightable in a more direct way
-        return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown}
+        return <div id={this.props.Document[Id]} className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown}
             onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
             onPointerEnter={e => Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)}
             style={{
@@ -931,7 +933,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
                 border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
                 boxShadow: this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined,
-                background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor,
+                background: finalColor,
                 width: "100%",
                 height: "100%",
                 opacity: this.Document.opacity
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index c203ca0c3..db2bb751f 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -95,8 +95,8 @@
 
 .formattedTextBox-inner-rounded,
 .formattedTextBox-inner {
-    padding: 10px 10px;
     height: 100%;
+    white-space: pre-wrap; 
 }
 
 // .menuicon {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index a320cff75..3567dbbd5 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -375,12 +375,15 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%");
 
+    public static DefaultLayout: Doc | string | undefined;
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
+        this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => FormattedTextBox.DefaultLayout = this.props.Document.proto as Doc, icon: "eye" });
+        funcs.push({ description: "Reset Default Layout", event: () => FormattedTextBox.DefaultLayout = undefined, icon: "eye" });
         !this.props.Document.expandedTemplate && funcs.push({ description: "Make Template", event: () => { this.props.Document.isTemplateDoc = true; Doc.AddDocToList(Cast(Doc.UserDoc().noteTypes, Doc, null), "data", this.props.Document); }, icon: "eye" });
-        funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.props.Document._showSidebar = !this.props.Document._showSidebar; }, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Menubar", event: () => { e.stopPropagation(); this.toggleMenubar(); }, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Record Bullet", event: () => this.recordBullet(), icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
         ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
             funcs.push({
                 description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
@@ -1125,7 +1128,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 onPointerLeave={action(() => this._entered = false)}
             >
                 <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
-                    <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
+                    <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
+                        style={{
+                            padding: `${NumCast(this.Document._xMargin, 0)}px  ${NumCast(this.Document._yMargin, 0)}px`,
+                            pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
+                        }} />
                 </div>
                 {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
                     <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
@@ -1152,15 +1159,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                         </CollectionFreeFormView>
                         <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} />
                     </div>}
-                <div className="formattedTextBox-dictation"
-                    onClick={e => {
-                        this._recording ? this.stopDictation(true) : this.recordDictation();
-                        setTimeout(() => this._editorView!.focus(), 500);
-                        e.stopPropagation();
-                    }} >
-                    <FontAwesomeIcon className="formattedTExtBox-audioFont"
-                        style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
-                </div>
+                {!this.props.Document._showAudio ? (null) :
+                    <div className="formattedTextBox-dictation"
+                        onClick={e => {
+                            this._recording ? this.stopDictation(true) : this.recordDictation();
+                            setTimeout(() => this._editorView!.focus(), 500);
+                            e.stopPropagation();
+                        }} >
+                        <FontAwesomeIcon className="formattedTExtBox-audioFont"
+                            style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
+                    </div>}
             </div>
         );
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 99000a0a9..c46191270 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -407,15 +407,16 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                         ref={this._imgRef}
                         onError={this.onError} /></div>}
             </div>
-            <div className="imageBox-audioBackground"
-                onPointerDown={this.audioDown}
-                onPointerEnter={this.onPointerEnter}
-                style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }}
-            >
-                <FontAwesomeIcon className="imageBox-audioFont"
-                    style={{ color: [DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }}
-                    icon={!DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" />
-            </div>
+            {!this.props.Document._showAudio ? (null) :
+                <div className="imageBox-audioBackground"
+                    onPointerDown={this.audioDown}
+                    onPointerEnter={this.onPointerEnter}
+                    style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }}
+                >
+                    <FontAwesomeIcon className="imageBox-audioFont"
+                        style={{ color: [DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }}
+                        icon={!DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" />
+                </div>}
             {this.considerDownloadIcon}
             {this.considerGooglePhotosLink()}
             <FaceRectangles document={this.dataDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index f3726cb5a..7006163e0 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -15,9 +15,14 @@ export const documentSchema = createSchema({
     _nativeHeight: "number",    // "
     _width: "number",           // width of document in its container's coordinate system
     _height: "number",          // "
-    _showCaption: "string",      // whether editable caption text is overlayed at the bottom of the document 
-    _showTitle: "string",        // the fieldkey whose contents should be displayed at the top of the document
-    _showTitleHover: "string",   // the showTitle should be shown only on hover
+    _xPadding: "number",        // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
+    _yPadding: "number",        // pixels of padding on top/bottom of collectionfreeformview contents when fitToBox is set
+    _xMargin: "number",         // margin added on left/right of most documents to add separation from their container
+    _yMargin: "number",         // margin added on top/bottom of most documents to add separation from their container
+    _showCaption: "string",     // whether editable caption text is overlayed at the bottom of the document 
+    _showTitle: "string",       // the fieldkey whose contents should be displayed at the top of the document
+    _showTitleHover: "string",  // the showTitle should be shown only on hover
+    _showAudio: "boolean",      // whether to show the audio record icon on documents
     _freeformLayoutEngine: "string",// the string ID for the layout engine to use to layout freeform view documents
     _LODdisable: "boolean",     // whether to disbale LOD switching for CollectionFreeFormViews
     _pivotField: "string",      // specifies which field should be used as the timeline/pivot axis
@@ -53,8 +58,6 @@ export const documentSchema = createSchema({
     strokeWidth: "number",
     fontSize: "string",
     fitToBox: "boolean",        // whether freeform view contents should be zoomed/panned to fill the area of the document view
-    xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
-    yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     letterSpacing: "string",
     textTransform: "string"
 });
-- 
cgit v1.2.3-70-g09d2

