From 1cb74bdb046a618fa017edca7de674992d067752 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Sun, 13 Oct 2019 13:41:08 -0400
Subject: ahh

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

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

diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 1cfa8d644..0eb4c66a2 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -183,7 +183,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
         let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect";
         return (
             <div className="inkingCanvas">
-                <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
+                <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} onTouchStart={(e) => e.stopPropagation()} />
                 {this.props.children()}
                 {this.drawnPaths}
             </div >
-- 
cgit v1.2.3-70-g09d2


From caebaeca6993ff48609ab16dcc390d4d0458b03a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 16 Oct 2019 23:50:27 -0400
Subject: fixed applying templates to collections.  fixed templates containing
 collections

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/views/CollectionLinearView.tsx          | 35 ++++++++++++++--------
 src/client/views/InkingCanvas.tsx                  |  3 +-
 src/client/views/InkingControl.tsx                 |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../views/collections/CollectionViewChromes.tsx    |  4 +--
 src/client/views/nodes/ColorBox.tsx                |  7 ++---
 src/client/views/nodes/FormattedTextBox.tsx        |  4 +--
 src/new_fields/Doc.ts                              | 14 +++++++--
 .../authentication/models/current_user_utils.ts    | 14 +++------
 10 files changed, 51 insertions(+), 36 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2ffbc8394..62175cbe3 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -69,7 +69,7 @@ export interface DocumentOptions {
     preventTreeViewOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expande/collapse state to be independent of other views of the same document in the tree view
     layout?: string | Doc;
     hideHeadings?: boolean; // whether stacking view column headings should be hidden
-    isTemplate?: boolean;
+    isTemplateField?: boolean;
     isTemplateDoc?: boolean;
     templates?: List<string>;
     viewType?: number;
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 3e2ab1459..04e131135 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -47,24 +47,35 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         }
     }
 
+    makeTemplate = (doc: Doc): boolean => {
+        let layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
+        let layout = StrCast(layoutDoc.layout).match(/fieldKey={"[^"]*"}/)![0];
+        let fieldKey = layout.replace('fieldKey={"', "").replace(/"}$/, "");
+        let docs = DocListCast(layoutDoc[fieldKey]);
+        let any = false;
+        docs.map(d => {
+            if (!StrCast(d.title).startsWith("-")) {
+                any = true;
+                return Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc));
+            }
+            if (d.type === DocumentType.COL) return this.makeTemplate(d);
+            return false;
+        });
+        return any;
+    }
+
     drop = action((e: Event, de: DragManager.DropEvent) => {
         (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => {
             let dbox = doc;
             if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) {
-                let template = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
-                if (template.type === DocumentType.COL) {
-                    let layout = StrCast(template.layout).match(/fieldKey={"[^"]*"}/)![0];
-                    let fieldKey = layout.replace('fieldKey={"', "").replace(/"}$/, "");
-                    let docs = DocListCast(template[fieldKey]);
-                    docs.map(d => {
-                        Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(template));
-                    });
-                    template.isTemplateDoc = true;
+                let layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
+                if (layoutDoc.type === DocumentType.COL) {
+                    layoutDoc.isTemplateDoc = this.makeTemplate(layoutDoc);
                 } else {
-                    template.isTemplateDoc = (template.type === DocumentType.TEXT || template.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction;
+                    layoutDoc.isTemplateDoc = (layoutDoc.type === DocumentType.TEXT || layoutDoc.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction;
                 }
-                dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: template.isTemplateDoc ? "font" : "bolt" });
-                dbox.dragFactory = template;
+                dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" });
+                dbox.dragFactory = layoutDoc;
                 dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
                 dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
             } else if (doc.viewType === CollectionViewType.Linear) {
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 9ab320eab..920ebaedd 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -181,9 +181,10 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     render() {
         let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect";
+        let cursor = svgCanvasStyle === "canSelect" ? (InkingControl.Instance.selectedTool === InkTool.Eraser ? "pointer" : "default") : undefined;
         return (
             <div className="inkingCanvas">
-                <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
+                <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} style={{ cursor: cursor }} />
                 {this.props.children()}
                 {this.drawnPaths}
             </div >
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index bc5249acd..b8f3c2875 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -94,7 +94,7 @@ export class InkingControl {
                 redo: () => oldColors.forEach(pair => pair.target.backgroundColor = captured)
             });
         } else {
-            CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen instanceof Doc && (CurrentUserUtils.UserDocument.activePen.pen.backgroundColor = this._selectedColor);
+            CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.backgroundColor = this._selectedColor);
         }
     });
     @action
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 46fbb5910..1f8c0b18f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -61,7 +61,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     if (args[1] instanceof Doc) {
                         this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc)));
                     } else {
-                        this.childDocs.map(async doc => doc.layout = undefined);
+                        this.childDocs.filter(d => !d.isTemplateField).map(async doc => doc.layout = undefined);
                     }
                 });
 
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 3a66c05f4..a5b7f0181 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -40,9 +40,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
 
     _templateCommand = {
-        title: "set template", script: "this.target.childLayout = this.source ? this.source[0] : undefined", params: ["target", "source"],
+        title: "set template", script: "setChildLayout(this.target, this.source && this.source.length ? this.source[0]:undefined)", params: ["target", "source"],
         initialize: emptyFunction,
-        immediate: (draggedDocs: Doc[]) => this.props.CollectionView.props.Document.childLayout = draggedDocs.length ? draggedDocs[0] : undefined
+        immediate: (draggedDocs: Doc[]) => Doc.setChildLayout(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined)
     };
     _contentCommand = {
         // title: "set content", script: "getProto(this.target).data = aliasDocs(this.source.map(async p => await p));", params: ["target", "source"],  // bcz: doesn't look like we can do async stuff in scripting...
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index e4fef0922..30554ea36 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -25,9 +25,8 @@ export class ColorBox extends DocStaticComponent<FieldViewProps, ColorDocument>(
         this._selectedDisposer = reaction(() => SelectionManager.SelectedDocuments(),
             action(() => this._startupColor = SelectionManager.SelectedDocuments().length ? StrCast(SelectionManager.SelectedDocuments()[0].Document.backgroundColor, "black") : "black"),
             { fireImmediately: true });
-        this._penDisposer = reaction(() => CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen,
-            action(() => this._startupColor = CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen instanceof Doc ?
-                StrCast(CurrentUserUtils.UserDocument.activePen.pen.backgroundColor, "black") : "black"),
+        this._penDisposer = reaction(() => CurrentUserUtils.ActivePen,
+            action(() => this._startupColor = CurrentUserUtils.ActivePen ? StrCast(CurrentUserUtils.ActivePen.backgroundColor, "black") : "black"),
             { fireImmediately: true });
 
         // compare to this reaction that used to be in Selection Manager
@@ -36,7 +35,7 @@ export class ColorBox extends DocStaticComponent<FieldViewProps, ColorDocument>(
         //     if (sel.length > 0) {
         //         let firstView = sel[0];
         //         let doc = firstView.props.Document;
-        //         let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc);
+        //         let targetDoc = doc.isTemplateField ? doc : Doc.GetProto(doc);
         //         let stored = StrCast(targetDoc.backgroundColor);
         //         stored.length > 0 && (targetColor = stored);
         //     }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ea82b1161..045eee3f7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -143,7 +143,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
 
-    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); }
+    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); }
 
     // the document containing the view layout information - will be the Document itself unless the Document has
     // a layout field.  In that case, all layout information comes from there unless overriden by Document
