From c3eea60fa586e8ff9cf59497c041044fd0143bb1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 28 Mar 2019 21:03:17 -0400
Subject: organized collectionfreeformview.  fixed issues with showing links

---
 .../collectionFreeForm/PreviewCursor.tsx           | 76 ++++++++++++++++++++++
 1 file changed, 76 insertions(+)
 create mode 100644 src/client/views/collections/collectionFreeForm/PreviewCursor.tsx

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

diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
new file mode 100644
index 000000000..95364f04b
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
@@ -0,0 +1,76 @@
+import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { Opt } from "../../../../fields/Field";
+import { Documents } from "../../../documents/Documents";
+import { Transform } from "../../../util/Transform";
+import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import "./PreviewCursor.scss";
+import React = require("react");
+
+
+export interface PreviewCursorProps {
+    getTransform: () => Transform;
+    container: CollectionFreeFormView;
+    addLiveTextDocument: (doc: Document) => void;
+}
+
+@observer
+export class PreviewCursor extends React.Component<PreviewCursorProps>  {
+    private _reactionDisposer: Opt<IReactionDisposer>;
+
+    @observable _lastX: number = 0;
+    @observable _lastY: number = 0;
+
+    componentDidMount() {
+        this._reactionDisposer = reaction(
+            () => this.props.container.PreviewCursorVisible,
+            (visible: boolean) => this.onCursorPlaced(visible, this.props.container.DownX, this.props.container.DownY))
+    }
+    componentWillUnmount() {
+        if (this._reactionDisposer) {
+            this._reactionDisposer();
+        }
+        this.cleanupInteractions();
+    }
+
+
+    @action
+    cleanupInteractions = () => {
+        document.removeEventListener("keypress", this.onKeyPress, false);
+    }
+
+    @action
+    onCursorPlaced = (visible: boolean, downX: number, downY: number): void => {
+        if (visible) {
+            document.addEventListener("keypress", this.onKeyPress, false);
+            this._lastX = downX;
+            this._lastY = downY;
+        } else
+            this.cleanupInteractions();
+    }
+
+    @action
+    onKeyPress = (e: KeyboardEvent) => {
+        // Mixing events between React and Native is finicky.  In FormattedTextBox, we set the
+        // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
+        // the keyPress here.
+        //if not these keys, make a textbox if preview cursor is active!
+        if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
+            //make textbox and add it to this collection
+            let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
+            let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" });
+            this.props.addLiveTextDocument(newBox);
+            e.stopPropagation();
+        }
+    }
+
+    render() {
+        //get local position and place cursor there!
+        let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
+        return (
+            !this.props.container.PreviewCursorVisible ? (null) :
+                <div className="previewCursor" id="previewCursor" style={{ transform: `translate(${x}px, ${y}px)` }}>I</div>)
+
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From eb220da697e2383b1e368dee743613158994746e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 31 Mar 2019 17:58:11 -0400
Subject: restructured marquee and preview cursor to not need reactions. fixed
 some inking bugs.

---
 src/client/views/InkingCanvas.tsx                  |  25 ++-
 .../CollectionFreeFormLinksView.tsx                |   1 +
 .../collectionFreeForm/CollectionFreeFormView.scss |   1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 199 ++++++++-------------
 .../collectionFreeForm/MarqueeView.scss            |   6 +
 .../collections/collectionFreeForm/MarqueeView.tsx | 169 +++++++++--------
 .../collectionFreeForm/PreviewCursor.scss          |   7 +-
 .../collectionFreeForm/PreviewCursor.tsx           |  71 +++++---
 8 files changed, 245 insertions(+), 234 deletions(-)

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

diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 36a8834a0..8fb74ec48 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -1,16 +1,15 @@
-import { observer } from "mobx-react";
-import { observable } from "mobx";
 import { action, computed } from "mobx";
-import { InkingControl } from "./InkingControl";
-import React = require("react");
-import { Transform } from "../util/Transform";
+import { observer } from "mobx-react";
 import { Document } from "../../fields/Document";
-import { KeyStore } from "../../fields/KeyStore";
+import { FieldWaiting } from "../../fields/Field";
 import { InkField, InkTool, StrokeData, StrokeMap } from "../../fields/InkField";
-import { InkingStroke } from "./InkingStroke";
-import "./InkingCanvas.scss"
+import { KeyStore } from "../../fields/KeyStore";
 import { Utils } from "../../Utils";
-import { FieldWaiting } from "../../fields/Field";
+import { Transform } from "../util/Transform";
+import "./InkingCanvas.scss";
+import { InkingControl } from "./InkingControl";
+import { InkingStroke } from "./InkingStroke";
+import React = require("react");
 
 interface InkCanvasProps {
     getScreenTransform: () => Transform;
@@ -71,8 +70,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
         // start the new line, saves a uuid to represent the field of the stroke
         this._idGenerator = Utils.GenerateGuid();
-        let data = this.inkData;
-        data.set(this._idGenerator,
+        this.inkData.set(this._idGenerator,
             {
                 pathData: [point],
                 color: InkingControl.Instance.selectedColor,
@@ -80,7 +78,6 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
                 tool: InkingControl.Instance.selectedTool,
                 page: this.props.Document.GetNumber(KeyStore.CurPage, -1)
             });
-        this.inkData = data;
         this._isDrawing = true;
     }
 
@@ -163,9 +160,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
         })
 
         return (
-
-            <div className="inking-canvas" style={canvasStyle}
-                onPointerDown={this.handleMouseDown} onPointerMove={this.handleMouseMove} >
+            <div className="inking-canvas" style={canvasStyle} onPointerDown={this.handleMouseDown} onPointerMove={this.handleMouseMove} >
                 <svg>
                     {paths}
                 </svg>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index f793b3a49..ea392eaea 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -96,6 +96,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
         return (
             <svg className="collectionfreeformlinksview-svgCanvas">
                 {this.uniqueConnections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />)}
+                {this.props.children}
             </svg>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 215a75243..c979b27a9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -6,6 +6,7 @@
   left: 0;
   width: 100%;
   height: 100%;
+  transform-origin: left top;
   .inking-canvas {
     transform-origin: 50000px 50000px;
   }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1f90b0d46..cdc5bdfb0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -3,7 +3,6 @@ import { observer } from "mobx-react";
 import { Document } from "../../../../fields/Document";
 import { FieldWaiting } from "../../../../fields/Field";
 import { KeyStore } from "../../../../fields/KeyStore";
-import { ListField } from "../../../../fields/ListField";
 import { TextField } from "../../../../fields/TextField";
 import { DragManager } from "../../../util/DragManager";
 import { Transform } from "../../../util/Transform";
@@ -14,10 +13,9 @@ import { DocumentContentsView } from "../../nodes/DocumentContentsView";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { COLLECTION_BORDER_WIDTH } from "../CollectionView";
 import { CollectionViewBase } from "../CollectionViewBase";
-import { MarqueeView } from "./MarqueeView";
-import { PreviewCursor } from "./PreviewCursor";
 import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
 import "./CollectionFreeFormView.scss";
+import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
 
@@ -27,12 +25,15 @@ export class CollectionFreeFormView extends CollectionViewBase {
     private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
 
     public addLiveTextBox = (newBox: Document) => {
-        // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself
+        // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself and receive text input
         this._selectOnLoaded = newBox.Id;
-        //set text to be the typed key and get focus on text box
-        this.props.addDocument(newBox, false);
-        //remove cursor from screen
-        this.PreviewCursorVisible = false;
+        this.addDocument(newBox, false);
+    }
+
+    public addDocument = (newBox: Document, allowDuplicates: boolean) => {
+        let added = this.props.addDocument(newBox, false);
+        this.bringToFront(newBox);
+        return added;
     }
 
     public selectDocuments = (docs: Document[]) => {
@@ -42,23 +43,15 @@ export class CollectionFreeFormView extends CollectionViewBase {
 
     public getActiveDocuments = () => {
         var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
-        const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
-        let active: Document[] = [];
-        if (lvalue && lvalue != FieldWaiting) {
-            lvalue.Data.map(doc => {
-                var page = doc.GetNumber(KeyStore.Page, -1);
-                if (page == curPage || page == -1) {
-                    active.push(doc);
-                }
-            })
-        }
-
-        return active;
+        return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).reduce((active, doc) => {
+            var page = doc.GetNumber(KeyStore.Page, -1);
+            if (page == curPage || page == -1) {
+                active.push(doc);
+            }
+            return active;
+        }, [] as Document[]);
     }
 
-    //determines whether the blinking cursor for indicating whether a text will be made on key down is visible
-    @observable public PreviewCursorVisible: boolean = false;
-    @observable public MarqueeVisible = false;
     @observable public DownX: number = 0;
     @observable public DownY: number = 0;
     @observable private _lastX: number = 0;
@@ -100,12 +93,10 @@ export class CollectionFreeFormView extends CollectionViewBase {
     cleanupInteractions = () => {
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
-        this.MarqueeVisible = false;
     }
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        this.PreviewCursorVisible = false;
         if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) {
             document.removeEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointermove", this.onPointerMove);
@@ -120,28 +111,13 @@ export class CollectionFreeFormView extends CollectionViewBase {
     onPointerUp = (e: PointerEvent): void => {
         e.stopPropagation();
 
-        if (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) {
-            //show preview text cursor on tap
-            this.PreviewCursorVisible = true;
-            //select is not already selected
-            if (!this.props.isSelected()) {
-                this.props.select(false);
-            }
-        }
         this.cleanupInteractions();
     }
 
     @action
     onPointerMove = (e: PointerEvent): void => {
         if (!e.cancelBubble && this.props.active()) {
-            if (e.buttons == 1 && !e.altKey && !e.metaKey) {
-                this.MarqueeVisible = true;
-            }
-            if (this.MarqueeVisible) {
-                e.stopPropagation();
-                e.preventDefault();
-            }
-            else if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) {
+            if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) {
                 let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
                 let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
                 let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
@@ -264,16 +240,12 @@ export class CollectionFreeFormView extends CollectionViewBase {
     @computed
     get views() {
         var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
-        const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
-        if (lvalue && lvalue != FieldWaiting) {
-            return lvalue.Data.map(doc => {
-                if (!doc) return null;
-                var page = doc.GetNumber(KeyStore.Page, 0);
-                return (page != curPage && page != 0) ? (null) :
-                    (<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
-            })
-        }
-        return null;
+        return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => {
+            var page = doc.GetNumber(KeyStore.Page, -1);
+            if (page == curPage || page == -1)
+                prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
+            return prev;
+        }, [] as JSX.Element[])
     }
 
     @computed
@@ -290,15 +262,10 @@ export class CollectionFreeFormView extends CollectionViewBase {
     }
 
     getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
-    getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
+    getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
     getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY);
     noScaling = () => 1;
 
-    //when focus is lost, this will remove the preview cursor
-    @action
-    onBlur = (): void => {
-        this.PreviewCursorVisible = false;
-    }
 
     private crosshairs?: HTMLCanvasElement;
     drawCrosshairs = (backgroundColor: string) => {
@@ -345,71 +312,63 @@ export class CollectionFreeFormView extends CollectionViewBase {
 
         return (
             <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
-                onPointerDown={this.onPointerDown}
-                onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))}
-                onWheel={this.onPointerWheel}
-                onDrop={this.onDrop.bind(this)}
-                onDragOver={this.onDragOver}
-                onBlur={this.onBlur}
-                style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}// , zIndex: !this.props.isTopMost ? -1 : undefined }}
-                tabIndex={0}
-                ref={this.createDropTarget}>
-                <div className="collectionfreeformview"
-                    style={{ transformOrigin: "left top", transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}
-                    ref={this._canvasRef}>
-                    {this.backgroundView}
-                    <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} />
-                    <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} getTransform={this.getTransform} />
-                    {this.views}
-                    <CollectionFreeFormLinksView {...this.props} />
-                    {super.getCursors().map(entry => {
-                        if (entry.Data.length > 0) {
-                            let id = entry.Data[0][0];
-                            let email = entry.Data[0][1];
-                            let point = entry.Data[1];
-                            this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22")
-                            return (
-                                <div
-                                    key={id}
-                                    style={{
-                                        position: "absolute",
-                                        transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`,
-                                        zIndex: 10000,
-                                        transformOrigin: 'center center',
-                                    }}
-                                >
-                                    <canvas
-                                        ref={(el) => { if (el) this.crosshairs = el }}
-                                        width={20}
-                                        height={20}
-                                        style={{
-                                            position: 'absolute',
-                                            width: "20px",
-                                            height: "20px",
-                                            opacity: 0.5,
-                                            borderRadius: "50%",
-                                            border: "2px solid black"
-                                        }}
-                                    />
-                                    <p
+                onPointerDown={this.onPointerDown} onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))}
+                onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel}
+                style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} tabIndex={0} ref={this.createDropTarget}>
+                <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
+                    addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
+                    getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
+                    <div className="collectionfreeformview" ref={this._canvasRef}
+                        style={{ transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}>
+                        {this.backgroundView}
+                        <CollectionFreeFormLinksView {...this.props} />
+                        {this.views}
+                        <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} />
+                        {super.getCursors().map(entry => {
+                            if (entry.Data.length > 0) {
+                                let id = entry.Data[0][0];
+                                let email = entry.Data[0][1];
+                                let point = entry.Data[1];
+                                this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22")
+                                return (
+                                    <div
+                                        key={id}
                                         style={{
-                                            fontSize: 14,
-                                            color: "black",
-                                            // fontStyle: "italic",
-                                            marginLeft: -12,
-                                            marginTop: 4
+                                            position: "absolute",
+                                            transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`,
+                                            zIndex: 10000,
+                                            transformOrigin: 'center center',
                                         }}
-                                    >{email[0].toUpperCase()}</p>
-                                </div>
-                            );
-                        }
-                    })}
-                </div>
-                <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
-                    addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
-                    getMarqueeTransform={this.getMarqueeTransform} getTransform={this.getTransform} />
-                {this.overlayView}
-
+                                    >
+                                        <canvas
+                                            ref={(el) => { if (el) this.crosshairs = el }}
+                                            width={20}
+                                            height={20}
+                                            style={{
+                                                position: 'absolute',
+                                                width: "20px",
+                                                height: "20px",
+                                                opacity: 0.5,
+                                                borderRadius: "50%",
+                                                border: "2px solid black"
+                                            }}
+                                        />
+                                        <p
+                                            style={{
+                                                fontSize: 14,
+                                                color: "black",
+                                                // fontStyle: "italic",
+                                                marginLeft: -12,
+                                                marginTop: 4
+                                            }}
+                                        >{email[0].toUpperCase()}</p>
+                                    </div>
+                                );
+                            }
+                        })}
+                    </div>
+                    {this.overlayView}
+                </MarqueeView>
             </div>
         );
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 6d9a79344..1ee3b244b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -1,8 +1,14 @@
 
 .marqueeView {
+    position: absolute;
+    width:100%;
+    height:100%;
+}
+.marquee {
     border-style: dashed;
     box-sizing: border-box;
     position: absolute;
     border-width: 1px;
     border-color: black;
+    pointer-events: none;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 2692690dd..2b0e7d228 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,62 +1,58 @@
-import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../../fields/Document";
-import { FieldWaiting, Opt } from "../../../../fields/Field";
+import { FieldWaiting } from "../../../../fields/Field";
+import { InkField, StrokeData } from "../../../../fields/InkField";
 import { KeyStore } from "../../../../fields/KeyStore";
 import { Documents } from "../../../documents/Documents";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { Transform } from "../../../util/Transform";
+import { InkingCanvas } from "../../InkingCanvas";
 import { CollectionFreeFormView } from "./CollectionFreeFormView";
 import "./MarqueeView.scss";
+import { PreviewCursor } from "./PreviewCursor";
 import React = require("react");
-import { InkField, StrokeData } from "../../../../fields/InkField";
-import { Utils } from "../../../../Utils";
-import { InkingCanvas } from "../../InkingCanvas";
 
 interface MarqueeViewProps {
-    getMarqueeTransform: () => Transform;
+    getContainerTransform: () => Transform;
     getTransform: () => Transform;
     container: CollectionFreeFormView;
     addDocument: (doc: Document, allowDuplicates: false) => boolean;
     activeDocuments: () => Document[];
     selectDocuments: (docs: Document[]) => void;
     removeDocument: (doc: Document) => boolean;
+    addLiveTextDocument: (doc: Document) => void;
 }
 
 @observer
 export class MarqueeView extends React.Component<MarqueeViewProps>
 {
-    private _reactionDisposer: Opt<IReactionDisposer>;
-
     @observable _lastX: number = 0;
     @observable _lastY: number = 0;
     @observable _downX: number = 0;
     @observable _downY: number = 0;
-
-    componentDidMount() {
-        this._reactionDisposer = reaction(
-            () => this.props.container.MarqueeVisible,
-            (visible: boolean) => this.onPointerDown(visible, this.props.container.DownX, this.props.container.DownY))
-    }
-    componentWillUnmount() {
-        if (this._reactionDisposer) {
-            this._reactionDisposer();
-        }
-        this.cleanupInteractions();
-    }
+    @observable _used: boolean = false;
+    @observable _visible: boolean = false;
+    static DRAG_THRESHOLD = 4;
 
     @action
-    cleanupInteractions = () => {
-        document.removeEventListener("pointermove", this.onPointerMove, true)
-        document.removeEventListener("pointerup", this.onPointerUp, true);
+    cleanupInteractions = (all: boolean = false) => {
+        if (all) {
+            document.removeEventListener("pointermove", this.onPointerMove, true)
+            document.removeEventListener("pointerup", this.onPointerUp, true);
+        } else {
+            this._used = true;
+        }
         document.removeEventListener("keydown", this.marqueeCommand, true);
+        this._visible = false;
     }
 
     @action
-    onPointerDown = (visible: boolean, downX: number, downY: number): void => {
-        if (visible) {
-            this._downX = this._lastX = downX;
-            this._downY = this._lastY = downY;
+    onPointerDown = (e: React.PointerEvent): void => {
+        if (e.buttons == 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) {
+            this._downX = this._lastX = e.pageX;
+            this._downY = this._lastY = e.pageY;
+            this._used = false;
             document.addEventListener("pointermove", this.onPointerMove, true)
             document.addEventListener("pointerup", this.onPointerUp, true);
             document.addEventListener("keydown", this.marqueeCommand, true);
@@ -67,11 +63,20 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     onPointerMove = (e: PointerEvent): void => {
         this._lastX = e.pageX;
         this._lastY = e.pageY;
+        if (!e.cancelBubble) {
+            if (!this._used && e.buttons == 1 && !e.altKey && !e.metaKey &&
+                (Math.abs(this._lastX - this._downX) > MarqueeView.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > MarqueeView.DRAG_THRESHOLD)) {
+                this._visible = true;
+            }
+            e.stopPropagation();
+            e.preventDefault();
+        }
     }
 
     @action
     onPointerUp = (e: PointerEvent): void => {
-        this.cleanupInteractions();
+        this.cleanupInteractions(true);
+        this._visible = false;
         if (!e.shiftKey) {
             SelectionManager.DeselectAll();
         }
@@ -83,6 +88,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
     }
 
+    @computed
     get Bounds() {
         let left = this._downX < this._lastX ? this._downX : this._lastX;
         let top = this._downY < this._lastY ? this._downY : this._lastY;
@@ -95,7 +101,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     marqueeCommand = (e: KeyboardEvent) => {
         if (e.key == "Backspace" || e.key == "Delete") {
             this.marqueeSelect().map(d => this.props.removeDocument(d));
-            this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
+            let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
+            if (ink && ink != FieldWaiting && ink.Data) {
+                this.marqueeInkDelete(ink.Data);
+            }
             this.cleanupInteractions();
         }
         if (e.key == "c") {
@@ -104,53 +113,61 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 this.props.removeDocument(d);
                 d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2);
                 d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2);
-                d.SetNumber(KeyStore.Page, 0);
+                d.SetNumber(KeyStore.Page, -1);
                 d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0));
                 return d;
             });
-            let liftedInk = this.marqueeInkSelect(true);
-            this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
-            //setTimeout(() => {
-            let newCollection = Documents.FreeformDocument(selected, {
-                x: bounds.left,
-                y: bounds.top,
-                panx: 0,
-                pany: 0,
-                width: bounds.width,
-                height: bounds.height,
-                backgroundColor: "Transparent",
-                ink: liftedInk,
-                title: "a nested collection"
-            });
-            this.props.addDocument(newCollection, false);
+            let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
+            if (ink && ink != FieldWaiting && ink.Data) {
+                //setTimeout(() => {
+                let newCollection = Documents.FreeformDocument(selected, {
+                    x: bounds.left,
+                    y: bounds.top,
+                    panx: 0,
+                    pany: 0,
+                    width: bounds.width,
+                    height: bounds.height,
+                    backgroundColor: "Transparent",
+                    ink: this.marqueeInkSelect(ink.Data),
+                    title: "a nested collection"
+                });
+                this.props.addDocument(newCollection, false);
+                this.marqueeInkDelete(ink.Data);
+            }
             // }, 100);
             this.cleanupInteractions();
         }
     }
-    marqueeInkSelect(select: boolean) {
-        let selRect = this.Bounds;
-        let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
-        let centerShiftY = 0 - (selRect.top + selRect.height / 2);
-        let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
-        if (ink && ink != FieldWaiting && ink.Data) {
-            let idata = new Map();
-            ink.Data.forEach((value: StrokeData, key: string, map: any) => {
-                let inside = InkingCanvas.IntersectStrokeRect(value, selRect);
-                if (inside && select) {
-                    idata.set(key,
-                        {
-                            pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }),
-                            color: value.color,
-                            width: value.width,
-                            tool: value.tool,
-                            page: -1
-                        });
-                } else if (!inside && !select) {
-                    idata.set(key, value);
-                }
-            })
-            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: StrokeData, key: string, map: any) => {
+            if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) {
+                idata.set(key,
+                    {
+                        pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }),
+                        color: value.color,
+                        width: value.width,
+                        tool: value.tool,
+                        page: -1
+                    });
+            }
+        });
+        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));
+
+        let idata = new Map();
+        ink.forEach((value: StrokeData, key: string, map: any) =>
+            !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value));
+        this.props.container.props.Document.SetDataOnPrototype(KeyStore.Ink, idata, InkField);
     }
 
     marqueeSelect() {
@@ -168,8 +185,16 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     }
 
     render() {
-        let p = this.props.getMarqueeTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
-        let v = this.props.getMarqueeTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
-        return (!this.props.container.MarqueeVisible ? (null) : <div className="marqueeView" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />);
+        let p = this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
+        let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+        return <div className="marqueeView" onPointerDown={this.onPointerDown}>
+            <PreviewCursor container={this.props.container} addLiveTextDocument={this.props.addLiveTextDocument}
+                getContainerTransform={this.props.getContainerTransform} getTransform={this.props.getTransform} >
+                {this.props.children}
+                {!this._visible ? (null) :
+                    <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />}
+
+            </PreviewCursor>
+        </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
index a797411f6..7d6d5aaab 100644
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
@@ -5,6 +5,11 @@
     transform-origin: left top;
     pointer-events: none;
 }
