From 2781f77d46612720865ac0c1d115d6e013806103 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Tue, 3 Dec 2019 17:34:58 -0500
Subject: improved multi-user touch stuff

---
 src/client/views/Touchable.tsx | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index 0056a1d96..d0e3db8a5 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -32,6 +32,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
                     break;
                 case 2:
                     this.handle2PointersDown(e);
+                    break;
             }
 
             document.removeEventListener("touchmove", this.onTouch);
@@ -46,10 +47,12 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
     */
     @action
     protected onTouch = (e: TouchEvent): void => {
+        let myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+
         // if we're not actually moving a lot, don't consider it as dragging yet
         // if (!InteractionUtils.IsDragging(this.prevPoints, e.targetTouches, 5) && !this._touchDrag) return;
         this._touchDrag = true;
-        switch (e.targetTouches.length) {
+        switch (myTouches.length) {
             case 1:
                 this.handle1PointerMove(e);
                 break;
@@ -73,6 +76,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
 
     @action
     protected onTouchEnd = (e: TouchEvent): void => {
+        console.log(InteractionUtils.GetMyTargetTouches(e, this.prevPoints).length + " up");
         this._touchDrag = false;
         e.stopPropagation();
 
@@ -81,6 +85,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
             let pt = e.targetTouches.item(i);
             if (pt) {
                 if (this.prevPoints.has(pt.identifier)) {
+                    console.log("delete");
                     this.prevPoints.delete(pt.identifier);
                 }
             }
@@ -89,7 +94,10 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         if (e.targetTouches.length === 0) {
             this.prevPoints.clear();
         }
-        this.cleanUpInteractions();
+
+        if (this.prevPoints.size === 0 && e.targetTouches.length === 0) {
+            this.cleanUpInteractions();
+        }
     }
 
     cleanUpInteractions = (): void => {
-- 
cgit v1.2.3-70-g09d2


From b3b81c8400ce2a92df1ae8c0db9b422cf4a2cf4c Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Sat, 7 Dec 2019 14:48:43 -0500
Subject: i think this is better now

---
 src/client/util/InteractionUtils.ts |  2 +-
 src/client/views/Touchable.tsx      | 33 ++++++++++++++-------------------
 src/pen-gestures/GestureUtils.ts    |  8 ++++----
 3 files changed, 19 insertions(+), 24 deletions(-)

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

diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts
index c24c70bca..7390890c6 100644
--- a/src/client/util/InteractionUtils.ts
+++ b/src/client/util/InteractionUtils.ts
@@ -53,7 +53,7 @@ export namespace InteractionUtils {
      * @param oldPoint2 - previous point 2
      */
     export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
-        let threshold = 4;
+        let threshold = 3;
         let oldDist = TwoPointEuclidist(oldPoint1, oldPoint2);
         let newDist = TwoPointEuclidist(pt1, pt2);
 
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index d0e3db8a5..5dcfec768 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -26,7 +26,8 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         }
 
         if (this.prevPoints.size) {
-            switch (e.targetTouches.length) {
+            console.log(e.targetTouches.length);
+            switch (this.prevPoints.size) {
                 case 1:
                     this.handle1PointerDown(e);
                     break;
@@ -67,35 +68,29 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
                 if (this.prevPoints.has(pt.identifier)) {
                     this.prevPoints.set(pt.identifier, pt);
                 }
-                else {
-                    this.prevPoints.set(pt.identifier, pt);
-                }
             }
         }
     }
 
     @action
     protected onTouchEnd = (e: TouchEvent): void => {
-        console.log(InteractionUtils.GetMyTargetTouches(e, this.prevPoints).length + " up");
-        this._touchDrag = false;
-        e.stopPropagation();
-
+        // console.log(InteractionUtils.GetMyTargetTouches(e, this.prevPoints).length + " up");
         // remove all the touches associated with the event
-        for (let i = 0; i < e.targetTouches.length; i++) {
-            let pt = e.targetTouches.item(i);
-            if (pt) {
-                if (this.prevPoints.has(pt.identifier)) {
-                    console.log("delete");
-                    this.prevPoints.delete(pt.identifier);
-                }
+        for (let i = 0; i < e.changedTouches.length; i++) {
+            let pt = e.changedTouches.item(i);
+            if (pt && this.prevPoints.has(pt.identifier)) {
+                this.prevPoints.delete(pt.identifier);
             }
         }
+        this._touchDrag = false;
+        e.stopPropagation();
 
-        if (e.targetTouches.length === 0) {
-            this.prevPoints.clear();
-        }
 
-        if (this.prevPoints.size === 0 && e.targetTouches.length === 0) {
+        // if (e.targetTouches.length === 0) {
+        //     this.prevPoints.clear();
+        // }
+
+        if (this.prevPoints.size === 0) {
             this.cleanUpInteractions();
         }
     }
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index caaa0736d..7a14452c8 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -6,15 +6,15 @@ import { Doc } from "../new_fields/Doc";
 
 export namespace GestureUtils {
     namespace GestureDataTypes {
-        export type BoxData = Doc[];
+        export type BoxData = Array<Doc>;
     }
 
-    export const GestureRecognizer = new NDollarRecognizer(false);
-
     export enum Gestures {
         Box = "box"
     }
 
+    export const GestureRecognizer = new NDollarRecognizer(false);
+
     export function GestureOptions(name: Gestures, gestureData: any): (() => any)[] {
         switch (name) {
             case Gestures.Box:
@@ -27,7 +27,7 @@ export namespace GestureUtils {
     }
 
     function BoxOptions(gestureData: GestureDataTypes.BoxData): (() => any)[] {
-        if (gestureData instanceof Doc[]) {
+        if (gestureData instanceof Array) {
             return [() => Docs.Create.FreeformDocument(gestureData as Doc[], {})];
         }
         return [];
-- 
cgit v1.2.3-70-g09d2


From 766e47cd144be6f0bcbb5787dbd174aa1bba3690 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Sat, 7 Dec 2019 16:53:43 -0500
Subject: box !!!!

---
 src/client/util/InteractionUtils.ts                |  2 +-
 src/client/views/Touchable.tsx                     |  4 ++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 23 ++++++++++++++++++----
 src/client/views/nodes/DocumentView.tsx            |  8 ++++----
 src/pen-gestures/GestureUtils.ts                   | 16 ++++-----------
 src/pen-gestures/ndollar.ts                        |  2 +-
 6 files changed, 31 insertions(+), 24 deletions(-)

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

diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts
index 7390890c6..7c386abc2 100644
--- a/src/client/util/InteractionUtils.ts
+++ b/src/client/util/InteractionUtils.ts
@@ -53,7 +53,7 @@ export namespace InteractionUtils {
      * @param oldPoint2 - previous point 2
      */
     export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
-        let threshold = 3;
+        let threshold = window.devicePixelRatio;
         let oldDist = TwoPointEuclidist(oldPoint1, oldPoint2);
         let newDist = TwoPointEuclidist(pt1, pt2);
 
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index 5dcfec768..a647dec04 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -18,9 +18,9 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
     protected onTouchStart = (e: React.TouchEvent): void => {
         for (let i = 0; i < e.targetTouches.length; i++) {
             let pt: any = e.targetTouches.item(i);
-            // pen is also a touch, but with a radius of 0.5 (at least with the surface pens). i doubt anyone's fingers are 2 pixels wide,
+            // pen is also a touch, but with a radius of 0.5 (at least with the surface pens).
             // and this seems to be the only way of differentiating pen and touch on touch events
-            if (pt.radiusX > 2 && pt.radiusY > 2) {
+            if (pt.radiusX > 0.5 && pt.radiusY > 0.5) {
                 this.prevPoints.set(pt.identifier, pt);
             }
         }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6dd472c84..bec0ace5a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -340,8 +340,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             let points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
 
             let result = GestureUtils.GestureRecognizer.Recognize(new Array(points));
-            if (result) {
-                console.log(result.Name);
+            if (result && result.Score > 0.75) {
+                switch (result.Name) {
+                    case GestureUtils.Gestures.Box:
+                        let bounds = { x: Math.min(...this._points.map(p => p.X)), r: Math.max(...this._points.map(p => p.X)), y: Math.min(...this._points.map(p => p.y)), b: Math.max(...this._points.map(p => p.Y)) };
+                        let sel = this.getActiveDocuments().filter(doc => {
+                            let l = NumCast(doc.x);
+                            let r = l + doc[WidthSym]();
+                            let t = NumCast(doc.y);
+                            let b = t + doc[HeightSym]();
+                            doc.x = l - B.left - B.width / 2;
+                            doc.y = t - B.top - B.height / 2;
+                            return !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t);
+                        });
+                        this.addDocument(Docs.Create.FreeformDocument(sel, { x: B.left, y: B.top, width: B.width, height: B.height, panX: 0, panY: 0 }));
+                        sel.forEach(d => this.props.removeDocument(d));
+                        break;
+                }
                 this._points = [];
             }
             else {
@@ -474,8 +489,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         let rawDelta = (dir * (d1 + d2));
 
                         // this floors and ceils the delta value to prevent jitteriness
-                        let delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 16);
-                        this.zoom(centerX, centerY, delta);
+                        let delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8);
+                        this.zoom(centerX, centerY, delta * window.devicePixelRatio);
                         this.prevPoints.set(pt1.identifier, pt1);
                         this.prevPoints.set(pt2.identifier, pt2);
                     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 38c46b258..dfeadb8c0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -142,7 +142,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             e.stopPropagation();
             let preventDefault = true;
-            if (this._doubleTap && this.props.renderDepth && !this.onClickHandler ?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+            if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 let fullScreenAlias = Doc.MakeAlias(this.props.Document);
                 if (StrCast(fullScreenAlias.layoutKey) !== "layoutCustom" && fullScreenAlias.layoutCustom !== undefined) {
                     fullScreenAlias.layoutKey = "layoutCustom";
@@ -362,7 +362,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     @action
     setCustomView = (custom: boolean): void => {
-        if (this.props.ContainingCollectionView ?.props.DataDoc || this.props.ContainingCollectionView ?.props.Document.isTemplateDoc) {
+        if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) {
             Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document);
         } else {
             custom ? DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc) : DocumentView.makeNativeViewClicked(this.props.Document);
@@ -687,10 +687,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont}
             onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
             onPointerEnter={e => {
-                console.log("Brush" + this.props.Document.title);
+                // console.log("Brush" + this.props.Document.title);
                 Doc.BrushDoc(this.props.Document);
             }} onPointerLeave={e => {
-                console.log("UnBrush" + this.props.Document.title);
+                // console.log("UnBrush"     + this.props.Document.title);
                 Doc.UnBrushDoc(this.props.Document);
 
             }}
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 7a14452c8..955dad5b4 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -2,7 +2,9 @@ import { NDollarRecognizer } from "./ndollar";
 import { Type } from "typescript";
 import { InkField } from "../new_fields/InkField";
 import { Docs } from "../client/documents/Documents";
-import { Doc } from "../new_fields/Doc";
+import { Doc, WidthSym, HeightSym } from "../new_fields/Doc";
+import { NumCast } from "../new_fields/Types";
+import { CollectionFreeFormView } from "../client/views/collections/collectionFreeForm/CollectionFreeFormView";
 
 export namespace GestureUtils {
     namespace GestureDataTypes {
@@ -15,21 +17,11 @@ export namespace GestureUtils {
 
     export const GestureRecognizer = new NDollarRecognizer(false);
 
-    export function GestureOptions(name: Gestures, gestureData: any): (() => any)[] {
+    export function GestureOptions(name: string, gestureData?: any): (params: {}) => any {
         switch (name) {
             case Gestures.Box:
-                if (gestureData as GestureDataTypes.BoxData) {
-                    return BoxOptions(gestureData as GestureDataTypes.BoxData);
-                }
                 break;
         }
         throw new Error("This means that you're trying to do something with the gesture that hasn't been defined yet. Define it in GestureUtils.ts");
     }
-
-    function BoxOptions(gestureData: GestureDataTypes.BoxData): (() => any)[] {
-        if (gestureData instanceof Array) {
-            return [() => Docs.Create.FreeformDocument(gestureData as Doc[], {})];
-        }
-        return [];
-    }
 }
\ No newline at end of file
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 5d4384202..8c8e079a4 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -168,7 +168,7 @@ export class NDollarRecognizer {
         //
         this.Multistrokes = new Array(NumMultistrokes);
         this.Multistrokes[0] = new Multistroke(GestureUtils.Gestures.Box, useBoundedRotationInvariance, new Array(
-            new Array(new Point(30, 146), new Point(30, 222), new Point(106, 225), new Point(106, 146))
+            new Array(new Point(30, 146), new Point(30, 222), new Point(106, 225), new Point(106, 146), new Point(30, 146))
         ));
 
         //
-- 
cgit v1.2.3-70-g09d2


From d16636a8246dadb486476fe98da63fc0db4d36dc Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Tue, 7 Jan 2020 14:49:27 -0500
Subject: resizing works, the math is just wrong looool

---
 src/client/util/InteractionUtils.ts                |   2 +-
 src/client/views/Touchable.tsx                     |  21 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  44 ++++-
 src/client/views/nodes/DocumentView.tsx            | 191 +++++++++++++++++----
 src/client/views/nodes/FormattedTextBox.scss       |  20 ++-
 5 files changed, 220 insertions(+), 58 deletions(-)

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

diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts
index 7c386abc2..2d3671041 100644
--- a/src/client/util/InteractionUtils.ts
+++ b/src/client/util/InteractionUtils.ts
@@ -8,7 +8,7 @@ export namespace InteractionUtils {
     const REACT_POINTER_PEN_BUTTON = 0;
     const ERASER_BUTTON = 5;
 
-    export function GetMyTargetTouches(e: TouchEvent, prevPoints: Map<number, React.Touch>): React.Touch[] {
+    export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map<number, React.Touch>): React.Touch[] {
         let myTouches = new Array<React.Touch>();
         for (let i = 0; i < e.targetTouches.length; i++) {
             let pt = e.targetTouches.item(i);
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index a647dec04..7b0581376 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -26,7 +26,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         }
 
         if (this.prevPoints.size) {
-            console.log(e.targetTouches.length);
             switch (this.prevPoints.size) {
                 case 1:
                     this.handle1PointerDown(e);
@@ -35,11 +34,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
                     this.handle2PointersDown(e);
                     break;
             }
-
-            document.removeEventListener("touchmove", this.onTouch);
-            document.addEventListener("touchmove", this.onTouch);
-            document.removeEventListener("touchend", this.onTouchEnd);
-            document.addEventListener("touchend", this.onTouchEnd);
         }
     }
 
@@ -110,6 +104,17 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         e.preventDefault();
     }
 
-    handle1PointerDown = (e: React.TouchEvent): any => { };
-    handle2PointersDown = (e: React.TouchEvent): any => { };
+    handle1PointerDown = (e: React.TouchEvent): any => {
+        document.removeEventListener("touchmove", this.onTouch);
+        document.addEventListener("touchmove", this.onTouch);
+        document.removeEventListener("touchend", this.onTouchEnd);
+        document.addEventListener("touchend", this.onTouchEnd);
+    }
+
+    handle2PointersDown = (e: React.TouchEvent): any => {
+        document.removeEventListener("touchmove", this.onTouch);
+        document.addEventListener("touchmove", this.onTouch);
+        document.removeEventListener("touchend", this.onTouchEnd);
+        document.addEventListener("touchend", this.onTouchEnd);
+    }
 }
\ 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 7b507f739..75690ab2c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -326,9 +326,30 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     handle1PointerDown = (e: React.TouchEvent) => {
+        if (e.nativeEvent.cancelBubble) return;
         let pt = e.targetTouches.item(0);
         if (pt) {
             this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false;
+            if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) {
+                document.removeEventListener("touchmove", this.onTouch);
+                document.addEventListener("touchmove", this.onTouch);
+                document.removeEventListener("touchend", this.onTouchEnd);
+                document.addEventListener("touchend", this.onTouchEnd);
+                if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) {
+                    e.stopPropagation();
+                    e.preventDefault();
+                    let point = this.getTransform().transformPoint(pt.pageX, pt.pageY);
+                    this._points.push({ X: point[0], Y: point[1] });
+                }
+                else if (InkingControl.Instance.selectedTool === InkTool.None) {
+                    this._lastX = pt.pageX;
+                    this._lastY = pt.pageY;
+                }
+                else {
+                    e.stopPropagation();
+                    e.preventDefault();
+                }
+            }
         }
     }
 
@@ -544,14 +565,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     handle2PointersDown = (e: React.TouchEvent) => {
-        let pt1: React.Touch | null = e.targetTouches.item(0);
-        let pt2: React.Touch | null = e.targetTouches.item(1);
-        if (!pt1 || !pt2) return;
-
-        let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
-        let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-        this._lastX = centerX;
-        this._lastY = centerY;
+        if (!e.nativeEvent.cancelBubble && this.props.active(true)) {
+            let pt1: React.Touch | null = e.targetTouches.item(0);
+            let pt2: React.Touch | null = e.targetTouches.item(1);
+            if (!pt1 || !pt2) return;
+
+            let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
+            let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+            this._lastX = centerX;
+            this._lastY = centerY;
+            document.removeEventListener("touchmove", this.onTouch);
+            document.addEventListener("touchmove", this.onTouch);
+            document.removeEventListener("touchend", this.onTouchEnd);
+            document.addEventListener("touchend", this.onTouchEnd);
+            e.stopPropagation();
+        }
     }
 
     cleanUpInteractions = () => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index dfeadb8c0..1780d9789 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,7 +4,7 @@ import { action, computed, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
 import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc";
-import { Document } from '../../../new_fields/documentSchemas';
+import { Document, PositionDocument } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { listSpec } from "../../../new_fields/Schema";
 import { ScriptField } from '../../../new_fields/ScriptField';
@@ -193,28 +193,165 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
+    handle1PointerDown = (e: React.TouchEvent) => {
+        if (!e.nativeEvent.cancelBubble) {
+            let touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
+            this._downX = touch.clientX;
+            this._downY = touch.clientY;
+            this._hitTemplateDrag = false;
+            for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
+                if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
+                    this._hitTemplateDrag = true;
+                }
+            }
+            if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation();
+            document.removeEventListener("touchmove", this.onTouch);
+            document.addEventListener("touchmove", this.onTouch);
+            document.removeEventListener("touchend", this.onTouchEnd);
+            document.addEventListener("touchend", this.onTouchEnd);
+            if ((e.nativeEvent as any).formattedHandled) e.stopPropagation();
+            console.log("down")
+        }
+    }
+
+    handle1PointerMove = (e: TouchEvent) => {
+        if ((e as any).formattedHandled) { e.stopPropagation; return; }
+        if (e.cancelBubble && this.active) {
+            document.removeEventListener("touchmove", this.onTouch);
+        }
+        else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
+            let touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
+            if (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3) {
+                if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick)) {
+                    document.removeEventListener("touchmove", this.onTouch);
+                    document.removeEventListener("touchend", this.onTouchEnd);
+                    this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
+                }
+            }
+            e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
+            e.preventDefault();
+
+        }
+    }
+
+    handle2PointersDown = (e: React.TouchEvent) => {
+        if (!e.nativeEvent.cancelBubble && !this.isSelected()) {
+            e.stopPropagation();
+            e.preventDefault();
+
+            document.removeEventListener("touchmove", this.onTouch);
+            document.addEventListener("touchmove", this.onTouch);
+            document.removeEventListener("touchend", this.onTouchEnd);
+            document.addEventListener("touchend", this.onTouchEnd);
+        }
+    }
+
+    @action
+    handle2PointersMove = (e: TouchEvent) => {
+        let myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+        let pt1 = myTouches[0];
+        let pt2 = myTouches[1];
+        let oldPoint1 = this.prevPoints.get(pt1.identifier);
+        let oldPoint2 = this.prevPoints.get(pt2.identifier);
+        let pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
+        if (pinching !== 0 && oldPoint1 && oldPoint2) {
+            // let dX = (Math.min(pt1.clientX, pt2.clientX) - Math.min(oldPoint1.clientX, oldPoint2.clientX));
+            // let dY = (Math.min(pt1.clientY, pt2.clientY) - Math.min(oldPoint1.clientY, oldPoint2.clientY));
+            // let dX = Math.sign(Math.abs(pt1.clientX - oldPoint1.clientX) - Math.abs(pt2.clientX - oldPoint2.clientX));
+            // let dY = Math.sign(Math.abs(pt1.clientY - oldPoint1.clientY) - Math.abs(pt2.clientY - oldPoint2.clientY));
+            // let dW = -dX;
+            // let dH = -dY;
+            let dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
+            let dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
+            let dX = -1 * Math.sign(dW);
+            let dY = -1 * Math.sign(dH);
+
+            if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
+                let doc = PositionDocument(this.props.Document);
+                let layoutDoc = PositionDocument(Doc.Layout(this.props.Document));
+                let nwidth = layoutDoc.nativeWidth || 0;
+                let nheight = layoutDoc.nativeHeight || 0;
+                let width = (layoutDoc.width || 0);
+                let height = (layoutDoc.height || (nheight / nwidth * width));
+                let scale = this.props.ScreenToLocalTransform().Scale * this.props.ContentScaling();
+                let actualdW = Math.max(width + (dW * scale), 20);
+                let actualdH = Math.max(height + (dH * scale), 20);
+                doc.x = (doc.x || 0) + dX * (actualdW - width);
+                doc.y = (doc.y || 0) + dY * (actualdH - height);
+                let fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight);
+                if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) {
+                    layoutDoc.ignoreAspect = false;
+                    layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
+                    layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
+                }
+                if (fixedAspect && (!nwidth || !nheight)) {
+                    layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
+                    layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
+                }
+                if (nwidth > 0 && nheight > 0 && !layoutDoc.ignoreAspect) {
+                    if (Math.abs(dW) > Math.abs(dH)) {
+                        if (!fixedAspect) {
+                            layoutDoc.nativeWidth = actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0);
+                        }
+                        layoutDoc.width = actualdW;
+                        if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width;
+                        else layoutDoc.height = actualdH;
+                    }
+                    else {
+                        if (!fixedAspect) {
+                            layoutDoc.nativeHeight = actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0);
+                        }
+                        layoutDoc.height = actualdH;
+                        if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height;
+                        else layoutDoc.width = actualdW;
+                    }
+                } else {
+                    dW && (layoutDoc.width = actualdW);
+                    dH && (layoutDoc.height = actualdH);
+                    dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false);
+                }
+            }
+            // let newWidth = Math.max(Math.abs(oldPoint1!.clientX - oldPoint2!.clientX), Math.abs(pt1.clientX - pt2.clientX))
+            // this.props.Document.width = newWidth;
+            e.stopPropagation();
+            e.preventDefault();
+        }
+    }
+
     onPointerDown = (e: React.PointerEvent): void => {
-        if ((e.nativeEvent.cancelBubble && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)))
-            // return if we're inking, and not selecting a button document
-            || (InkingControl.Instance.selectedTool !== InkTool.None && !this.Document.onClick)
-            // return if using pen or eraser
-            || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InteractionUtils.IsType(e, InteractionUtils.ERASERTYPE)) return;
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        this._hitTemplateDrag = false;
-        // this whole section needs to move somewhere else.  We're trying to initiate a special "template" drag where
-        // this document is the template and we apply it to whatever we drop it on.
-        for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
-            if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
-                this._hitTemplateDrag = true;
+        // console.log(e.button)
+        // console.log(e.nativeEvent)
+        // continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document)
+        if (!InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE)) {
+            e.stopPropagation();
+            return;
+        }
+        if ((!e.nativeEvent.cancelBubble || this.Document.onClick || this.Document.onDragStart)) {
+            // if ((e.nativeEvent.cancelBubble && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)))
+            //     // return if we're inking, and not selecting a button document
+            //     || (InkingControl.Instance.selectedTool !== InkTool.None && !this.Document.onClick)
+            //     // return if using pen or eraser
+            //     || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InteractionUtils.IsType(e, InteractionUtils.ERASERTYPE)) {
+            //     return;
+            // }
+
+            this._downX = e.clientX;
+            this._downY = e.clientY;
+            this._hitTemplateDrag = false;
+            // this whole section needs to move somewhere else.  We're trying to initiate a special "template" drag where
+            // this document is the template and we apply it to whatever we drop it on.
+            for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
+                if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
+                    this._hitTemplateDrag = true;
+                }
             }
+            if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); // events stop at the lowest document that is active.  if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
+            document.removeEventListener("pointermove", this.onPointerMove);
+            document.removeEventListener("pointerup", this.onPointerUp);
+            document.addEventListener("pointermove", this.onPointerMove);
+            document.addEventListener("pointerup", this.onPointerUp);
+            if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
         }
-        if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); // events stop at the lowest document that is active.  if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        document.addEventListener("pointermove", this.onPointerMove);
-        document.addEventListener("pointerup", this.onPointerUp);
-        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
     }
 
     onPointerMove = (e: PointerEvent): void => {
@@ -649,20 +786,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return (this.Document.isBackground && !this.isSelected()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
     }
 
-    @action
-    handle2PointersMove = (e: TouchEvent) => {
-        let myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
-        let pt1 = myTouches[0];
-        let pt2 = myTouches[1];
-        let oldPoint1 = this.prevPoints.get(pt1.identifier);
-        let oldPoint2 = this.prevPoints.get(pt2.identifier);
-        let pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
-        if (pinching !== 0) {
-            let newWidth = Math.max(Math.abs(oldPoint1!.clientX - oldPoint2!.clientX), Math.abs(pt1.clientX - pt2.clientX))
-            this.props.Document.width = newWidth;
-        }
-    }
-
     render() {
         if (!this.props.Document) return (null);
         const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 77cdd3d42..2e5848db4 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -11,6 +11,7 @@
 }
 
 .formattedTextBox-cont {
+    touch-action: none;
     cursor: text;
     background: inherit;
     padding: 0;
@@ -50,6 +51,7 @@
     width: 100%;
     height: 100%;
 }
+
 .formattedTextBox-sidebar-handle {
     position: absolute;
     top: calc(50% - 17.5px);
@@ -58,19 +60,23 @@
     background: lightgray;
     border-radius: 20px;
 }
-.formattedTextBox-cont > .formattedTextBox-sidebar-handle {
+
+.formattedTextBox-cont>.formattedTextBox-sidebar-handle {
     right: 0;
     left: unset;
 }
-.formattedTextBox-sidebar, .formattedTextBox-sidebar-inking {
-    border-left: dashed 1px black; 
-    height: 100%; 
+
+.formattedTextBox-sidebar,
+.formattedTextBox-sidebar-inking {
+    border-left: dashed 1px black;
+    height: 100%;
     display: inline-block;
     position: absolute;
     right: 0;
-    > .formattedTextBox-sidebar-handle {
-        right:unset;
-        left:-5;
+
+    >.formattedTextBox-sidebar-handle {
+        right: unset;
+        left: -5;
     }
 }
 
-- 
cgit v1.2.3-70-g09d2


From 685ba9666929eddac09a09e77a2e4df1322af066 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Wed, 8 Jan 2020 15:32:49 -0500
Subject: starter stuff for touch hold

---
 src/client/views/Touchable.tsx          | 21 +++++++++++++++++++--
 src/client/views/nodes/DocumentView.tsx |  5 +++++
 2 files changed, 24 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index b19984327..251cd41e5 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -2,7 +2,11 @@ import * as React from 'react';
 import { action } from 'mobx';
 import { InteractionUtils } from '../util/InteractionUtils';
 
+const HOLD_DURATION = 1000;
+
 export abstract class Touchable<T = {}> extends React.Component<T> {
+    private holdTimer: NodeJS.Timeout | undefined;
+
     protected _touchDrag: boolean = false;
     protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
 
@@ -29,6 +33,8 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
             switch (this.prevPoints.size) {
                 case 1:
                     this.handle1PointerDown(e);
+                    e.persist();
+                    this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(e), HOLD_DURATION);
                     break;
                 case 2:
                     this.handle2PointersDown(e);
@@ -45,9 +51,11 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
 
         // if we're not actually moving a lot, don't consider it as dragging yet
-        // if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
-        console.log(myTouches.length)
+        if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
         this._touchDrag = true;
+        if (this.holdTimer) {
+            clearTimeout(this.holdTimer);
+        }
         switch (myTouches.length) {
             case 1:
                 this.handle1PointerMove(e);
@@ -79,6 +87,9 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
                 }
             }
         }
+        if (this.holdTimer) {
+            clearTimeout(this.holdTimer);
+        }
         this._touchDrag = false;
         e.stopPropagation();
 
@@ -120,4 +131,10 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         document.removeEventListener("touchend", this.onTouchEnd);
         document.addEventListener("touchend", this.onTouchEnd);
     }
+
+    handle1PointerHoldStart = (e: React.TouchEvent): any => {
+        console.log("Hold");
+        e.stopPropagation();
+        e.preventDefault();
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a01e77c4e..a14f69f71 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -584,6 +584,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @action
     onContextMenu = async (e: React.MouseEvent): Promise<void> => {
+        // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
+        if (e.button === 0) {
+            e.preventDefault();
+            return;
+        }
         e.persist();
         e.stopPropagation();
         if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 ||
-- 
cgit v1.2.3-70-g09d2