@@ -265,7 +265,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 }
                 // apply as template when dragging with Meta
             } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "MetaKey") {
-                draggedDoc.isTemplate = true;
+                draggedDoc.isTemplateDoc = true;
                 let newLayout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
                 if (typeof (draggedDoc.layout) === "string") {
                     newLayout = Doc.MakeDelegate(draggedDoc);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 51fcb818a..04f310785 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -599,7 +599,7 @@ export namespace Doc {
         return target;
     }
 
-    export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc, suppressTitle: boolean = false) {
+    export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc, suppressTitle: boolean = false): boolean {
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
         let metadataFieldName = StrCast(fieldTemplate.title).replace(/^-/, "");
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
@@ -613,7 +613,7 @@ export namespace Doc {
 
         fieldTemplate.templateField = metadataFieldName;
         fieldTemplate.title = metadataFieldName;
-        fieldTemplate.isTemplate = true;
+        fieldTemplate.isTemplateField = true;
         fieldTemplate.backgroundLayout = backgroundLayout;
         /* move certain layout properties from the original data doc to the template layout to avoid
            inheriting them from the template's data doc which may also define these fields for its own use.
@@ -636,6 +636,7 @@ export namespace Doc {
             if (fieldTemplate.backgroundColor !== templateDataDoc.defaultBackgroundColor) fieldTemplate.defaultBackgroundColor = fieldTemplate.backgroundColor;
             fieldTemplate.proto = templateDataDoc;
         }), 0);
+        return true;
     }
 
     export function overlapping(doc: Doc, doc2: Doc, clusterDistance: number) {
@@ -735,9 +736,18 @@ export namespace Doc {
     export function UnBrushAllDocs() {
         manager.BrushedDoc.clear();
     }
+
+    export function setChildLayout(target: Doc, source?: Doc) {
+        target.childLayout = source && source.isTemplateDoc ? source : source &&
+            source.dragFactory instanceof Doc && source.dragFactory.isTemplateDoc ? source.dragFactory :
+            source && source.layout instanceof Doc && source.layout.isTemplateDoc ? source.layout : undefined;
+    }
 }
+
+
 Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; });
 Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); });
+Scripting.addGlobal(function setChildLayout(target: any, source: any) { Doc.setChildLayout(target, source); });
 Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
 Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
 Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); });
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 5ce707011..e6f202685 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -23,16 +23,11 @@ export class CurrentUserUtils {
     public static get MainDocId() { return this.mainDocId; }
     public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
     @computed public static get UserDocument() { return Doc.UserDoc(); }
+    @computed public static get ActivePen() { return Doc.UserDoc().activePen instanceof Doc && (Doc.UserDoc().activePen as Doc).pen as Doc; }
 
     @observable public static GuestTarget: Doc | undefined;
     @observable public static GuestWorkspace: Doc | undefined;
 
-    private static createUserDocument(id: string): Doc {
-        let doc = new Doc(id, true);
-        doc.title = Doc.CurrentUserEmail;
-        return this.updateUserDocument(doc);// this should be the last 
-    }
-
     // a default set of note types .. not being used yet...
     static setupNoteTypes(doc: Doc) {
         return [
@@ -174,6 +169,7 @@ export class CurrentUserUtils {
     }
 
     static updateUserDocument(doc: Doc) {
+        doc.title = Doc.CurrentUserEmail;
         new InkingControl();
         (doc.optionalRightCollection === undefined) && CurrentUserUtils.setupMobileUploads(doc);
         (doc.overlays === undefined) && CurrentUserUtils.setupOverlays(doc);
@@ -216,10 +212,8 @@ export class CurrentUserUtils {
         Doc.CurrentUserEmail = email;
         await rp.get(Utils.prepend(RouteStore.getUserDocumentId)).then(id => {
             if (id && id !== "guest") {
-                return DocServer.GetRefField(id).then(async field => {
-                    let userDoc = field instanceof Doc ? await this.updateUserDocument(field) : this.createUserDocument(id);
-                    runInAction(() => Doc.SetUserDoc(userDoc));
-                });
+                return DocServer.GetRefField(id).then(async field =>
+                    Doc.SetUserDoc(await this.updateUserDocument(field instanceof Doc ? field : new Doc(id, true))));
             } else {
                 throw new Error("There should be a user id! Why does Dash think there isn't one?");
             }
-- 
cgit v1.2.3-70-g09d2


From 3cff8e7d101a528e392d885420de118cccca6ae5 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Tue, 22 Oct 2019 16:39:52 -0400
Subject: touch + inking can now pan with two fingers

---
 src/client/views/InkingCanvas.tsx                  | 35 +++++++++++++++++-----
 src/client/views/Touchable.tsx                     | 20 ++++++++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 15 ++++++++--
 3 files changed, 57 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 7651060af..2f3083cfe 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -10,6 +10,8 @@ import { UndoManager } from "../util/UndoManager";
 import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
+import { Touchable } from "./Touchable";
+import { InteractionUtils } from "../util/InteractionUtils";
 
 interface InkCanvasProps {
     getScreenTransform: () => Transform;
@@ -20,7 +22,7 @@ interface InkCanvasProps {
 }
 
 @observer
-export class InkingCanvas extends React.Component<InkCanvasProps> {
+export class InkingCanvas extends Touchable<InkCanvasProps> {
     maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome
     @observable inkMidX: number = 0;
     @observable inkMidY: number = 0;
@@ -93,6 +95,18 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
         }
     }
 
+    @action
+    handle1PointerMove = (e: TouchEvent) => {
+        e.stopPropagation();
+        e.preventDefault();
+        let pointer = e.targetTouches.item(0);
+        if (pointer) {
+            this.handleMove(pointer.clientX, pointer.clientY);
+        }
+    }
+
+    handle2PointersMove = () => { }
+
     @action
     onPointerUp = (e: PointerEvent): void => {
         document.removeEventListener("pointermove", this.onPointerMove, true);
@@ -116,21 +130,28 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
         batch.end();
     }
 
-    @action
-    onPointerMove = (e: PointerEvent): void => {
-        e.stopPropagation();
-        e.preventDefault();
+    handleMove = (x: number, y: number) => {
         if (InkingControl.Instance.selectedTool !== InkTool.Eraser) {
             let data = this.inkData;  // add points to new line as it is being drawn
             let strokeData = data.get(this._currentStrokeId);
             if (strokeData) {
-                strokeData.pathData.push(this.relativeCoordinatesForEvent(e.clientX, e.clientY));
+                strokeData.pathData.push(this.relativeCoordinatesForEvent(x, y));
                 data.set(this._currentStrokeId, strokeData);
             }
             this.inkData = data;
         }
     }
 
+    @action
+    onPointerMove = (e: PointerEvent): void => {
+        if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) {
+            return;
+        }
+        e.stopPropagation();
+        e.preventDefault();
+        this.handleMove(e.clientX, e.clientY);
+    }
+
     relativeCoordinatesForEvent = (ex: number, ey: number): { x: number, y: number } => {
         let [x, y] = this.props.getScreenTransform().transformPoint(ex, ey);
         return { x, y };
@@ -183,7 +204,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
         let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect";
         return (
             <div className="inkingCanvas">
-                <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} onTouchStart={(e) => e.stopPropagation()} />
+                <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart} />
                 {this.props.children()}
                 {this.drawnPaths}
             </div >
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index e9671ab8b..4955129ba 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -20,6 +20,15 @@ export abstract class Touchable<T> extends React.Component<T> {
             let pt = e.targetTouches.item(i);
             this.prevPoints.set(pt.identifier, pt);
         }
+
+        switch (e.targetTouches.length) {
+            case 1:
+                this.handle1PointerDown();
+                break;
+            case 2:
+                this.handle2PointersDown(e);
+        }
+
         document.removeEventListener("touchmove", this.onTouch);
         document.addEventListener("touchmove", this.onTouch);
         document.removeEventListener("touchend", this.onTouchEnd);
@@ -36,10 +45,10 @@ export abstract class Touchable<T> extends React.Component<T> {
         this._touchDrag = true;
         switch (e.targetTouches.length) {
             case 1:
-                this.handle1Pointer(e)
+                this.handle1PointerMove(e)
                 break;
             case 2:
-                this.handle2Pointers(e);
+                this.handle2PointersMove(e);
                 break;
         }
     }
@@ -70,13 +79,16 @@ export abstract class Touchable<T> extends React.Component<T> {
         document.removeEventListener("touchend", this.onTouchEnd);
     }
 
-    handle1Pointer = (e: TouchEvent): any => {
+    handle1PointerMove = (e: TouchEvent): any => {
         e.stopPropagation();
         e.preventDefault();
     }
 
-    handle2Pointers = (e: TouchEvent): any => {
+    handle2PointersMove = (e: TouchEvent): any => {
         e.stopPropagation();
         e.preventDefault();
     }
+
+    handle1PointerDown = (): any => { };
+    handle2PointersDown = (e: React.TouchEvent): any => { };
 }
\ 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 440a0a8e5..123941b03 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -356,7 +356,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
     }
 
-    handle1Pointer = (e: TouchEvent) => {
+    handle1PointerMove = (e: TouchEvent) => {
         // panning a workspace
         if (!e.cancelBubble && this.props.active()) {
             let pt = e.targetTouches.item(0);
@@ -368,7 +368,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
     }
 
-    handle2Pointers = (e: TouchEvent) => {
+    handle2PointersMove = (e: TouchEvent) => {
         // pinch zooming
         if (!e.cancelBubble) {
             let pt1: Touch | null = e.targetTouches.item(0);
@@ -413,6 +413,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         e.preventDefault();
     }
 
+    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;
+    }
+
     cleanUpInteractions = () => {
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
-- 
cgit v1.2.3-70-g09d2


From 77119dc1719af955e162248e5747bd9ef8921b4c Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 22 Oct 2019 20:43:24 -0400
Subject: added ink audio scrubbing

---
 src/client/documents/Documents.ts                   |  4 +---
 src/client/views/InkingCanvas.tsx                   | 11 +++++++----
 src/client/views/InkingControl.tsx                  |  1 +
 src/client/views/InkingStroke.tsx                   | 18 ++++++++++++------
 src/client/views/nodes/AudioBox.tsx                 | 21 +++++++++++++--------
 src/client/views/nodes/VideoBox.tsx                 |  2 --
 src/new_fields/InkField.ts                          |  4 +++-
 .../authentication/models/current_user_utils.ts     |  1 +
 8 files changed, 38 insertions(+), 24 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 316efe44c..5ae4ca82a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -334,9 +334,7 @@ export namespace Docs {
             let dataDoc = MakeDataDelegate(proto, protoProps, data);
             let viewDoc = Doc.MakeDelegate(dataDoc, delegId);
 
-            AudioBox.ActiveRecordings.map(d => {
-                DocUtils.MakeLink({ doc: viewDoc }, { doc: d }, "audio link", "link to audio: " + d.title);
-            });
+            AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: viewDoc }, { doc: d }, "audio link", "link to audio: " + d.title));
 
             return Doc.assign(viewDoc, delegateProps);
         }
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 920ebaedd..0037b95d0 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -78,7 +78,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
         this.previousState = new Map(this.inkData);
 
-        if (InkingControl.Instance.selectedTool !== InkTool.Eraser) {
+        if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
             // start the new line, saves a uuid to represent the field of the stroke
             this._currentStrokeId = Utils.GenerateGuid();
             const data = this.inkData;
@@ -87,7 +87,8 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
                 color: InkingControl.Instance.selectedColor,
                 width: InkingControl.Instance.selectedWidth,
                 tool: InkingControl.Instance.selectedTool,
-                displayTimecode: NumCast(this.props.Document.currentTimecode, -1)
+                displayTimecode: NumCast(this.props.Document.currentTimecode, -1),
+                creationTime: new Date().getTime()
             });
             this.inkData = data;
         }
@@ -120,7 +121,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     onPointerMove = (e: PointerEvent): void => {
         e.stopPropagation();
         e.preventDefault();
-        if (InkingControl.Instance.selectedTool !== InkTool.Eraser) {
+        if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
             let data = this.inkData;  // add points to new line as it is being drawn
             let strokeData = data.get(this._currentStrokeId);
             if (strokeData) {
@@ -161,6 +162,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
                     color={strokeData.color}
                     width={strokeData.width}
                     tool={strokeData.tool}
+                    creationTime={strokeData.creationTime}
                     deleteCallback={this.removeLine} />);
             }
             return paths;
@@ -181,7 +183,8 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     render() {
         let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect";
-        let cursor = svgCanvasStyle === "canSelect" ? (InkingControl.Instance.selectedTool === InkTool.Eraser ? "pointer" : "default") : undefined;
+        let cursor = svgCanvasStyle === "canSelect" ? (InkingControl.Instance.selectedTool === InkTool.Eraser ||
+            InkingControl.Instance.selectedTool === InkTool.Scrubber ? "pointer" : "default") : undefined;
         return (
             <div className="inkingCanvas">
                 <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} style={{ cursor: cursor }} />
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 105adc03d..75faa9641 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -126,6 +126,7 @@ export class InkingControl {
 Scripting.addGlobal(function activatePen(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Pen : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
 Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
 Scripting.addGlobal(function activateEraser(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Eraser : InkTool.None); });
+Scripting.addGlobal(function activateScrubber(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Scrubber : InkTool.None); });
 Scripting.addGlobal(function deactivateInk() { return InkingControl.Instance.switchTool(InkTool.None); });
 Scripting.addGlobal(function setInkWidth(width: any) { return InkingControl.Instance.switchWidth(width); });
 Scripting.addGlobal(function setInkColor(color: any) { return InkingControl.Instance.updateSelectedColor(color); });
\ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index b8d428d31..7bbf71482 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,9 +1,10 @@
 import { observer } from "mobx-react";
-import { observable, trace } from "mobx";
+import { observable, trace, runInAction } from "mobx";
 import { InkingControl } from "./InkingControl";
 import React = require("react");
 import { InkTool } from "../../new_fields/InkField";
 import "./InkingStroke.scss";
+import { AudioBox } from "./nodes/AudioBox";
 
 
 interface StrokeProps {
@@ -15,6 +16,7 @@ interface StrokeProps {
     color: string;
     width: string;
     tool: InkTool;
+    creationTime: number;
     deleteCallback: (index: string) => void;
 }
 
@@ -31,6 +33,11 @@ export class InkingStroke extends React.Component<StrokeProps> {
             e.stopPropagation();
             e.preventDefault();
         }
+        if (InkingControl.Instance.selectedTool === InkTool.Scrubber && e.buttons === 1) {
+            runInAction(() => AudioBox.ScrubTime = this.props.creationTime);
+            e.stopPropagation();
+            e.preventDefault();
+        }
     }
 
     parseData = (line: Array<{ x: number, y: number }>): string => {
@@ -55,10 +62,9 @@ export class InkingStroke extends React.Component<StrokeProps> {
         let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes
         let marker = this.props.tool === InkTool.Highlighter ? "-marker" : "";
 
-        let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ? "all" : "none";
-        return (
-            <path className={`inkingStroke${marker}`} d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }} strokeLinejoin="round" strokeLinecap="round"
-                onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />
-        );
+        let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ||
+            InkingControl.Instance.selectedTool === InkTool.Scrubber ? "all" : "none";
+        return (<path className={`inkingStroke${marker}`} d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }}
+            strokeLinejoin="round" strokeLinecap="round" onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 55b472726..62ec683da 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -38,11 +38,13 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
 
     _linkPlayDisposer: IReactionDisposer | undefined;
     _reactionDisposer: IReactionDisposer | undefined;
+    _scrubbingDisposer: IReactionDisposer | undefined;
     _ele: HTMLAudioElement | null = null;
     _recorder: any;
     _lastUpdate = 0;
 
     @observable private _audioState = 0;
+    @observable public static ScrubTime = 0;
     public static ActiveRecordings: Doc[] = [];
 
     componentDidMount() {
@@ -53,7 +55,9 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
                     const la1 = l.anchor1 as Doc;
                     const la2 = l.anchor2 as Doc;
                     if (l[Id] === scrollLinkId && la1 && la2) {
-                        setTimeout(() => this.playFrom(Doc.AreProtosEqual(la1, this.dataDoc) ? la2 : la1), 250);
+                        let doc = Doc.AreProtosEqual(la1, this.dataDoc) ? la2 : la1;
+                        let seek = DateCast(la1.creationTime);
+                        setTimeout(() => this.playFrom(seek.date.getTime()), 250);
                     }
                 });
                 scrollLinkId && (this.layoutDoc.scrollLinkID = undefined);
@@ -61,8 +65,9 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
         this._reactionDisposer = reaction(() => SelectionManager.SelectedDocuments(),
             selected => {
                 let sel = selected.length ? selected[0].props.Document : undefined;
-                this.Document.playOnSelect && sel && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFrom(sel);
+                this.Document.playOnSelect && sel && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFrom(DateCast(sel.creationTime).date.getTime());
             });
+        this._scrubbingDisposer = reaction(() => AudioBox.ScrubTime, time => this.Document.playOnSelect && this.playFrom(time));
     }
 
     updateHighlights = () => {
@@ -86,14 +91,13 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
         }
     }
 
-    playFrom = (sel: Doc) => {
+    playFrom = (seek: number) => {
         const extensionDoc = this.extensionDoc;
         let start = extensionDoc && DateCast(extensionDoc.recordingStart);
-        let seek = sel && DateCast(sel.creationDate);
-        if (this._ele && start && seek) {
-            if (sel) {
-                let delta = (seek.date.getTime() - start.date.getTime()) / 1000;
-                if (start && seek && delta > 0 && delta < this._ele.duration) {
+        if (this._ele && start) {
+            if (seek) {
+                let delta = (seek - start.date.getTime()) / 1000;
+                if (start && delta > 0 && delta < this._ele.duration) {
                     this._ele.currentTime = delta;
                     this._ele.play();
                     this._lastUpdate = delta;
@@ -110,6 +114,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
     componentWillUnmount() {
         this._reactionDisposer && this._reactionDisposer();
         this._linkPlayDisposer && this._linkPlayDisposer();
+        this._scrubbingDisposer && this._scrubbingDisposer();
     }
 
     recordAudioAnnotation = () => {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 48a699e58..53baea4ae 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -301,7 +301,6 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
         this._isResetClick = 0;
         document.addEventListener("pointermove", this.onResetMove, true);
         document.addEventListener("pointerup", this.onResetUp, true);
-        InkingControl.Instance.switchTool(InkTool.Eraser);
     }
 
     onResetMove = (e: PointerEvent) => {
@@ -314,7 +313,6 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
     onResetUp = (e: PointerEvent) => {
         document.removeEventListener("pointermove", this.onResetMove, true);
         document.removeEventListener("pointerup", this.onResetUp, true);
-        InkingControl.Instance.switchTool(InkTool.None);
         this._isResetClick < 10 && (this.Document.currentTimecode = 0);
     }
 
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index e381d0218..d94834e91 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -8,7 +8,8 @@ export enum InkTool {
     None,
     Pen,
     Highlighter,
-    Eraser
+    Eraser,
+    Scrubber
 }
 
 export interface StrokeData {
@@ -17,6 +18,7 @@ export interface StrokeData {
     width: string;
     tool: InkTool;
     displayTimecode: number;
+    creationTime: number;
 }
 
 export type InkData = Map<string, StrokeData>;
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 477b36ea4..95ebe3cb6 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -58,6 +58,7 @@ export class CurrentUserUtils {
             { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", unchecked: `!sameDocs(this.activePen.pen,  this)`, activePen: doc },
             { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc },
             { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc },
+            { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc },
             { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', unchecked: `!sameDocs(this.activePen.pen, this) && this.activePen.pen !== undefined`, backgroundColor: "white", activePen: doc },
         ];
         return docProtoData.map(data => Docs.Create.FontIconDocument({
-- 
cgit v1.2.3-70-g09d2


From 563a8926c0646e9907c8a4eec2e648ab5ae79e02 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Sat, 9 Nov 2019 16:27:07 -0500
Subject: hey, Sam's pushing these changes

---
 src/client/documents/DocumentTypes.ts              |  3 +-
 src/client/documents/Documents.ts                  |  4 ++
 src/client/views/InkingCanvas.tsx                  | 64 +++++++++++-----------
 src/client/views/InkingStroke.tsx                  | 13 +++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 45 ++++++++++-----
 src/client/views/nodes/DocumentContentsView.tsx    |  2 +-
 6 files changed, 79 insertions(+), 52 deletions(-)

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

diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 12501065a..f6dd0c346 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -24,5 +24,6 @@ export enum DocumentType {
     QUERY = "search",
     COLOR = "color",
     DOCULINK = "doculink",
-    PDFANNO = "pdfanno"
+    PDFANNO = "pdfanno",
+    INK = "ink"
 }
\ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d1fcabc4a..2c6b40cb9 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -411,6 +411,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.TEXT), "", options);
         }
 
+        export function InkDocument(options: DocumentOptions = {}) {
+            return InstanceFromProto(Prototypes.get(DocumentType.INK), "", options);
+        }
+
         export function IconDocument(icon: string, options: DocumentOptions = {}) {
             return InstanceFromProto(Prototypes.get(DocumentType.ICON), new IconField(icon), options);
         }
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 5c17696c8..a0ea37300 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -170,37 +170,37 @@ export class InkingCanvas extends Touchable<InkCanvasProps> {
         this.inkData = data;
     }
 
-    @computed
-    get drawnPaths() {
-        let curTimecode = NumCast(this.props.Document.currentTimecode, -1);
-        let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
-            if (strokeData.displayTimecode === -1 || (Math.abs(Math.round(strokeData.displayTimecode) - Math.round(curTimecode)) < 3)) {
-                paths.push(<InkingStroke key={id} id={id}
-                    line={strokeData.pathData}
-                    count={strokeData.pathData.length}
-                    offsetX={this.maxCanvasDim - this.inkMidX}
-                    offsetY={this.maxCanvasDim - this.inkMidY}
-                    color={strokeData.color}
-                    width={strokeData.width}
-                    tool={strokeData.tool}
-                    creationTime={strokeData.creationTime}
-                    deleteCallback={this.removeLine} />);
-            }
-            return paths;
-        }, [] as JSX.Element[]);
-        let markerPaths = paths.filter(path => path.props.tool === InkTool.Highlighter);
-        let penPaths = paths.filter(path => path.props.tool !== InkTool.Highlighter);
-        return [!penPaths.length ? (null) :
-            <svg className={`inkingCanvas-paths-ink`} key="Pens"
-                style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} >
-                {penPaths}
-            </svg>,
-        !markerPaths.length ? (null) :
-            <svg className={`inkingCanvas-paths-markers`} key="Markers"
-                style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}>
-                {markerPaths}
-            </svg>];
-    }
+    // @computed
+    // get drawnPaths() {
+    //     let curTimecode = NumCast(this.props.Document.currentTimecode, -1);
+    //     let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
+    //         if (strokeData.displayTimecode === -1 || (Math.abs(Math.round(strokeData.displayTimecode) - Math.round(curTimecode)) < 3)) {
+    //             paths.push(<InkingStroke key={id} id={id}
+    //                 line={strokeData.pathData}
+    //                 count={strokeData.pathData.length}
+    //                 offsetX={this.maxCanvasDim - this.inkMidX}
+    //                 offsetY={this.maxCanvasDim - this.inkMidY}
+    //                 color={strokeData.color}
+    //                 width={strokeData.width}
+    //                 tool={strokeData.tool}
+    //                 creationTime={strokeData.creationTime}
+    //                 deleteCallback={this.removeLine} />);
+    //         }
+    //         return paths;
+    //     }, [] as JSX.Element[]);
+    //     let markerPaths = paths.filter(path => path.props.tool === InkTool.Highlighter);
+    //     let penPaths = paths.filter(path => path.props.tool !== InkTool.Highlighter);
+    //     return [!penPaths.length ? (null) :
+    //         <svg className={`inkingCanvas-paths-ink`} key="Pens"
+    //             style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} >
+    //             {penPaths}
+    //         </svg>,
+    //     !markerPaths.length ? (null) :
+    //         <svg className={`inkingCanvas-paths-markers`} key="Markers"
+    //             style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}>
+    //             {markerPaths}
+    //         </svg>];
+    // }
 
     render() {
         let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect";
@@ -210,7 +210,7 @@ export class InkingCanvas extends Touchable<InkCanvasProps> {
             <div className="inkingCanvas">
                 <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart} style={{ cursor: cursor }} />
                 {this.props.children()}
-                {this.drawnPaths}
+                {/* {this.drawnPaths} */}
             </div >
         );
     }
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index a097a7991..824f40b1f 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -6,6 +6,10 @@ import { InkTool } from "../../new_fields/InkField";
 import "./InkingStroke.scss";
 import { AudioBox } from "./nodes/AudioBox";
 import { Doc } from "../../new_fields/Doc";