+.previewCursorView {
+    position: absolute;
+    width:100%;
+    height:100%;
+}
 
 //this is an animation for the blinking cursor!
 @keyframes blink {
@@ -14,5 +19,5 @@
 }
 
 #previewCursor {
-    animation: blink 1s infinite;
+     animation: blink 1s infinite;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
index 95364f04b..339e056a8 100644
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
@@ -1,7 +1,6 @@
-import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { action, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../../fields/Document";
-import { Opt } from "../../../../fields/Field";
 import { Documents } from "../../../documents/Documents";
 import { Transform } from "../../../util/Transform";
 import { CollectionFreeFormView } from "./CollectionFreeFormView";
@@ -11,43 +10,50 @@ import React = require("react");
 
 export interface PreviewCursorProps {
     getTransform: () => Transform;
+    getContainerTransform: () => Transform;
     container: CollectionFreeFormView;
     addLiveTextDocument: (doc: Document) => void;
 }
 
 @observer
 export class PreviewCursor extends React.Component<PreviewCursorProps>  {
-    private _reactionDisposer: Opt<IReactionDisposer>;
-
     @observable _lastX: number = 0;
     @observable _lastY: number = 0;
+    @observable public _visible: boolean = false;
+    @observable public DownX: number = 0;
+    @observable public DownY: number = 0;
+    _showOnUp: boolean = false;
+    public _previewDivRef = React.createRef<HTMLDivElement>();
 
-    componentDidMount() {
-        this._reactionDisposer = reaction(
-            () => this.props.container.PreviewCursorVisible,
-            (visible: boolean) => this.onCursorPlaced(visible, this.props.container.DownX, this.props.container.DownY))
-    }
-    componentWillUnmount() {
-        if (this._reactionDisposer) {
-            this._reactionDisposer();
-        }
-        this.cleanupInteractions();
+    @action
+    cleanupInteractions = () => {
+        document.removeEventListener("pointerup", this.onPointerUp, true);
     }
 
-
     @action
-    cleanupInteractions = () => {
+    onPointerDown = (e: React.PointerEvent) => {
+        this._visible = false;
         document.removeEventListener("keypress", this.onKeyPress, false);
+        this._showOnUp = true;
+        this._lastX = this.DownX = e.pageX;
+        this._lastY = this.DownY = e.pageY;
+        document.addEventListener("pointerup", this.onPointerUp, true);
+        document.addEventListener("pointermove", this.onPointerMove, true);
+    }
+    @action
+    onPointerMove = (e: PointerEvent): void => {
+        if (Math.abs(this.DownX - e.clientX) > 4 || Math.abs(this.DownY - e.clientY) > 4) {
+            this._showOnUp = false;
+        }
     }
 
     @action
-    onCursorPlaced = (visible: boolean, downX: number, downY: number): void => {
-        if (visible) {
+    onPointerUp = (e: PointerEvent): void => {
+        if (this._showOnUp) {
             document.addEventListener("keypress", this.onKeyPress, false);
-            this._lastX = downX;
-            this._lastY = downY;
-        } else
-            this.cleanupInteractions();
+            this._visible = true;
+        }
+        this.cleanupInteractions();
     }
 
     @action
@@ -61,16 +67,29 @@ export class PreviewCursor extends React.Component<PreviewCursorProps>  {
             let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
             let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" });
             this.props.addLiveTextDocument(newBox);
+            document.removeEventListener("keypress", this.onKeyPress, false);
+            this._visible = false;
             e.stopPropagation();
         }
     }
+    //when focus is lost, this will remove the preview cursor
+    @action
+    onBlur = (): void => {
+        this._visible = false;
+        document.removeEventListener("keypress", this.onKeyPress, false);
+    }
 
     render() {
         //get local position and place cursor there!
-        let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
+        let p = this.props.getContainerTransform().transformPoint(this._lastX, this._lastY);
+        if (this._visible && this._previewDivRef.current)
+            this._previewDivRef.current!.focus();
         return (
-            !this.props.container.PreviewCursorVisible ? (null) :
-                <div className="previewCursor" id="previewCursor" style={{ transform: `translate(${x}px, ${y}px)` }}>I</div>)
-
+            <div className="previewCursorView" tabIndex={0} ref={this._previewDivRef} onBlur={this.onBlur} onPointerDown={this.onPointerDown}>
+                {this.props.children}
+                {!this._visible ? (null) :
+                    <div className="previewCursor" id="previewCursor" style={{ transform: `translate(${p[0]}px, ${p[1]}px)` }}>I</div>}
+            </div>
+        )
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 5940a2dce5b45382521cd20b4770732dcbc3e732 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 1 Apr 2019 10:03:06 -0400
Subject: fixed inking problem - blinking cursors don't play nicely with ink

---
 src/client/views/InkingCanvas.tsx                        | 15 ++++-----------
 src/client/views/InkingStroke.tsx                        | 15 ++++-----------
 .../collections/collectionFreeForm/PreviewCursor.scss    | 16 ++++++++--------
 .../collections/collectionFreeForm/PreviewCursor.tsx     |  7 ++++---
 4 files changed, 20 insertions(+), 33 deletions(-)

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

diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index fc871e108..15dfb255a 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -19,19 +19,12 @@ interface InkCanvasProps {
 @observer
 export class InkingCanvas extends React.Component<InkCanvasProps> {
     static InkOffset: number = 50000;
+    private _currentStrokeId: string = "";
     public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean {
-        let inside = false;
-        stroke.pathData.map(val => {
-            if (selRect.left < val.x - InkingCanvas.InkOffset && selRect.left + selRect.width > val.x - InkingCanvas.InkOffset &&
+        return stroke.pathData.reduce((inside, val) => inside ||
+            (selRect.left < val.x - InkingCanvas.InkOffset && selRect.left + selRect.width > val.x - InkingCanvas.InkOffset &&
                 selRect.top < val.y - InkingCanvas.InkOffset && selRect.top + selRect.height > val.y - InkingCanvas.InkOffset)
-                inside = true;
-        });
-        return inside
-    }
-    private _currentStrokeId: string = "";
-
-    constructor(props: Readonly<InkCanvasProps>) {
-        super(props);
+            , false);
     }
 
     @computed
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 87b5c43d8..52111c711 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -21,21 +21,14 @@ export class InkingStroke extends React.Component<StrokeProps> {
     @observable private _strokeColor: string = this.props.color;
     @observable private _strokeWidth: string = this.props.width;
 
-    deleteStroke = (e: React.MouseEvent): void => {
+    deleteStroke = (e: React.PointerEvent): void => {
         if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) {
             this.props.deleteCallback(this.props.id);
         }
     }
 
     parseData = (line: Array<{ x: number, y: number }>): string => {
-        if (line.length === 0) {
-            return "";
-        }
-        const pathData = "M " +
-            line.map(p => {
-                return p.x + " " + p.y;
-            }).join(" L ");
-        return pathData;
+        return !line.length ? "" : "M " + line.map(p => p.x + " " + p.y).join(" L ");
     }
 
     createStyle() {
@@ -57,8 +50,8 @@ export class InkingStroke extends React.Component<StrokeProps> {
 
         return (
             <path className={(this._strokeTool === InkTool.Highlighter) ? "highlight" : ""}
-                d={pathData} style={pathStyle} strokeLinejoin="round" strokeLinecap="round"
-                onMouseOver={this.deleteStroke} onMouseDown={this.deleteStroke} />
+                d={pathData} style={{ ...pathStyle, pointerEvents: "all" }} strokeLinejoin="round" strokeLinecap="round"
+                onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />
         )
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
index 7d6d5aaab..21210be2b 100644
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
@@ -12,12 +12,12 @@
 }
 
 //this is an animation for the blinking cursor!
-@keyframes blink {
-	0% {opacity: 0}
-	49%{opacity: 0}
-	50% {opacity: 1}
-}
+// @keyframes blink {
+// 	0% {opacity: 0}
+// 	49%{opacity: 0}
+// 	50% {opacity: 1}
+// }
 
-#previewCursor {
-     animation: blink 1s infinite;
-}
\ No newline at end of file
+// #previewCursor {
+//      animation: blink 1s infinite;
+// }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
index 339e056a8..877de8846 100644
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
@@ -1,4 +1,4 @@
-import { action, observable } from "mobx";
+import { action, observable, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../../fields/Document";
 import { Documents } from "../../../documents/Documents";
@@ -84,11 +84,12 @@ export class PreviewCursor extends React.Component<PreviewCursorProps>  {
         let p = this.props.getContainerTransform().transformPoint(this._lastX, this._lastY);
         if (this._visible && this._previewDivRef.current)
             this._previewDivRef.current!.focus();
+        trace();
         return (
-            <div className="previewCursorView" tabIndex={0} ref={this._previewDivRef} onBlur={this.onBlur} onPointerDown={this.onPointerDown}>
+            <div className="previewCursorView" onBlur={this.onBlur} onPointerDown={this.onPointerDown}>
                 {this.props.children}
                 {!this._visible ? (null) :
-                    <div className="previewCursor" id="previewCursor" style={{ transform: `translate(${p[0]}px, ${p[1]}px)` }}>I</div>}
+                    <div className="previewCursor" tabIndex={0} ref={this._previewDivRef} id="previewCursor" style={{ transform: `translate(${p[0]}px, ${p[1]}px)` }}>I</div>}
             </div>
         )
     }
-- 
cgit v1.2.3-70-g09d2


From a687a9962c8952074177e52366b69dcce231a18e Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 1 Apr 2019 13:46:32 -0400
Subject: fixed several things related to selections and creating text boxes. 
 moved remote cursor stuff into its own file.

---
 src/client/util/SelectionManager.ts                |  13 ++-
 src/client/views/InkingCanvas.tsx                  |   8 +-
 .../views/collections/CollectionViewBase.tsx       |   8 --
 .../CollectionFreeFormLinksView.tsx                |   7 +-
 .../CollectionFreeFormRemoteCursors.tsx            | 115 ++++++++++++++++++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 118 ++++-----------------
 .../collections/collectionFreeForm/MarqueeView.tsx |  24 ++---
 .../collectionFreeForm/PreviewCursor.tsx           |  67 ++++++++----
 8 files changed, 213 insertions(+), 147 deletions(-)
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx

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

diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 1a711ae64..6d6ae26fc 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,5 +1,6 @@
 import { observable, action } from "mobx";
 import { DocumentView } from "../views/nodes/DocumentView";
+import { Document } from "../../fields/Document"
 
 export namespace SelectionManager {
     class Manager {
@@ -29,8 +30,16 @@ export namespace SelectionManager {
         return manager.SelectedDocuments.indexOf(doc) !== -1;
     }
 
-    export function DeselectAll(): void {
-        manager.SelectedDocuments = []
+    export function DeselectAll(except?: Document): void {
+        let found: DocumentView | undefined = undefined;
+        if (except) {
+            for (let i = 0; i < manager.SelectedDocuments.length; i++) {
+                let view = manager.SelectedDocuments[i];
+                if (view.props.Document == except)
+                    found = view;
+            }
+        }
+        manager.SelectedDocuments.length = 0;
     }
 
     export function SelectedDocuments(): Array<DocumentView> {
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 15dfb255a..123ff679b 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -101,7 +101,6 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     @computed
     get drawnPaths() {
-        trace();
         // parse data from server
         let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1)
         let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
@@ -111,10 +110,10 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
                     tool={strokeData.tool} deleteCallback={this.removeLine} />)
             return paths;
         }, [] as JSX.Element[]);
-        return [<svg className={`inkingCanvas-paths-markers`}  >
+        return [<svg className={`inkingCanvas-paths-markers`} key="Markers"  >
             {paths.filter(path => path.props.tool == InkTool.Highlighter)}
         </svg>,
-        <svg className={`inkingCanvas-paths-ink`}  >
+        <svg className={`inkingCanvas-paths-ink`} key="Pens" >
             {paths.filter(path => path.props.tool != InkTool.Highlighter)}
         </svg>];
     }
@@ -122,11 +121,10 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     render() {
         let svgCanvasStyle = InkingControl.Instance.selectedTool != InkTool.None ? "canSelect" : "noSelect";
 
-        trace();
         return (
             <div className="inkingCanvas" >
                 <svg className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
-                {this.props.children}
+                {(this.props.children as any)() /* bcz: is there a better way to know that children is a function? */}
                 {this.drawnPaths}
             </div >
         )
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index 48adce4d8..987f3cb6c 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -76,14 +76,6 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
         }
     }
 
-    protected getCursors(): CursorEntry[] {
-        let doc = this.props.Document;
-        let id = CurrentUserUtils.id;
-        let cursors = doc.GetList<CursorEntry>(KeyStore.Cursors, []);
-        let notMe = cursors.filter(entry => entry.Data[0][0] !== id);
-        return id ? notMe : [];
-    }
-
     @undoBatch
     @action
     protected drop(e: Event, de: DragManager.DropEvent): boolean {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index ef60aa672..eb20b3100 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,4 +1,4 @@
-import { computed, reaction, runInAction } from "mobx";
+import { computed, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../../fields/Document";
 import { FieldWaiting } from "../../../../fields/Field";
@@ -71,7 +71,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
 
     @computed
     get uniqueConnections() {
-        return DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
+        let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
             let srcViews = this.documentAnchors(connection.a);
             let targetViews = this.documentAnchors(connection.b);
             let possiblePairs: { a: Document, b: Document, }[] = [];
@@ -90,13 +90,14 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
             })
             return drawnPairs
         }, [] as { a: Document, b: Document, l: Document[] }[]);
+        return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
     }
 
     render() {
         return (
             <div className="collectionfreeformlinksview-container">
                 <svg className="collectionfreeformlinksview-svgCanvas">
-                    {this.uniqueConnections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />)}
+                    {this.uniqueConnections}
                 </svg>
                 {this.props.children}
             </div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
new file mode 100644
index 000000000..19382e66f
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -0,0 +1,115 @@
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { FieldWaiting } from "../../../../fields/Field";
+import { KeyStore } from "../../../../fields/KeyStore";
+import { TextField } from "../../../../fields/TextField";
+import { DragManager } from "../../../util/DragManager";
+import { Transform } from "../../../util/Transform";
+import { undoBatch } from "../../../util/UndoManager";
+import { InkingCanvas } from "../../InkingCanvas";
+import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
+import { DocumentContentsView } from "../../nodes/DocumentContentsView";
+import { DocumentViewProps } from "../../nodes/DocumentView";
+import { COLLECTION_BORDER_WIDTH } from "../CollectionView";
+import { CollectionViewBase, CollectionViewProps, CursorEntry } from "../CollectionViewBase";
+import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
+import "./CollectionFreeFormView.scss";
+import { MarqueeView } from "./MarqueeView";
+import React = require("react");
+import v5 = require("uuid/v5");
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
+
+@observer
+export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
+    protected getCursors(): CursorEntry[] {
+        let doc = this.props.Document;
+        let id = CurrentUserUtils.id;
+        let cursors = doc.GetList<CursorEntry>(KeyStore.Cursors, []);
+        let notMe = cursors.filter(entry => entry.Data[0][0] !== id);
+        return id ? notMe : [];
+    }
+
+    private crosshairs?: HTMLCanvasElement;
+    drawCrosshairs = (backgroundColor: string) => {
+        if (this.crosshairs) {
+            let c = this.crosshairs;
+            let ctx = c.getContext('2d');
+            if (ctx) {
+                ctx.fillStyle = backgroundColor;
+                ctx.fillRect(0, 0, 20, 20);
+
+                ctx.fillStyle = "black";
+                ctx.lineWidth = 0.5;
+
+                ctx.beginPath();
+
+                ctx.moveTo(10, 0);
+                ctx.lineTo(10, 8);
+
+                ctx.moveTo(10, 20);
+                ctx.lineTo(10, 12);
+
+                ctx.moveTo(0, 10);
+                ctx.lineTo(8, 10);
+
+                ctx.moveTo(20, 10);
+                ctx.lineTo(12, 10);
+
+                ctx.stroke();
+
+                // ctx.font = "10px Arial";
+                // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10);
+            }
+        }
+    }
+    @computed
+    get sharedCursors() {
+        return this.getCursors().map(entry => {
+            if (entry.Data.length > 0) {
+                let id = entry.Data[0][0];
+                let email = entry.Data[0][1];
+                let point = entry.Data[1];
+                this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22")
+                return (
+                    <div
+                        key={id}
+                        style={{
+                            position: "absolute",
+                            transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`,
+                            zIndex: 10000,
+                            transformOrigin: 'center center',
+                        }}
+                    >
+                        <canvas
+                            ref={(el) => { if (el) this.crosshairs = el }}
+                            width={20}
+                            height={20}
+                            style={{
+                                position: 'absolute',
+                                width: "20px",
+                                height: "20px",
+                                opacity: 0.5,
+                                borderRadius: "50%",
+                                border: "2px solid black"
+                            }}
+                        />
+                        <p
+                            style={{
+                                fontSize: 14,
+                                color: "black",
+                                // fontStyle: "italic",
+                                marginLeft: -12,
+                                marginTop: 4
+                            }}
+                        >{email[0].toUpperCase()}</p>
+                    </div>
+                );
+            }
+        })
+    }
+
+    render() {
+        return this.sharedCursors;
+    }
+}
\ 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 ae1c775e6..60fb95ff5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../../fields/Document";
 import { FieldWaiting } from "../../../../fields/Field";
@@ -18,6 +18,8 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
+import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
+import { PreviewCursor } from "./PreviewCursor";
 
 @observer
 export class CollectionFreeFormView extends CollectionViewBase {
@@ -60,7 +62,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
     @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
     @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
     @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
-    @computed get isAnnotationOverlay() { return this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
+    @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
     @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
     @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
     @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
@@ -97,13 +99,15 @@ export class CollectionFreeFormView extends CollectionViewBase {
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) {
+        if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) && this.props.active()) {
             document.removeEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointerup", this.onPointerUp);
             this._lastX = this.DownX = e.pageX;
             this._lastY = this.DownY = e.pageY;
+            if (this.props.isSelected())
+                e.stopPropagation();
         }
     }
 
@@ -134,7 +138,6 @@ export class CollectionFreeFormView extends CollectionViewBase {
     onPointerWheel = (e: React.WheelEvent): void => {
         this.props.select(false);
         e.stopPropagation();
-        e.preventDefault();
         let coefficient = 1000;
 
         if (e.ctrlKey) {
@@ -265,50 +268,13 @@ export class CollectionFreeFormView extends CollectionViewBase {
     getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
     getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY);
     noScaling = () => 1;
-
-
-    private crosshairs?: HTMLCanvasElement;
-    drawCrosshairs = (backgroundColor: string) => {
-        if (this.crosshairs) {
-            let c = this.crosshairs;
-            let ctx = c.getContext('2d');
-            if (ctx) {
-                ctx.fillStyle = backgroundColor;
-                ctx.fillRect(0, 0, 20, 20);
-
-                ctx.fillStyle = "black";
-                ctx.lineWidth = 0.5;
-
-                ctx.beginPath();
-
-                ctx.moveTo(10, 0);
-                ctx.lineTo(10, 8);
-
-                ctx.moveTo(10, 20);
-                ctx.lineTo(10, 12);
-
-                ctx.moveTo(0, 10);
-                ctx.lineTo(8, 10);
-
-                ctx.moveTo(20, 10);
-                ctx.lineTo(12, 10);
-
-                ctx.stroke();
-
-                // ctx.font = "10px Arial";
-                // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10);
-            }
-        }
-    }
+    childViews = () => this.views;
 
     render() {
         let [dx, dy] = [this.centeringShiftX, this.centeringShiftY];
 
         const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0);
         const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0);
-        // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX;
-        // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY;
-        // console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY));
 
         return (
             <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
@@ -316,60 +282,22 @@ export class CollectionFreeFormView extends CollectionViewBase {
                 onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel}
                 style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} ref={this.createDropTarget}>
                 <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
-                    addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
+                    addDocument={this.addDocument} removeDocument={this.props.removeDocument}
                     getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
-                    <div className="collectionfreeformview" ref={this._canvasRef}
-                        style={{ transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}>
-                        {this.backgroundView}
-                        <CollectionFreeFormLinksView {...this.props}>
-                            <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
-                                {this.views}
-                            </InkingCanvas>
-                        </CollectionFreeFormLinksView>
-                        {super.getCursors().map(entry => {
-                            if (entry.Data.length > 0) {
-                                let id = entry.Data[0][0];
-                                let email = entry.Data[0][1];
-                                let point = entry.Data[1];
-                                this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22")
-                                return (
-                                    <div
-                                        key={id}
-                                        style={{
-                                            position: "absolute",
-                                            transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`,
-                                            zIndex: 10000,
-                                            transformOrigin: 'center center',
-                                        }}
-                                    >
-                                        <canvas
-                                            ref={(el) => { if (el) this.crosshairs = el }}
-                                            width={20}
-                                            height={20}
-                                            style={{
-                                                position: 'absolute',
-                                                width: "20px",
-                                                height: "20px",
-                                                opacity: 0.5,
-                                                borderRadius: "50%",
-                                                border: "2px solid black"
-                                            }}
-                                        />
-                                        <p
-                                            style={{
-                                                fontSize: 14,
-                                                color: "black",
-                                                // fontStyle: "italic",
-                                                marginLeft: -12,
-                                                marginTop: 4
-                                            }}
-                                        >{email[0].toUpperCase()}</p>
-                                    </div>
-                                );
-                            }
-                        })}
-                    </div>
-                    {this.overlayView}
+                    <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox}
+                        getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} >
+                        <div className="collectionfreeformview" ref={this._canvasRef}
+                            style={{ transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}>
+                            {this.backgroundView}
+                            <CollectionFreeFormLinksView {...this.props}>
+                                <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
+                                    {this.childViews}
+                                </InkingCanvas>
+                            </CollectionFreeFormLinksView>
+                            <CollectionFreeFormRemoteCursors {...this.props} />
+                        </div>
+                        {this.overlayView}
+                    </PreviewCursor>
                 </MarqueeView>
             </div>
         );
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 2b0e7d228..20132a4b1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../../fields/Document";
 import { FieldWaiting } from "../../../../fields/Field";
@@ -21,7 +21,6 @@ interface MarqueeViewProps {
     activeDocuments: () => Document[];
     selectDocuments: (docs: Document[]) => void;
     removeDocument: (doc: Document) => boolean;
-    addLiveTextDocument: (doc: Document) => void;
 }
 
 @observer
@@ -77,10 +76,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     onPointerUp = (e: PointerEvent): void => {
         this.cleanupInteractions(true);
         this._visible = false;
+        let mselect = this.marqueeSelect();
         if (!e.shiftKey) {
-            SelectionManager.DeselectAll();
+            SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
         }
-        this.props.selectDocuments(this.marqueeSelect());
+        this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
     }
 
     intersectRect(r1: { left: number, top: number, width: number, height: number },
@@ -184,17 +184,17 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         return selection;
     }
 
-    render() {
+    @computed
+    get marqueeDiv() {
         let p = this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
         let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
-        return <div className="marqueeView" onPointerDown={this.onPointerDown}>
-            <PreviewCursor container={this.props.container} addLiveTextDocument={this.props.addLiveTextDocument}
-                getContainerTransform={this.props.getContainerTransform} getTransform={this.props.getTransform} >
-                {this.props.children}
-                {!this._visible ? (null) :
-                    <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />}
+        return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />
+    }
 
-            </PreviewCursor>
+    render() {
+        return <div className="marqueeView" onPointerDown={this.onPointerDown}>
+            {this.props.children}
+            {!this._visible ? (null) : this.marqueeDiv}
         </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
index 877de8846..93c98f7b0 100644
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
@@ -1,4 +1,4 @@
-import { action, observable, trace } from "mobx";
+import { action, observable, trace, computed, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../../fields/Document";
 import { Documents } from "../../../documents/Documents";
@@ -6,6 +6,7 @@ import { Transform } from "../../../util/Transform";
 import { CollectionFreeFormView } from "./CollectionFreeFormView";
 import "./PreviewCursor.scss";
 import React = require("react");
+import { interfaceDeclaration } from "babel-types";
 
 
 export interface PreviewCursorProps {
@@ -23,27 +24,29 @@ export class PreviewCursor extends React.Component<PreviewCursorProps>  {
     @observable public DownX: number = 0;
     @observable public DownY: number = 0;
     _showOnUp: boolean = false;
-    public _previewDivRef = React.createRef<HTMLDivElement>();
 
     @action
     cleanupInteractions = () => {
         document.removeEventListener("pointerup", this.onPointerUp, true);
+        document.removeEventListener("pointermove", this.onPointerMove, true);
     }
 
     @action
     onPointerDown = (e: React.PointerEvent) => {
-        this._visible = false;
-        document.removeEventListener("keypress", this.onKeyPress, false);
-        this._showOnUp = true;
-        this._lastX = this.DownX = e.pageX;
-        this._lastY = this.DownY = e.pageY;
-        document.addEventListener("pointerup", this.onPointerUp, true);
-        document.addEventListener("pointermove", this.onPointerMove, true);
+        if (e.button == 0 && this.props.container.props.active()) {
+            document.removeEventListener("keypress", this.onKeyPress, false);
+            this._showOnUp = true;
+            this.DownX = e.pageX;
+            this.DownY = e.pageY;
+            document.addEventListener("pointerup", this.onPointerUp, true);
+            document.addEventListener("pointermove", this.onPointerMove, true);
+        }
     }
     @action
     onPointerMove = (e: PointerEvent): void => {
         if (Math.abs(this.DownX - e.clientX) > 4 || Math.abs(this.DownY - e.clientY) > 4) {
             this._showOnUp = false;
+            this._visible = false;
         }
     }
 
@@ -51,6 +54,8 @@ export class PreviewCursor extends React.Component<PreviewCursorProps>  {
     onPointerUp = (e: PointerEvent): void => {
         if (this._showOnUp) {
             document.addEventListener("keypress", this.onKeyPress, false);
+            this._lastX = this.DownX;
+            this._lastY = this.DownY;
             this._visible = true;
         }
         this.cleanupInteractions();
@@ -72,25 +77,43 @@ export class PreviewCursor extends React.Component<PreviewCursorProps>  {
             e.stopPropagation();
         }
     }
-    //when focus is lost, this will remove the preview cursor
-    @action
-    onBlur = (): void => {
-        this._visible = false;
+
+    getPoint = () => this.props.getContainerTransform().transformPoint(this._lastX, this._lastY);
+    getVisible = () => this._visible;
+    setVisible = (v: boolean) => {
+        this._visible = v;
         document.removeEventListener("keypress", this.onKeyPress, false);
     }
-
     render() {
-        //get local position and place cursor there!
-        let p = this.props.getContainerTransform().transformPoint(this._lastX, this._lastY);
-        if (this._visible && this._previewDivRef.current)
-            this._previewDivRef.current!.focus();
-        trace();
         return (
-            <div className="previewCursorView" onBlur={this.onBlur} onPointerDown={this.onPointerDown}>
+            <div className="previewCursorView" onPointerDown={this.onPointerDown}>
                 {this.props.children}
-                {!this._visible ? (null) :
-                    <div className="previewCursor" tabIndex={0} ref={this._previewDivRef} id="previewCursor" style={{ transform: `translate(${p[0]}px, ${p[1]}px)` }}>I</div>}
+                <PreviewCursorPrompt setVisible={this.setVisible} getPoint={this.getPoint} getVisible={this.getVisible} />
             </div>
         )
     }
+}
+
+export interface PromptProps {
+    getPoint: () => number[];
+    getVisible: () => boolean;
+    setVisible: (v: boolean) => void;
+}
+
+@observer
+export class PreviewCursorPrompt extends React.Component<PromptProps> {
+    private _promptRef = React.createRef<HTMLDivElement>();
+
+    //when focus is lost, this will remove the preview cursor
+    @action onBlur = (): void => this.props.setVisible(false);
+
+    render() {
+        let p = this.props.getPoint();
+        if (this.props.getVisible() && this._promptRef.current)
+            this._promptRef.current.focus();
+        return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._promptRef}
+            style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, opacity: this.props.getVisible() ? 1 : 0 }}>
+            I
+        </div >;
+    }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2