+import { createSchema, makeInterface } from "../../new_fields/Schema";
+import { documentSchema } from "../../new_fields/documentSchemas";
+import { DocExtendableComponent } from "./DocComponent";
+import { FieldViewProps, FieldView } from "./nodes/FieldView";
 
 
 interface StrokeProps {
@@ -21,13 +25,12 @@ interface StrokeProps {
     deleteCallback: (index: string) => void;
 }
 
-export type InkDocAndStroke = {
-    Document: Doc;
-    Ink: Map<any, any>;
-};
+type InkDocument = makeInterface<[typeof documentSchema]>;
+const InkDocument = makeInterface(documentSchema);
 
 @observer
-export class InkingStroke extends React.Component<StrokeProps> {
+export class InkingStroke extends DocExtendableComponent<FieldViewProps & StrokeProps, InkDocument>(InkDocument) {
+    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
 
     @observable private _strokeTool: InkTool = this.props.tool;
     @observable private _strokeColor: string = this.props.color;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9acffc952..8294eaaec 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -5,7 +5,7 @@ import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
-import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { InkField, StrokeData, InkTool } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
@@ -39,6 +39,7 @@ import { InteractionUtils } from "../../../util/InteractionUtils";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import PDFMenu from "../../pdf/PDFMenu";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
+import { InkingControl } from "../../InkingControl";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -263,6 +264,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return clusterColor;
     }
 
+    @observable private _points: { x: number, y: number }[] = [];
+
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.nativeEvent.cancelBubble) return;
@@ -272,8 +275,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointerup", this.onPointerUp);
-            this._lastX = e.pageX;
-            this._lastY = e.pageY;
+            if (InkingControl.Instance.selectedTool === InkTool.None) {
+                this._lastX = e.pageX;
+                this._lastY = e.pageY;
+            }
+            else {
+                e.stopPropagation();
+                e.preventDefault();
+
+                if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
+                    this._points.push({ x: e.pageX, y: e.pageY });
+                }
+            }
         }
     }
 
@@ -340,14 +353,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             return;
         }
         if (!e.cancelBubble) {
-            if (this._hitCluster && this.tryDragCluster(e)) {
-                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();
-                document.removeEventListener("pointermove", this.onPointerMove);
-                document.removeEventListener("pointerup", this.onPointerUp);
-                return;
+            if (InkingControl.Instance.selectedTool === InkTool.None) {
+                if (this._hitCluster && this.tryDragCluster(e)) {
+                    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();
+                    document.removeEventListener("pointermove", this.onPointerMove);
+                    document.removeEventListener("pointerup", this.onPointerUp);
+                    return;
+                }
+                this.pan(e);
+            }
+            if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
+                this._points.push({ x: e.clientX, y: e.clientY });
             }
-            this.pan(e);
             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();
         }
@@ -789,9 +807,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
                         easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
                         {!this.extensionDoc ? (null) :
-                            <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.extensionDoc} inkFieldKey={"ink"} >
-                                {this.childViews}
-                            </InkingCanvas>}
+                            // <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.extensionDoc} inkFieldKey={"ink"} >
+                            this.childViews
+                            // </InkingCanvas>
+                        }
                         <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
                     </CollectionFreeFormViewPannableContents>
                 </MarqueeView>
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 96271cfe1..12ae5b6e5 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -99,7 +99,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                     FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, FieldView,
                     CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
                     PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox,
-                    ColorBox, DocuLinkBox
+                    ColorBox, DocuLinkBox, InkingStroke
                 }}
                 bindings={this.CreateBindings()}
                 jsx={this.layout}
-- 
cgit v1.2.3-70-g09d2


From c7c18eeea36b35ee9172a120352af84fe21f267b Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Sun, 10 Nov 2019 16:27:56 -0500
Subject: inks are now dox

---
 src/client/documents/Documents.ts                  |  14 ++-
 src/client/util/SelectionManager.ts                |  32 ------
 src/client/views/DocumentDecorations.tsx           |  80 ++++++---------
 src/client/views/InkSelectDecorations.tsx          |  16 +--
 src/client/views/InkingCanvas.tsx                  |  12 +--
 src/client/views/InkingStroke.tsx                  | 107 +++++++++------------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  64 +++++++++---
 .../collections/collectionFreeForm/MarqueeView.tsx |  92 +++++++++---------
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/new_fields/InkField.ts                         |  20 ++--
 src/new_fields/documentSchemas.ts                  |   1 +
 11 files changed, 207 insertions(+), 233 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2c6b40cb9..a074d267e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -48,6 +48,8 @@ import { PresElementBox } from "../views/presentationview/PresElementBox";
 import { QueryBox } from "../views/nodes/QueryBox";
 import { ColorBox } from "../views/nodes/ColorBox";
 import { DocuLinkBox } from "../views/nodes/DocuLinkBox";
+import { InkingStroke } from "../views/InkingStroke";
+import { InkField } from "../../new_fields/InkField";
 var requestImageSize = require('../util/request-image-size');
 var path = require('path');
 
@@ -107,6 +109,7 @@ export interface DocumentOptions {
     sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
     targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
     dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
+    strokeWidth?: number;
     // [key: string]: Opt<Field>;
 }
 
@@ -209,6 +212,9 @@ export namespace Docs {
             [DocumentType.PRESELEMENT, {
                 layout: { view: PresElementBox, dataField: data }
             }],
+            [DocumentType.INK, {
+                layout: { view: InkingStroke, dataField: data }
+            }]
         ]);
 
         // All document prototypes are initialized with at least these values
@@ -411,8 +417,12 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.TEXT), "", options);
         }
 
-        export function InkDocument(options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.INK), "", options);
+        export function InkDocument(color: string, tool: number, strokeWidth: number, points: { x: number, y: number }[], options: DocumentOptions = {}) {
+            let doc = InstanceFromProto(Prototypes.get(DocumentType.INK), new InkField(points), options);
+            doc.color = color;
+            doc.strokeWidth = strokeWidth;
+            doc.tool = tool;
+            return doc;
         }
 
         export function IconDocument(icon: string, options: DocumentOptions = {}) {
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 2a57c67bd..ca61f9014 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -3,8 +3,6 @@ import { Doc, Opt } from "../../new_fields/Doc";
 import { DocumentView } from "../views/nodes/DocumentView";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { NumCast, StrCast } from "../../new_fields/Types";
-import { InkingControl } from "../views/InkingControl";
-import { InkDocAndStroke } from "../views/InkingStroke";
 
 export namespace SelectionManager {
 
@@ -12,7 +10,6 @@ export namespace SelectionManager {
 
         @observable IsDragging: boolean = false;
         @observable SelectedDocuments: Array<DocumentView> = [];
-        @observable SelectedInk: Array<{ Document: Doc, Ink: Map<any, any> }> = [];
 
 
         @action
@@ -43,20 +40,6 @@ export namespace SelectionManager {
         DeselectAll(): void {
             manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false));
             manager.SelectedDocuments = [];
-            manager.SelectedInk = [];
-        }
-
-        @action
-        SelectInk(ink: { Document: Doc, Ink: Map<any, any> }, ctrlPressed: boolean): void {
-            if (manager.SelectedInk.indexOf(ink) === -1) {
-                if (!ctrlPressed) {
-                    this.DeselectAll();
-                }
-
-                manager.SelectedInk.push(ink);
-            } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) {
-                manager.SelectedInk = [ink];
-            }
         }
     }
 
@@ -69,10 +52,6 @@ export namespace SelectionManager {
         manager.SelectDoc(docView, ctrlPressed);
     }
 
-    export function SelectInk(ink: { Document: Doc, Ink: Map<any, any> }, ctrlPressed: boolean): void {
-        manager.SelectInk(ink, ctrlPressed);
-    }
-
     export function IsSelected(doc: DocumentView): boolean {
         return manager.SelectedDocuments.indexOf(doc) !== -1;
     }
@@ -95,15 +74,4 @@ export namespace SelectionManager {
     export function SelectedDocuments(): Array<DocumentView> {
         return manager.SelectedDocuments.slice();
     }
-
-    export function SelectedInk(): Array<{ Document: Doc, Ink: Map<any, any> }> {
-        return manager.SelectedInk.slice();
-    }
-
-    export function AllSelected(): Array<DocumentView | InkDocAndStroke> {
-        let arr: Array<DocumentView | InkDocAndStroke> = [];
-        arr = SelectionManager.SelectedDocuments();
-        arr.push(...SelectionManager.SelectedInk());
-        return arr;
-    }
 }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e208e5f3b..10764a9ce 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -24,7 +24,7 @@ import { DocumentView } from "./nodes/DocumentView";
 import { FieldView } from "./nodes/FieldView";
 import { IconBox } from "./nodes/IconBox";
 import React = require("react");
-import { StrokeData } from '../../new_fields/InkField';
+import { PointData } from '../../new_fields/InkField';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -162,44 +162,23 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @computed
     get Bounds(): { x: number, y: number, b: number, r: number } {
         let x = this._forceUpdate;
-        this._lastBox = SelectionManager.AllSelected().reduce((bounds, docViewOrInk) => {
-            if (docViewOrInk instanceof DocumentView) {
-                if (docViewOrInk.props.renderDepth === 0 ||
-                    Doc.AreProtosEqual(docViewOrInk.props.Document, CurrentUserUtils.UserDocument)) {
-                    return bounds;
-                }
-                let transform = (docViewOrInk.props.ScreenToLocalTransform().scale(docViewOrInk.props.ContentScaling())).inverse();
-                if (transform.TranslateX === 0 && transform.TranslateY === 0) {
-                    setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds
-                    return this._lastBox;
-                }
-
-                var [sptX, sptY] = transform.transformPoint(0, 0);
-                let [bptX, bptY] = transform.transformPoint(docViewOrInk.props.PanelWidth(), docViewOrInk.props.PanelHeight());
-                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)
-                };
+        this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, docView) => {
+            if (docView.props.renderDepth === 0 ||
+                Doc.AreProtosEqual(docView.props.Document, CurrentUserUtils.UserDocument)) {
+                return bounds;
             }
-            else {
-                let left = bounds.x;
-                let top = bounds.y;
-                let right = bounds.r;
-                let bottom = bounds.b;
-                let ink;
-                docViewOrInk.Ink.forEach((value: StrokeData, key: string) => {
-                    value.pathData.map(val => {
-                        ink = docViewOrInk.Document.ink;
-                        left = Math.min(val.x, left);
-                        top = Math.min(val.y, top);
-                        right = Math.max(val.x, right);
-                        bottom = Math.max(val.y, bottom);
-                    });
-                });
-                return {
-                    x: left, y: top, r: right, b: bottom
-                };
+            let transform = (docView.props.ScreenToLocalTransform().scale(docView.props.ContentScaling())).inverse();
+            if (transform.TranslateX === 0 && transform.TranslateY === 0) {
+                setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds
+                return this._lastBox;
             }
+
+            var [sptX, sptY] = transform.transformPoint(0, 0);
+            let [bptX, bptY] = transform.transformPoint(docView.props.PanelWidth(), docView.props.PanelHeight());
+            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 });
         return this._lastBox;
     }
@@ -226,7 +205,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         document.removeEventListener("pointerup", this.onBackgroundUp);
         document.removeEventListener("pointermove", this.onTitleMove);
         document.removeEventListener("pointerup", this.onTitleUp);
-        DragManager.StartDocumentDrag(SelectionManager.AllSelected().map(docOrInk => docOrInk instanceof DocumentView ? docOrInk.ContentDiv! : (document.createElement("div"))), dragData, e.x, e.y, {
+        DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, {
             handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) },
             hideSource: true
         });
@@ -550,21 +529,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
     @computed
     get selectionTitle(): string {
-        if (SelectionManager.AllSelected().length === 1) {
-            let selected = SelectionManager.AllSelected()[0];
-            if (selected instanceof DocumentView) {
-                let field = selected.props.Document[this._fieldKey];
-                if (typeof field === "string") {
-                    return field;
-                }
-                else if (typeof field === "number") {
-                    return field.toString();
-                }
+        if (SelectionManager.SelectedDocuments().length === 1) {
+            let selected = SelectionManager.SelectedDocuments()[0];
+            let field = selected.props.Document[this._fieldKey];
+            if (typeof field === "string") {
+                return field;
             }
-            else {
-                return "-ink strokes-";
+            else if (typeof field === "number") {
+                return field.toString();
             }
-        } else if (SelectionManager.AllSelected().length > 1) {
+        } else if (SelectionManager.SelectedDocuments().length > 1) {
             return "-multiple-";
         }
         return "-unset-";
@@ -590,7 +564,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let minimizeIcon = (
             <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>
                 {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
-                {(SelectionManager.SelectedDocuments().length === 1 && SelectionManager.SelectedInk().length === 0) ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
+                {(SelectionManager.SelectedDocuments().length === 1) ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
             </div>);
 
         bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
@@ -611,7 +585,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 left: bounds.x - this._resizeBorderWidth / 2,
                 top: bounds.y - this._resizeBorderWidth / 2,
                 pointerEvents: this.Interacting ? "none" : "all",
-                zIndex: SelectionManager.AllSelected().length > 1 ? 900 : 0,
+                zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0,
             }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
             </div>
             <div className="documentDecorations-container" ref={this.setTextBar} style={{
diff --git a/src/client/views/InkSelectDecorations.tsx b/src/client/views/InkSelectDecorations.tsx
index a8eef3305..95ccc1777 100644
--- a/src/client/views/InkSelectDecorations.tsx
+++ b/src/client/views/InkSelectDecorations.tsx
@@ -1,6 +1,6 @@
 import React = require("react");
 import { Touchable } from "./Touchable";
-import { StrokeData } from "../../new_fields/InkField";
+import { PointData } from "../../new_fields/InkField";
 import { observer } from "mobx-react";
 import { computed, observable, action, runInAction } from "mobx";
 import "./InkSelectDecorations.scss"
@@ -33,13 +33,13 @@ export default class InkSelectDecorations extends Touchable {
         let top = Number.MAX_VALUE;
         let right = -Number.MAX_VALUE;
         let bottom = -Number.MAX_VALUE;
-        this._selectedInkNodes.forEach((value: StrokeData, key: string) => {
-            value.pathData.map(val => {
-                left = Math.min(val.x, left);
-                top = Math.min(val.y, top);
-                right = Math.max(val.x, right);
-                bottom = Math.max(val.y, bottom);
-            });
+        this._selectedInkNodes.forEach((value: PointData, key: string) => {
+            // value.pathData.map(val => {
+            //     left = Math.min(val.x, left);
+            //     top = Math.min(val.y, top);
+            //     right = Math.max(val.x, right);
+            //     bottom = Math.max(val.y, bottom);
+            // });
         });
         return { x: left, y: top, b: bottom, r: right };
     }
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index a0ea37300..e5253c377 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -7,7 +7,7 @@ import { InkingControl } from "./InkingControl";
 import { InkingStroke } from "./InkingStroke";
 import React = require("react");
 import { UndoManager } from "../util/UndoManager";
-import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
+import { PointData, InkField, InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
 import { Touchable } from "./Touchable";
@@ -26,15 +26,15 @@ export class InkingCanvas extends Touchable<InkCanvasProps> {
     maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome
     @observable inkMidX: number = 0;
     @observable inkMidY: number = 0;
-    private previousState?: Map<string, StrokeData>;
+    private previousState?: Map<string, PointData>;
     private _currentStrokeId: string = "";
-    public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean {
+    public static IntersectStrokeRect(stroke: PointData, selRect: { left: number, top: number, width: number, height: number }): boolean {
         return stroke.pathData.reduce((inside: boolean, val) => inside ||
             (selRect.left < val.x && selRect.left + selRect.width > val.x &&
                 selRect.top < val.y && selRect.top + selRect.height > val.y)
             , false);
     }
-    public static StrokeRect(stroke: StrokeData): { left: number, top: number, right: number, bottom: number } {
+    public static StrokeRect(stroke: PointData): { left: number, top: number, right: number, bottom: number } {
         return stroke.pathData.reduce((bounds: { left: number, top: number, right: number, bottom: number }, val) =>
             ({
                 left: Math.min(bounds.left, val.x), top: Math.min(bounds.top, val.y),
@@ -58,12 +58,12 @@ export class InkingCanvas extends Touchable<InkCanvasProps> {
     }
 
     @computed
-    get inkData(): Map<string, StrokeData> {
+    get inkData(): Map<string, PointData> {
         let map = Cast(this.props.AnnotationDocument[this.props.inkFieldKey], InkField);
         return !map ? new Map : new Map(map.inkData);
     }
 
-    set inkData(value: Map<string, StrokeData>) {
+    set inkData(value: Map<string, PointData>) {
         this.props.AnnotationDocument[this.props.inkFieldKey] = new InkField(value);
     }
 
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 824f40b1f..411b0d3a0 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,79 +1,66 @@
 import { observer } from "mobx-react";
-import { observable, trace, runInAction } from "mobx";
+import { observable, trace, runInAction, computed } from "mobx";
 import { InkingControl } from "./InkingControl";
 import React = require("react");
-import { InkTool } from "../../new_fields/InkField";
+import { InkTool, InkField, InkData } from "../../new_fields/InkField";
 import "./InkingStroke.scss";
 import { AudioBox } from "./nodes/AudioBox";
-import { Doc } from "../../new_fields/Doc";
-import { createSchema, makeInterface } from "../../new_fields/Schema";
+import { Doc, FieldResult } from "../../new_fields/Doc";
+import { createSchema, makeInterface, listSpec } from "../../new_fields/Schema";
 import { documentSchema } from "../../new_fields/documentSchemas";
 import { DocExtendableComponent } from "./DocComponent";
 import { FieldViewProps, FieldView } from "./nodes/FieldView";
-
-
-interface StrokeProps {
-    offsetX: number;
-    offsetY: number;
-    id: string;
-    count: number;
-    line: Array<{ x: number, y: number }>;
-    color: string;
-    width: string;
-    tool: InkTool;
-    creationTime: number;
-    deleteCallback: (index: string) => void;
-}
+import { Transform } from "../util/Transform";
+import { Cast, FieldValue } from "../../new_fields/Types";
+import { List } from "../../new_fields/List";
 
 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) {
+    let 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 & StrokeProps, InkDocument>(InkDocument) {
+export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocument>(InkDocument) {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
 
-    @observable private _strokeTool: InkTool = this.props.tool;
-    @observable private _strokeColor: string = this.props.color;
-    @observable private _strokeWidth: string = this.props.width;
-
-    deleteStroke = (e: React.PointerEvent): void => {
-        if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) {
-            this.props.deleteCallback(this.props.id);
-            e.stopPropagation();
-            e.preventDefault();
-        }
-        if (InkingControl.Instance.selectedTool === InkTool.Scrubber && e.buttons === 1) {
-            AudioBox.SetScrubTime(this.props.creationTime);
-            e.stopPropagation();
-            e.preventDefault();
-        }
-    }
-
-    parseData = (line: Array<{ x: number, y: number }>): string => {
-        return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L ");
-    }
-
-    createStyle() {
-        switch (this._strokeTool) {
-            // add more tool styles here
-            default:
-                return {
-                    fill: "none",
-                    stroke: this._strokeColor,
-                    strokeWidth: this._strokeWidth + "px",
-                };
-        }
-    }
+    @computed get PanelWidth() { return this.props.PanelWidth(); }
+    @computed get PanelHeight() { return this.props.PanelHeight(); }
 
     render() {
-        let pathStyle = this.createStyle();
-        let pathData = this.parseData(this.props.line);
-        let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes
-        let marker = this.props.tool === InkTool.Highlighter ? "-marker" : "";
-
-        let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ||
-            InkingControl.Instance.selectedTool === InkTool.Scrubber ? "all" : "none";
-        return (<path className={`inkingStroke${marker}`} d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }}
-            strokeLinejoin="round" strokeLinecap="round" onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />);
+        // let pathData = this.parseData(this.props.line);
+        let data: InkData = Cast(this.Document.data, InkField) ?.inkData ?? [];
+        let xs = data.map(p => p.x);
+        let ys = data.map(p => p.y);
+        let left = Math.min(...xs);
+        let top = Math.min(...ys);
+        let right = Math.max(...xs);
+        let bottom = Math.max(...ys);
+        let points = CreatePolyline(data, 0, 0, this.Document.color, this.Document.strokeWidth);
+        let width = right - left;
+        let height = bottom - top;
+        let scaleX = this.PanelWidth / width;
+        let scaleY = this.PanelHeight / height;
+        // let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes
+        return (
+            <svg width={width} height={height} style={{
+                transformOrigin: "top left",
+                transform: `translate(${left}px, ${top}px) scale(${scaleX}, ${scaleY})`,
+                mixBlendMode: this.Document.tool === InkTool.Highlighter ? "multiply" : "unset"
+            }}>
+                {points}
+            </svg>
+        );
     }
 }
\ 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 8294eaaec..21981c25e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -5,7 +5,7 @@ import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
-import { InkField, StrokeData, InkTool } from "../../../../new_fields/InkField";
+import { InkField, PointData, InkTool } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
@@ -40,6 +40,7 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import PDFMenu from "../../pdf/PDFMenu";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { InkingControl } from "../../InkingControl";
+import { InkingStroke, CreatePolyline } from "../../InkingStroke";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -108,10 +109,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         added && this.updateCluster(newBox);
         return added;
     }
-    private selectDocuments = (docs: Doc[], ink: { Document: Doc, Ink: Map<any, any> }[]) => {
+    private selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
-        ink.forEach(i => SelectionManager.SelectInk(i, true));
     }
     public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
 
@@ -284,14 +284,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 e.preventDefault();
 
                 if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
-                    this._points.push({ x: e.pageX, y: e.pageY });
+                    let point = this.getTransform().transformPoint(e.pageX, e.pageY);
+                    this._points.push({ x: point[0], y: point[1] });
                 }
             }
         }
     }
 
+    @action
     onPointerUp = (e: PointerEvent): void => {
         if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) return;
+
+        if (this._points.length > 1) {
+            let B = this.svgBounds;
+            let points = this._points.map(p => ({ x: p.x - B.left, y: p.y - B.top }));
+            let inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { width: B.width, height: B.height, x: B.left, y: B.top });
+            this.addDocument(inkDoc);
+            this._points = [];
+        }
+
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         document.removeEventListener("touchmove", this.onTouch);
@@ -324,11 +335,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }, [[minx, maxx], [miny, maxy]]);
             let ink = this.extensionDoc && Cast(this.extensionDoc.ink, InkField);
             if (ink && ink.inkData) {
-                ink.inkData.forEach((value: StrokeData, key: string) => {
-                    let bounds = InkingCanvas.StrokeRect(value);
-                    ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)];
-                    ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)];
-                });
+                // ink.inkData.forEach((value: PointData, key: string) => {
+                //     let bounds = InkingCanvas.StrokeRect(value);
+                //     ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)];
+                //     ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)];
+                // });
             }
 
             let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
@@ -363,8 +374,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }
                 this.pan(e);
             }
-            if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
-                this._points.push({ x: e.clientX, y: e.clientY });
+            else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
+                let point = this.getTransform().transformPoint(e.clientX, e.clientY);
+                this._points.push({ x: point[0], y: point[1] });
             }
             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();
@@ -470,7 +482,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
         else if (this.props.active()) {
             e.stopPropagation();
-            this.zoom(e.clientX, e.clientY, e.deltaY)
+            this.zoom(e.clientX, e.clientY, e.deltaY);
         }
     }
 
@@ -790,6 +802,31 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             ...this.views,
         ];
     }
+
+    @computed get svgBounds() {
+        let xs = this._points.map(p => p.x);
+        let ys = this._points.map(p => p.y);
+        let right = Math.max(...xs);
+        let left = Math.min(...xs);
+        let bottom = Math.max(...ys);
+        let top = Math.min(...ys);
+        return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top };
+    }
+
+    @computed get currentStroke() {
+        if (this._points.length <= 1) {
+            return (null);
+        }
+
+        let B = this.svgBounds;
+
+        return (
+            <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)` }}>
+                {CreatePolyline(this._points, B.left, B.top)}
+            </svg>
+        );
+    }
+
     render() {
         // 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;
@@ -808,9 +845,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
                         {!this.extensionDoc ? (null) :
                             // <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.extensionDoc} inkFieldKey={"ink"} >
-                            this.childViews
+                            this.childViews()
                             // </InkingCanvas>
                         }
+                        {this.currentStroke}
                         <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
                     </CollectionFreeFormViewPannableContents>
                 </MarqueeView>
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 138168fed..b5f6f095e 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../../../new_fields/Doc";
-import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { InkField, PointData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { listSpec } from "../../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
@@ -198,10 +198,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             if (!e.shiftKey) {
                 SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document);
             }
-            let inkselect = this.ink ? this.marqueeInkSelect(this.ink.inkData) : new Map();
-            let inks = inkselect.size ? [{ Document: this.inkDoc, Ink: inkselect }] : [];
-            let docs = mselect.length ? mselect : (inks.length ? [] : [this.props.Document]);
-            this.props.selectDocuments(docs, inks);
+            // let inkselect = this.ink ? this.marqueeInkSelect(this.ink.inkData) : new Map();
+            // let inks = inkselect.size ? [{ Document: this.inkDoc, Ink: inkselect }] : [];
+            let docs = mselect.length ? mselect : [this.props.Document];
+            this.props.selectDocuments(docs, []);
         }
         if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) {
             MarqueeOptionsMenu.Instance.createCollection = this.collection;
@@ -217,7 +217,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             this.hideMarquee();
             MarqueeOptionsMenu.Instance.fadeOut(true);
             document.removeEventListener("pointerdown", hideMarquee);
-        }
+        };
         document.addEventListener("pointerdown", hideMarquee);
 
         if (e.altKey) {
@@ -294,7 +294,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     delete = () => {
         this.marqueeSelect(false).map(d => this.props.removeDocument(d));
         if (this.ink) {
-            this.marqueeInkDelete(this.ink.inkData);
+            // this.marqueeInkDelete(this.ink.inkData);
         }
         SelectionManager.DeselectAll();
         this.cleanupInteractions(false);
@@ -336,8 +336,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             title: "a nested collection",
         });
         let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
-        dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
-        this.marqueeInkDelete(inkData);
+        // dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
+        // this.marqueeInkDelete(inkData);
         this.hideMarquee();
         return newCollection;
     }
@@ -422,45 +422,45 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             this.cleanupInteractions(false);
         }
     }
-    @action
-    marqueeInkSelect(ink: Map<any, any>) {
-        let idata = new Map();
-        let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
-        let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2);
-        ink.forEach((value: StrokeData, key: string, map: any) => {
-            if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) {
-                // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling());
-                idata.set(key,
-                    {
-                        pathData: value.pathData.map(val => {
-                            let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y);
-                            return { x: tVal[0], y: tVal[1] };
-                            // return { x: val.x + centerShiftX, y: val.y + centerShiftY }
-                        }),
-                        color: value.color,
-                        width: value.width,
-                        tool: value.tool,
-                        page: -1
-                    });
-            }
-        });
-        // InkSelectDecorations.Instance.SetSelected(idata);
-        return idata;
-    }
+    // @action
+    // marqueeInkSelect(ink: Map<any, any>) {
+    //     let idata = new Map();
+    //     let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
+    //     let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2);
+    //     ink.forEach((value: PointData, key: string, map: any) => {
+    //         if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) {
+    //             // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling());
+    //             idata.set(key,
+    //                 {
+    //                     pathData: value.pathData.map(val => {
+    //                         let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y);
+    //                         return { x: tVal[0], y: tVal[1] };
+    //                         // return { x: val.x + centerShiftX, y: val.y + centerShiftY }
+    //                     }),
+    //                     color: value.color,
+    //                     width: value.width,
+    //                     tool: value.tool,
+    //                     page: -1
+    //                 });
+    //         }
+    //     });
+    //     // InkSelectDecorations.Instance.SetSelected(idata);
+    //     return idata;
+    // }
 
-    @action
-    marqueeInkDelete(ink?: Map<any, any>) {
-        // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB.
-        // ink.forEach((value: StrokeData, key: string, map: any) =>
-        //     InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key));
+    // @action
+    // marqueeInkDelete(ink?: Map<any, any>) {
+    //     // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB.
+    //     // ink.forEach((value: StrokeData, key: string, map: any) =>
+    //     //     InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key));
 
-        if (ink) {
-            let idata = new Map();
-            ink.forEach((value: StrokeData, key: string, map: any) =>
-                !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value));
-            this.ink = new InkField(idata);
-        }
-    }
+    //     if (ink) {
+    //         let idata = new Map();
+    //         ink.forEach((value: PointData, key: string, map: any) =>
+    //             !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value));
+    //         this.ink = new InkField(idata);
+    //     }
+    // }
 
     marqueeSelect(selectBackgrounds: boolean = true) {
         let selRect = this.Bounds;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 62529a5fb..5c89472ce 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -632,7 +632,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     {searchHighlight}
                 </div>
             }
-        </>
+        </>;
     }
     render() {
         if (!this.props.Document) return (null);
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index d94834e91..2d8bb582a 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -12,16 +12,12 @@ export enum InkTool {
     Scrubber
 }
 
-export interface StrokeData {
-    pathData: Array<{ x: number, y: number }>;
-    color: string;
-    width: string;
-    tool: InkTool;
-    displayTimecode: number;
-    creationTime: number;
+export interface PointData {
+    x: number;
+    y: number;
 }
 
-export type InkData = Map<string, StrokeData>;
+export type InkData = Array<PointData>;
 
 const pointSchema = createSimpleSchema({
     x: true, y: true
@@ -34,16 +30,16 @@ const strokeDataSchema = createSimpleSchema({
 
 @Deserializable("ink")
 export class InkField extends ObjectField {
-    @serializable(map(object(strokeDataSchema)))
+    @serializable(list(object(strokeDataSchema)))
     readonly inkData: InkData;
 
-    constructor(data?: InkData) {
+    constructor(data: InkData) {
         super();
-        this.inkData = data || new Map;
+        this.inkData = data;
     }
 
     [Copy]() {
-        return new InkField(DeepCopy(this.inkData));
+        return new InkField(this.inkData);
     }
 
     [ToScriptString]() {
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index e2730914f..0b28561bf 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -43,6 +43,7 @@ export const documentSchema = createSchema({
     isAnimating: "boolean",     // whether the document is in the midst of animating between two layouts (used by icons to de/iconify documents).
     animateToDimensions: listSpec("number"), // layout information about the target rectangle a document is animating towards 
     scrollToLinkID: "string",   // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc.
+    strokeWidth: "number"
 });
 
 export const positionSchema = createSchema({
-- 
cgit v1.2.3-70-g09d2


From e86c44757770c097392fb17726041a4410c16e74 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Sat, 16 Nov 2019 16:22:42 -0500
Subject: fixed some bugs with touch, text document are a little buggy?

---
 src/client/views/InkingCanvas.scss                 |  51 -----
 src/client/views/InkingCanvas.tsx                  | 217 ---------------------
 .../views/collections/CollectionStaffView.tsx      |   4 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   9 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   1 -
 src/client/views/nodes/DocumentView.tsx            |   7 +-
 6 files changed, 9 insertions(+), 280 deletions(-)
 delete mode 100644 src/client/views/InkingCanvas.scss
 delete mode 100644 src/client/views/InkingCanvas.tsx

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

diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
deleted file mode 100644
index 8f32652ed..000000000
--- a/src/client/views/InkingCanvas.scss
+++ /dev/null
@@ -1,51 +0,0 @@
-@import "globalCssVariables";
-
-.inkingCanvas {
-   // opacity: 0.99;
-    touch-action: none;
-
-    .jsx-parser {
-        position: absolute;
-        width: 100%;
-        height: 100%;
-        background: inherit;
-        //z-index: -1; // allows annotations to appear on videos when screen is full-size & ... 
-    }
-}
-
-.inkingCanvas-paths-ink,
-.inkingCanvas-paths-markers,
-.inkingCanvas-noSelect,
-.inkingCanvas-canSelect {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 8192px;
-    height: 8192px;
-    cursor: "crosshair";
-    pointer-events: all;
-}
-
-.inkingCanvas-canSelect,
-.inkingCanvas-noSelect {
-    top: -50000px;
-    left: -50000px;
-    width: 100000px;
-    height: 100000px;
-}
-
-.inkingCanvas-noSelect {
-    pointer-events: none;
-    cursor: "crosshair";
-}
-
-.inkingCanvas-paths-ink,
-.inkingCanvas-paths-markers {
-    pointer-events: none;
-    z-index: 10000; // overlays ink on top of everything 
-    cursor: "arrow";
-}
-
-.inkingCanvas-paths-markers {
-    mix-blend-mode: multiply;
-}
\ No newline at end of file
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
deleted file mode 100644
index e5253c377..000000000
--- a/src/client/views/InkingCanvas.tsx
+++ /dev/null
@@ -1,217 +0,0 @@
-import { action, computed, trace, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { Utils } from "../../Utils";
-import { Transform } from "../util/Transform";
-import "./InkingCanvas.scss";
-import { InkingControl } from "./InkingControl";
-import { InkingStroke } from "./InkingStroke";
-import React = require("react");
-import { UndoManager } from "../util/UndoManager";
-import { PointData, InkField, InkTool } from "../../new_fields/InkField";
-import { Doc } from "../../new_fields/Doc";
-import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
-import { Touchable } from "./Touchable";
-import { InteractionUtils } from "../util/InteractionUtils";
-
-interface InkCanvasProps {
-    getScreenTransform: () => Transform;
-    AnnotationDocument: Doc;
-    Document: Doc;
-    inkFieldKey: string;
-    children: () => JSX.Element[];
-}
-
-@observer
-export class InkingCanvas extends Touchable<InkCanvasProps> {
-    maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome
-    @observable inkMidX: number = 0;
-    @observable inkMidY: number = 0;
-    private previousState?: Map<string, PointData>;
-    private _currentStrokeId: string = "";
-    public static IntersectStrokeRect(stroke: PointData, selRect: { left: number, top: number, width: number, height: number }): boolean {
-        return stroke.pathData.reduce((inside: boolean, val) => inside ||
-            (selRect.left < val.x && selRect.left + selRect.width > val.x &&
-                selRect.top < val.y && selRect.top + selRect.height > val.y)
-            , false);
-    }
-    public static StrokeRect(stroke: PointData): { left: number, top: number, right: number, bottom: number } {
-        return stroke.pathData.reduce((bounds: { left: number, top: number, right: number, bottom: number }, val) =>
-            ({
-                left: Math.min(bounds.left, val.x), top: Math.min(bounds.top, val.y),
-                right: Math.max(bounds.right, val.x), bottom: Math.max(bounds.bottom, val.y)
-            })
-            , { left: Number.MAX_VALUE, top: Number.MAX_VALUE, right: -Number.MAX_VALUE, bottom: -Number.MAX_VALUE });
-    }
-
-    componentDidMount() {
-        PromiseValue(Cast(this.props.AnnotationDocument[this.props.inkFieldKey], InkField)).then(ink => runInAction(() => {
-            if (ink) {
-                let bounds = Array.from(ink.inkData).reduce(([mix, max, miy, may], [id, strokeData]) =>
-                    strokeData.pathData.reduce(([mix, max, miy, may], p) =>
-                        [Math.min(mix, p.x), Math.max(max, p.x), Math.min(miy, p.y), Math.max(may, p.y)],
-                        [mix, max, miy, may]),
-                    [Number.MAX_VALUE, Number.MIN_VALUE, Number.MAX_VALUE, Number.MIN_VALUE]);
-                this.inkMidX = (bounds[0] + bounds[1]) / 2;
-                this.inkMidY = (bounds[2] + bounds[3]) / 2;
-            }
-        }));
-    }
-
-    @computed
-    get inkData(): Map<string, PointData> {
-        let map = Cast(this.props.AnnotationDocument[this.props.inkFieldKey], InkField);
-        return !map ? new Map : new Map(map.inkData);
-    }
-
-    set inkData(value: Map<string, PointData>) {
-        this.props.AnnotationDocument[this.props.inkFieldKey] = new InkField(value);
-    }
-
-    @action
-    onPointerDown = (e: React.PointerEvent): void => {
-        if (e.button !== 0 || e.altKey || e.ctrlKey || InkingControl.Instance.selectedTool === InkTool.None) {
-            return;
-        }
-
-        document.addEventListener("pointermove", this.onPointerMove, true);
-        document.addEventListener("pointerup", this.onPointerUp, true);
-        e.stopPropagation();
-        e.preventDefault();
-
-        this.previousState = new Map(this.inkData);
-
-        if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
-            // start the new line, saves a uuid to represent the field of the stroke
-            this._currentStrokeId = Utils.GenerateGuid();
-            const data = this.inkData;
-            data.set(this._currentStrokeId, {
-                pathData: [this.relativeCoordinatesForEvent(e.clientX, e.clientY)],
-                color: InkingControl.Instance.selectedColor,
-                width: InkingControl.Instance.selectedWidth,
-                tool: InkingControl.Instance.selectedTool,
-                displayTimecode: NumCast(this.props.Document.currentTimecode, -1),
-                creationTime: new Date().getTime()
-            });
-            this.inkData = data;
-        }
-    }
-
-    @action
-    handle1PointerMove = (e: TouchEvent) => {
-        e.stopPropagation();
-        e.preventDefault();
-        let pointer = e.targetTouches.item(0);
-        if (pointer) {
-            this.handleMove(pointer.clientX, pointer.clientY);
-        }
-    }
-
-    handle2PointersMove = () => { }
-
-    @action
-    onPointerUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onPointerMove, true);
-        document.removeEventListener("pointerup", this.onPointerUp, true);
-        let coord = this.relativeCoordinatesForEvent(e.clientX, e.clientY);
-        if (Math.abs(coord.x - this.inkMidX) > 500 || Math.abs(coord.y - this.inkMidY) > 500) {
-            this.inkMidX = coord.x;
-            this.inkMidY = coord.y;
-        }
-        e.stopPropagation();
-        e.preventDefault();
-
-        const batch = UndoManager.StartBatch("One ink stroke");
-        const oldState = this.previousState || new Map;
-        this.previousState = undefined;
-        const newState = new Map(this.inkData);
-        UndoManager.AddEvent({
-            undo: () => this.inkData = oldState,
-            redo: () => this.inkData = newState
-        });
-        batch.end();
-    }
-
-    handleMove = (x: number, y: number) => {
-        if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
-            let data = this.inkData;  // add points to new line as it is being drawn
-            let strokeData = data.get(this._currentStrokeId);
-            if (strokeData) {
-                strokeData.pathData.push(this.relativeCoordinatesForEvent(x, y));
-                data.set(this._currentStrokeId, strokeData);
-            }
-            this.inkData = data;
-        }
-    }
-
-    @action
-    onPointerMove = (e: PointerEvent): void => {
-        if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) {
-            return;
-        }
-        e.stopPropagation();
-        e.preventDefault();
-        this.handleMove(e.clientX, e.clientY);
-    }
-
-    relativeCoordinatesForEvent = (ex: number, ey: number): { x: number, y: number } => {
-        let [x, y] = this.props.getScreenTransform().transformPoint(ex, ey);
-        return { x, y };
-    }
-
-    @action
-    removeLine = (id: string): void => {
-        if (!this.previousState) {
-            this.previousState = new Map(this.inkData);
-            document.addEventListener("pointermove", this.onPointerMove, true);
-            document.addEventListener("pointerup", this.onPointerUp, true);
-        }
-        let data = this.inkData;
-        data.delete(id);
-        this.inkData = data;
-    }
-
-    // @computed
-    // get drawnPaths() {
-    //     let curTimecode = NumCast(this.props.Document.currentTimecode, -1);
-    //     let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
-    //         if (strokeData.displayTimecode === -1 || (Math.abs(Math.round(strokeData.displayTimecode) - Math.round(curTimecode)) < 3)) {
-    //             paths.push(<InkingStroke key={id} id={id}
-    //                 line={strokeData.pathData}
-    //                 count={strokeData.pathData.length}
-    //                 offsetX={this.maxCanvasDim - this.inkMidX}
-    //                 offsetY={this.maxCanvasDim - this.inkMidY}
-    //                 color={strokeData.color}
-    //                 width={strokeData.width}
-    //                 tool={strokeData.tool}
-    //                 creationTime={strokeData.creationTime}
-    //                 deleteCallback={this.removeLine} />);
-    //         }
-    //         return paths;
-    //     }, [] as JSX.Element[]);
-    //     let markerPaths = paths.filter(path => path.props.tool === InkTool.Highlighter);
-    //     let penPaths = paths.filter(path => path.props.tool !== InkTool.Highlighter);
-    //     return [!penPaths.length ? (null) :
-    //         <svg className={`inkingCanvas-paths-ink`} key="Pens"
-    //             style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} >
-    //             {penPaths}
-    //         </svg>,
-    //     !markerPaths.length ? (null) :
-    //         <svg className={`inkingCanvas-paths-markers`} key="Markers"
-    //             style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}>
-    //             {markerPaths}
-    //         </svg>];
-    // }
-
-    render() {
-        let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect";
-        let cursor = svgCanvasStyle === "canSelect" ? (InkingControl.Instance.selectedTool === InkTool.Eraser ||
-            InkingControl.Instance.selectedTool === InkTool.Scrubber ? "pointer" : "default") : undefined;
-        return (
-            <div className="inkingCanvas">
-                <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart} style={{ cursor: cursor }} />
-                {this.props.children()}
-                {/* {this.drawnPaths} */}
-            </div >
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx
index 5b1224b96..eea05ea61 100644
--- a/src/client/views/collections/CollectionStaffView.tsx
+++ b/src/client/views/collections/CollectionStaffView.tsx
@@ -1,5 +1,4 @@
 import { CollectionSubView } from "./CollectionSubView";
-import { InkingCanvas } from "../InkingCanvas";
 import { Transform } from "../../util/Transform";
 import React = require("react")
 import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx";
@@ -54,9 +53,6 @@ export class CollectionStaffView extends CollectionSubView(doc => doc) {
     render() {
         return (
             <div className="collectionStaffView" ref={this._mainCont}>
-                <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.fieldExtensionDoc} inkFieldKey={"ink"} >
-                    {() => []}
-                </InkingCanvas>
                 {this.staves}
                 {this.addStaffButton}
             </div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 61d097c62..c7806a097 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -24,7 +24,6 @@ import { undoBatch, UndoManager } from "../../../util/UndoManager";
 import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
 import { ContextMenu } from "../../ContextMenu";
 import { ContextMenuProps } from "../../ContextMenuItem";
-import { InkingCanvas } from "../../InkingCanvas";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { FormattedTextBox } from "../../nodes/FormattedTextBox";
@@ -298,8 +297,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         if (this._points.length > 1) {
             let B = this.svgBounds;
             let points = this._points.map(p => ({ x: p.x - B.left, y: p.y - B.top }));
-            let inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { width: B.width, height: B.height, x: B.left, y: B.top });
-            this.addDocument(inkDoc);
+            UndoManager.RunInBatch(() => {
+                let inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { width: B.width, height: B.height, x: B.left, y: B.top });
+                this.addDocument(inkDoc);
+            }, "addink");
             this._points = [];
         }
 
@@ -385,7 +386,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     handle1PointerMove = (e: TouchEvent) => {
         // panning a workspace
-        if (!e.cancelBubble && this.props.active()) {
+        if (!e.cancelBubble && this.props.active() && !SelectionManager.GetIsDragging()) {
             let pt = e.targetTouches.item(0);
             if (pt) {
                 this.pan(pt);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b5f6f095e..1066f4f8d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -13,7 +13,6 @@ import { Docs } from "../../../documents/Documents";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { Transform } from "../../../util/Transform";
 import { undoBatch } from "../../../util/UndoManager";
-import { InkingCanvas } from "../../InkingCanvas";
 import { PreviewCursor } from "../../PreviewCursor";
 import { CollectionViewType } from "../CollectionView";
 import { CollectionFreeFormView } from "./CollectionFreeFormView";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5b3eea280..6ffa62fe5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -41,6 +41,7 @@ import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import { FormattedTextBox } from './FormattedTextBox';
 import React = require("react");
+import { InteractionUtils } from '../../util/InteractionUtils';
 
 library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -138,7 +139,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";
@@ -216,7 +217,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive() || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
-                if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && e.buttons === 1) {
+                if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCH))) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
                     this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
@@ -354,7 +355,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);
-- 
cgit v1.2.3-70-g09d2

