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

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

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
new file mode 100644
index 000000000..4f28f43eb
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -0,0 +1,56 @@
+import { computed } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { FieldWaiting } from "../../../../fields/Field";
+import { KeyStore } from "../../../../fields/KeyStore";
+import { Utils } from "../../../../Utils";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { DocumentView } from "../../nodes/DocumentView";
+import { CollectionViewProps } from "../CollectionViewBase";
+import "./CollectionFreeFormLinksView.scss";
+import React = require("react");
+import v5 = require("uuid/v5");
+import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
+
+@observer
+export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
+
+    documentAnchors(view: DocumentView) {
+        let equalViews = [view];
+        let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document);
+        if (containerDoc && containerDoc != FieldWaiting && containerDoc instanceof Document) {
+            equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype() as Document)
+        }
+        return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == this.props.Document);
+    }
+
+    @computed
+    get uniqueConnections() {
+        return DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
+            let srcViews = this.documentAnchors(connection.a);
+            let targetViews = this.documentAnchors(connection.b);
+            let possiblePairs: { a: Document, b: Document, }[] = [];
+            srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document })));
+            possiblePairs.map(possiblePair => {
+                if (!drawnPairs.reduce((found, drawnPair) => {
+                    let match = (possiblePair.a == drawnPair.a && possiblePair.b == drawnPair.b);
+                    if (match) {
+                        if (!drawnPair.l.reduce((found, link) => found || link.Id == connection.l.Id, false))
+                            drawnPair.l.push(connection.l);
+                    }
+                    return match || found;
+                }, false)) {
+                    drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] as Document[] });
+                }
+            })
+            return drawnPairs
+        }, [] as { a: Document, b: Document, l: Document[] }[]);
+    }
+
+    render() {
+        return (
+            <svg className="collectionfreeformlinksview-svgCanvas">
+                {this.uniqueConnections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />)}
+            </svg>);
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 6e993fb5817e8ddce756396e53883a42530f52bb Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 29 Mar 2019 18:49:22 -0400
Subject: brushes mostly working - some problems with cycles.

---
 src/client/northstar/dash-fields/HistogramField.ts | 17 +++++++-
 src/client/northstar/dash-nodes/HistogramBox.tsx   | 27 ++++++++----
 .../dash-nodes/HistogramBoxPrimitives.tsx          | 14 ++++---
 src/client/northstar/operations/BaseOperation.ts   |  6 ++-
 .../northstar/operations/HistogramOperation.ts     | 22 +++++-----
 .../CollectionFreeFormLinksView.tsx                | 49 +++++++++++++++++++++-
 src/fields/KeyStore.ts                             |  1 +
 7 files changed, 107 insertions(+), 29 deletions(-)

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

diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index 00912c595..1929f8dcd 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -16,7 +16,8 @@ export class HistogramField extends BasicField<HistogramOperation> {
     }
 
     toString(): string {
-        return JSON.stringify(this.Data);
+        let omitted = this.omitKeys(this.Data, ['Links', 'BrushLinks']);
+        return JSON.stringify(omitted);
     }
 
     Copy(): Field {
@@ -27,10 +28,22 @@ export class HistogramField extends BasicField<HistogramOperation> {
         return `new HistogramField("${this.Data}")`;
     }
 
+    omitKeys(obj: any, keys: any) {
+        var dup: any = {};
+        for (var key in obj) {
+            if (keys.indexOf(key) == -1) {
+                dup[key] = obj[key];
+            }
+        }
+        return dup;
+    }
+
     ToJson(): { type: Types, data: string, _id: string } {
+        let omitted = this.omitKeys(this.Data, ['Links', 'BrushLinks']);
         return {
             type: Types.HistogramOp,
-            data: JSON.stringify(this.Data),
+
+            data: JSON.stringify(omitted),
             _id: this.Id
         }
     }
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index b464e125c..9976ff6ad 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -1,5 +1,5 @@
 import React = require("react")
-import { action, computed, observable, reaction, runInAction } from "mobx";
+import { action, computed, observable, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import Measure from "react-measure";
 import { FieldWaiting, Opt } from "../../../fields/Field";
@@ -8,7 +8,7 @@ import { KeyStore } from "../../../fields/KeyStore";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
 import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
-import { AggregateBinRange, AggregateFunction, BinRange, Catalog, DoubleValueAggregateResult, HistogramResult } from "../../northstar/model/idea/idea";
+import { AggregateBinRange, AggregateFunction, BinRange, Catalog, DoubleValueAggregateResult, HistogramResult, Result } from "../../northstar/model/idea/idea";
 import { ModelHelpers } from "../../northstar/model/ModelHelpers";
 import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
 import { SizeConverter } from "../../northstar/utils/SizeConverter";
@@ -39,10 +39,10 @@ export class HistogramBox extends React.Component<FieldViewProps> {
     @observable public HistoOp: HistogramOperation = HistogramOperation.Empty;
     @observable public VisualBinRanges: VisualBinRange[] = [];
     @observable public ValueRange: number[] = [];
+    @observable public HistogramResult: HistogramResult = new HistogramResult();
     @observable public SizeConverter: SizeConverter = new SizeConverter();
 
-    @computed get createOperationParamsCache() { return this.HistoOp.CreateOperationParameters(); }
-    @computed get HistogramResult() { return this.HistoOp ? this.HistoOp.Result as HistogramResult : undefined; }
+    @computed get createOperationParamsCache() { trace(); return this.HistoOp.CreateOperationParameters(); }
     @computed get BinRanges() { return this.HistogramResult ? this.HistogramResult.binRanges : undefined; }
     @computed get ChartType() {
         return !this.BinRanges ? ChartType.SinglePoint : this.BinRanges[0] instanceof AggregateBinRange ?
@@ -123,12 +123,21 @@ export class HistogramBox extends React.Component<FieldViewProps> {
             this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
                 this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty;
                 if (this.HistoOp != HistogramOperation.Empty) {
-                    reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []),
-                        (docs: Document[]) => {
+                    this.HistoOp.YieldResult = (r: Result) => action(() => this.HistogramResult = r as HistogramResult)();
+                    reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+                    reaction(() => this.props.doc.GetList(KeyStore.BrushingDocs, []).length,
+                        () => {
+                            let brushingDocs = this.props.doc.GetList(KeyStore.BrushingDocs, [] as Document[]);
                             var availableColors = StyleConstants.BRUSH_COLORS.map(c => c);
-                            docs.map((brush, i) => brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length]));
-                            this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...docs);
-                            //this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs)
+                            let proto = this.props.doc.GetPrototype() as Document;
+                            let brushingLinks = brushingDocs.map((brush, i) => {
+                                // brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length]);
+                                let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
+                                if (!brushed || brushed.length < 2)
+                                    return undefined;
+                                return { l: brush, b: brushed[0].Id == proto.Id ? brushed[1] : brushed[0] }
+                            }).filter(x => x != undefined) as { l: Document, b: Document }[];
+                            this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingLinks);
                         }, { fireImmediately: true });
                     reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
                 }
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
index 648070241..c97acb064 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -1,5 +1,5 @@
 import React = require("react")
-import { computed, observable, runInAction, reaction } from "mobx";
+import { computed, observable, runInAction, reaction, untracked, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Utils as DashUtils } from '../../../Utils';
 import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
@@ -20,7 +20,7 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
     private get histoOp() { return this.props.HistoBox.HistoOp; }
     private get renderDimension() { return this.props.HistoBox.SizeConverter.RenderDimension; }
     componentDidMount() {
-        reaction(() => this.props.HistoBox.HistogramResult, () => this._selectedPrims.length = 0);
+        reaction(() => this.props.HistoBox.HistoOp.FilterString, () => this._selectedPrims.length = this.histoOp.FilterModels.length = 0);
     }
     @observable _selectedPrims: HistogramBinPrimitive[] = [];
     @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
@@ -30,10 +30,10 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
         let histoResult = this.props.HistoBox.HistogramResult;
         if (!histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length)
             return (null);
+        trace();
         let allBrushIndex = ModelHelpers.AllBrushIndex(histoResult);
         return Object.keys(histoResult.bins).reduce((prims, key) => {
             let drawPrims = new HistogramBinPrimitiveCollection(histoResult!.bins![key], this.props.HistoBox);
-
             let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex,
                 ModelHelpers.GetBinFilterModel(histoResult!.bins![key], allBrushIndex, histoResult!, this.histoOp.X, this.histoOp.Y));
             drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(bp =>
@@ -327,6 +327,7 @@ export class HistogramBinPrimitiveCollection {
     }
 
     private baseColorFromBrush(brush: Brush): number {
+        let bc = StyleConstants.BRUSH_COLORS;
         if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) {
             return StyleConstants.HIGHLIGHT_COLOR;
         }
@@ -336,9 +337,12 @@ export class HistogramBinPrimitiveCollection {
         else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) {
             return 0x00ff00;
         }
-        else if (this.histoOp.BrushColors.length > 0) {
-            return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length];
+        else if (bc.length > 0) {
+            return bc[brush.brushIndex! % bc.length];
         }
+        // else if (this.histoOp.BrushColors.length > 0) {
+        //     return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length];
+        // }
         return StyleConstants.HIGHLIGHT_COLOR;
     }
 }
\ No newline at end of file
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
index 94e0849af..21d1db749 100644
--- a/src/client/northstar/operations/BaseOperation.ts
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -10,7 +10,8 @@ export abstract class BaseOperation {
 
     @observable public Error: string = "";
     @observable public OverridingFilters: FilterModel[] = [];
-    @observable public Result?: Result = undefined;
+    //@observable 
+    public Result?: Result = undefined;
     @observable public ComputationStarted: boolean = false;
     public OperationReference?: OperationReference = undefined;
 
@@ -45,8 +46,11 @@ export abstract class BaseOperation {
     }
 
 
+    public YieldResult: ((result: Result) => void) | undefined;
     @action
     public SetResult(result: Result): void {
+        if (this.YieldResult)
+            this.YieldResult(result);
         this.Result = result;
     }
 
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index fa51e2a8c..48bad73df 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -1,4 +1,4 @@
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace } from "mobx";
 import { Document } from "../../../fields/Document";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { ColumnAttributeModel } from "../core/attribute/AttributeModel";
@@ -16,12 +16,13 @@ import { BaseOperation } from "./BaseOperation";
 import { KeyStore } from "../../../fields/KeyStore";
 import { HistogramField } from "../dash-fields/HistogramField";
 import { FieldWaiting } from "../../../fields/Field";
+import { StyleConstants } from "../utils/StyleContants";
 
 
 export class HistogramOperation extends BaseOperation implements IBaseFilterConsumer, IBaseFilterProvider {
     @observable public FilterOperand: FilterOperand = FilterOperand.AND;
     @observable public Links: Document[] = [];
-    @observable public BrushLinks: Document[] = [];
+    @observable public BrushLinks: { l: Document, b: Document }[] = [];
     @observable public BrushColors: number[] = [];
     @observable public Normalization: number = -1;
     @observable public FilterModels: FilterModel[] = [];
@@ -69,16 +70,15 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
 
     @computed
     public get BrushString(): string[] {
+        trace();
         let brushes: string[] = [];
         this.BrushLinks.map(brushLink => {
-            let brusherDoc = brushLink.Get(KeyStore.LinkedFromDocs);
-            if (brusherDoc && brusherDoc != FieldWaiting && brusherDoc instanceof Document) {
-                let brushHistogram = brusherDoc.GetT(KeyStore.Data, HistogramField);
-                if (brushHistogram && brushHistogram != FieldWaiting) {
-                    let filterModels: FilterModel[] = [];
-                    let brush = FilterModel.GetFilterModelsRecursive(brushHistogram!.Data, new Set<IBaseFilterProvider>(), filterModels, false)
-                    brushes.push(brush);
-                }
+            let brusherDoc = brushLink.b;
+            let brushHistogram = brusherDoc.GetT(KeyStore.Data, HistogramField);
+            if (brushHistogram && brushHistogram != FieldWaiting) {
+                let filterModels: FilterModel[] = [];
+                let brush = FilterModel.GetFilterModelsRecursive(brushHistogram!.Data, new Set<IBaseFilterProvider>(), filterModels, false)
+                brushes.push(brush);
             }
         });
         return brushes;
@@ -137,7 +137,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
 
     @action
     public async Update(): Promise<void> {
-        this.BrushColors = this.BrushLinks.map(e => e.GetNumber(KeyStore.BackgroundColor, 0));
+        //this.BrushColors = this.BrushLinks.map(e => e.l.GetNumber(KeyStore.BackgroundColor, 0));
         return super.Update();
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 4f28f43eb..8dbf80533 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,4 +1,4 @@
-import { computed } from "mobx";
+import { computed, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../../fields/Document";
 import { FieldWaiting } from "../../../../fields/Field";
@@ -11,10 +11,57 @@ import "./CollectionFreeFormLinksView.scss";
 import React = require("react");
 import v5 = require("uuid/v5");
 import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
+import { ListField } from "../../../../fields/ListField";
+import { TextField } from "../../../../fields/TextField";
+import { StyleConstants } from "../../../northstar/utils/StyleContants";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
 
+    componentDidMount() {
+        reaction(() => {
+            return DocumentManager.Instance.getAllDocumentViews(this.props.Document).map(dv => dv.props.Document.GetNumber(KeyStore.X, 0))
+        }, () => {
+            let views = DocumentManager.Instance.getAllDocumentViews(this.props.Document);
+            for (let i = 0; i < views.length; i++) {
+                for (let j = i + 1; j < views.length; j++) {
+                    let srcDoc = views[j].props.Document;
+                    let dstDoc = views[i].props.Document;
+                    let x1 = srcDoc.GetNumber(KeyStore.X, 0);
+                    let x1w = srcDoc.GetNumber(KeyStore.Width, 0);
+                    let x2 = dstDoc.GetNumber(KeyStore.X, 0);
+                    let x2w = dstDoc.GetNumber(KeyStore.Width, 0);
+                    if (Math.abs(x1 + x1w - x2) < 20 || Math.abs(x2 + x2w - x1) < 20) {
+                        let linkDoc: Document = new Document();
+                        dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) =>
+                            srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => {
+                                linkDoc.Set(KeyStore.Title, new TextField("New Brush"));
+                                linkDoc.Set(KeyStore.LinkDescription, new TextField(""));
+                                linkDoc.Set(KeyStore.LinkTags, new TextField("Default"));
+                                linkDoc.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[0]);
+
+                                let dstTarg = (protoDest ? protoDest : dstDoc);
+                                let srcTarg = (protoSrc ? protoSrc : srcDoc);
+                                linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField);
+                                dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) })
+                                srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) })
+                            }))
+                        )
+                    } else {
+                        dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) =>
+                            srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => {
+
+                                let dstTarg = (protoDest ? protoDest : dstDoc);
+                                let srcTarg = (protoSrc ? protoSrc : srcDoc);
+                                dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField<Document>).Data.length = 0 })
+                                srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField<Document>).Data.length = 0 })
+                            }))
+                        )
+                    }
+                }
+            }
+        });
+    }
     documentAnchors(view: DocumentView) {
         let equalViews = [view];
         let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document);
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index aa0b9ce92..1f039e592 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -29,6 +29,7 @@ export namespace KeyStore {
     export const Caption = new Key("Caption");
     export const ActiveWorkspace = new Key("ActiveWorkspace");
     export const DocumentText = new Key("DocumentText");
+    export const BrushingDocs = new Key("BrushingDocs");
     export const LinkedToDocs = new Key("LinkedToDocs");
     export const LinkedFromDocs = new Key("LinkedFromDocs");
     export const LinkDescription = new Key("LinkDescription");
-- 
cgit v1.2.3-70-g09d2


From a2e4321eb23bedda9b78f012d3eb061045c5b33c Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 29 Mar 2019 22:50:49 -0400
Subject: improved brushing

---
 src/client/northstar/dash-nodes/HistogramBox.tsx   |  5 +-
 src/client/northstar/operations/BaseOperation.ts   |  5 +-
 .../CollectionFreeFormLinksView.tsx                | 56 ++++++++++------------
 3 files changed, 29 insertions(+), 37 deletions(-)

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

diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 6bf2697fc..65d917fd3 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -36,7 +36,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
     @observable public HistoOp: HistogramOperation = HistogramOperation.Empty;
     @observable public VisualBinRanges: VisualBinRange[] = [];
     @observable public ValueRange: number[] = [];
-    @observable public HistogramResult: HistogramResult = new HistogramResult();
+    @computed public get HistogramResult(): HistogramResult { return this.HistoOp.Result as HistogramResult; }
     @observable public SizeConverter: SizeConverter = new SizeConverter();
 
     @computed get createOperationParamsCache() { trace(); return this.HistoOp.CreateOperationParameters(); }
@@ -120,7 +120,6 @@ export class HistogramBox extends React.Component<FieldViewProps> {
             this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
                 this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty;
                 if (this.HistoOp != HistogramOperation.Empty) {
-                    this.HistoOp.YieldResult = (r: Result) => action(() => this.HistogramResult = r as HistogramResult)();
                     reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
                     reaction(() => this.props.doc.GetList(KeyStore.BrushingDocs, []).length,
                         () => {
@@ -128,7 +127,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                             var availableColors = StyleConstants.BRUSH_COLORS.map(c => c);
                             let proto = this.props.doc.GetPrototype() as Document;
                             let brushingLinks = brushingDocs.map((brush, i) => {
-                                // brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length]);
+                                brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length]);
                                 let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
                                 if (!brushed || brushed.length < 2)
                                     return undefined;
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
index 21d1db749..f545b2c58 100644
--- a/src/client/northstar/operations/BaseOperation.ts
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -11,7 +11,7 @@ export abstract class BaseOperation {
     @observable public Error: string = "";
     @observable public OverridingFilters: FilterModel[] = [];
     //@observable 
-    public Result?: Result = undefined;
+    @observable public Result?: Result = undefined;
     @observable public ComputationStarted: boolean = false;
     public OperationReference?: OperationReference = undefined;
 
@@ -46,11 +46,8 @@ export abstract class BaseOperation {
     }
 
 
-    public YieldResult: ((result: Result) => void) | undefined;
     @action
     public SetResult(result: Result): void {
-        if (this.YieldResult)
-            this.YieldResult(result);
         this.Result = result;
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 8dbf80533..2dcf7fbbe 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -3,17 +3,15 @@ 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 { Utils } from "../../../../Utils";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DocumentView } from "../../nodes/DocumentView";
 import { CollectionViewProps } from "../CollectionViewBase";
 import "./CollectionFreeFormLinksView.scss";
+import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
 import React = require("react");
 import v5 = require("uuid/v5");
-import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
-import { ListField } from "../../../../fields/ListField";
-import { TextField } from "../../../../fields/TextField";
-import { StyleConstants } from "../../../northstar/utils/StyleContants";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
@@ -31,36 +29,34 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
                     let x1w = srcDoc.GetNumber(KeyStore.Width, 0);
                     let x2 = dstDoc.GetNumber(KeyStore.X, 0);
                     let x2w = dstDoc.GetNumber(KeyStore.Width, 0);
-                    if (Math.abs(x1 + x1w - x2) < 20 || Math.abs(x2 + x2w - x1) < 20) {
-                        let linkDoc: Document = new Document();
-                        dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) =>
-                            srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => {
-                                linkDoc.Set(KeyStore.Title, new TextField("New Brush"));
-                                linkDoc.Set(KeyStore.LinkDescription, new TextField(""));
-                                linkDoc.Set(KeyStore.LinkTags, new TextField("Default"));
-                                linkDoc.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[0]);
-
-                                let dstTarg = (protoDest ? protoDest : dstDoc);
-                                let srcTarg = (protoSrc ? protoSrc : srcDoc);
+                    dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) =>
+                        srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => {
+                            let dstTarg = (protoDest ? protoDest : dstDoc);
+                            let srcTarg = (protoSrc ? protoSrc : srcDoc);
+                            let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => {
+                                let bdocs = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
+                                return (bdocs[0] == dstTarg && bdocs[1] == srcTarg) || (bdocs[0] == srcTarg && bdocs[1] == dstTarg)
+                            });
+                            let brushAction = (field: ListField<Document>) => {
+                                let found = findBrush(field);
+                                if (found != -1)
+                                    field.Data.splice(found, 1);
+                            };
+                            if (Math.abs(x1 + x1w - x2) < 20 || Math.abs(x2 + x2w - x1) < 20) {
+                                let linkDoc: Document = new Document();
+                                linkDoc.SetText(KeyStore.Title, "Histogram Brush");
+                                linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title);
                                 linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField);
-                                dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) })
-                                srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) })
-                            }))
-                        )
-                    } else {
-                        dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) =>
-                            srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => {
 
-                                let dstTarg = (protoDest ? protoDest : dstDoc);
-                                let srcTarg = (protoSrc ? protoSrc : srcDoc);
-                                dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField<Document>).Data.length = 0 })
-                                srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField<Document>).Data.length = 0 })
-                            }))
-                        )
-                    }
+                                brushAction = (field: ListField<Document>) => (findBrush(field) == -1) && field.Data.push(linkDoc);
+                            }
+                            dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction);
+                            srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction);
+                        }
+                        )))
                 }
             }
-        });
+        })
     }
     documentAnchors(view: DocumentView) {
         let equalViews = [view];
-- 
cgit v1.2.3-70-g09d2


From 74e95e596d9876e90c294ade30df3d666d167139 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 29 Mar 2019 23:48:34 -0400
Subject: fixed brush colors.

---
 src/client/northstar/dash-fields/HistogramField.ts             |  2 +-
 src/client/northstar/dash-nodes/HistogramBox.tsx               | 10 +++-------
 src/client/northstar/operations/HistogramOperation.ts          |  2 +-
 .../collectionFreeForm/CollectionFreeFormLinksView.tsx         | 10 ++++++----
 4 files changed, 11 insertions(+), 13 deletions(-)

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

diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index ae83ea5ba..4a557e123 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -25,7 +25,7 @@ export class HistogramField extends BasicField<HistogramOperation> {
         return dup;
     }
     toString(): string {
-        return JSON.stringify(this.omitKeys(this.Data, ['Links', 'BrushLinks', 'Result']));
+        return JSON.stringify(this.omitKeys(this.Data, ['Links', 'BrushLinks', 'Result', 'BrushColors']));
     }
 
     Copy(): Field {
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 65d917fd3..a968def96 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -124,16 +124,12 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                     reaction(() => this.props.doc.GetList(KeyStore.BrushingDocs, []).length,
                         () => {
                             let brushingDocs = this.props.doc.GetList(KeyStore.BrushingDocs, [] as Document[]);
-                            var availableColors = StyleConstants.BRUSH_COLORS.map(c => c);
                             let proto = this.props.doc.GetPrototype() as Document;
-                            let brushingLinks = brushingDocs.map((brush, i) => {
-                                brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length]);
+                            this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => {
+                                brush.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length]);
                                 let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
-                                if (!brushed || brushed.length < 2)
-                                    return undefined;
                                 return { l: brush, b: brushed[0].Id == proto.Id ? brushed[1] : brushed[0] }
-                            }).filter(x => x != undefined) as { l: Document, b: Document }[];
-                            this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingLinks);
+                            }));
                         }, { fireImmediately: true });
                     reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
                 }
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index 4689cb233..e63de1632 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -116,7 +116,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
 
     @action
     public async Update(): Promise<void> {
-        //this.BrushColors = this.BrushLinks.map(e => e.l.GetNumber(KeyStore.BackgroundColor, 0));
+        this.BrushColors = this.BrushLinks.map(e => e.l.GetNumber(KeyStore.BackgroundColor, 0));
         return super.Update();
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 2dcf7fbbe..f793b3a49 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -26,16 +26,18 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
                     let srcDoc = views[j].props.Document;
                     let dstDoc = views[i].props.Document;
                     let x1 = srcDoc.GetNumber(KeyStore.X, 0);
-                    let x1w = srcDoc.GetNumber(KeyStore.Width, 0);
+                    let x1w = srcDoc.GetNumber(KeyStore.Width, -1);
                     let x2 = dstDoc.GetNumber(KeyStore.X, 0);
-                    let x2w = dstDoc.GetNumber(KeyStore.Width, 0);
+                    let x2w = dstDoc.GetNumber(KeyStore.Width, -1);
+                    if (x1w < 0 || x2w < 0)
+                        continue;
                     dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) =>
                         srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => {
                             let dstTarg = (protoDest ? protoDest : dstDoc);
                             let srcTarg = (protoSrc ? protoSrc : srcDoc);
                             let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => {
                                 let bdocs = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
-                                return (bdocs[0] == dstTarg && bdocs[1] == srcTarg) || (bdocs[0] == srcTarg && bdocs[1] == dstTarg)
+                                return (bdocs.length == 0 || (bdocs[0] == dstTarg && bdocs[1] == srcTarg) || (bdocs[0] == srcTarg && bdocs[1] == dstTarg))
                             });
                             let brushAction = (field: ListField<Document>) => {
                                 let found = findBrush(field);
@@ -48,7 +50,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
                                 linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title);
                                 linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField);
 
-                                brushAction = (field: ListField<Document>) => (findBrush(field) == -1) && field.Data.push(linkDoc);
+                                brushAction = brushAction = (field: ListField<Document>) => (findBrush(field) == -1) && field.Data.push(linkDoc);
                             }
                             dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction);
                             srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction);
-- 
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/CollectionFreeFormLinksView.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 2ac86b53d28abb8be2f3abd501b801e543973e28 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 31 Mar 2019 22:45:43 -0400
Subject: restructured inkingCanvas and linksViews

---
 src/client/views/InkingCanvas.scss                 | 164 +++------------------
 src/client/views/InkingCanvas.tsx                  | 121 +++++++--------
 src/client/views/InkingControl.scss                | 135 +++++++++++++++++
 src/client/views/InkingControl.tsx                 |   3 +-
 .../CollectionFreeFormLinksView.scss               |   3 +
 .../CollectionFreeFormLinksView.tsx                |   9 +-
 .../collectionFreeForm/CollectionFreeFormView.scss |  10 --
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  10 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   6 +-
 src/client/views/nodes/PDFBox.tsx                  |  10 +-
 10 files changed, 233 insertions(+), 238 deletions(-)
 create mode 100644 src/client/views/InkingControl.scss

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

diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
index e79b146b9..6d7821cd2 100644
--- a/src/client/views/InkingCanvas.scss
+++ b/src/client/views/InkingCanvas.scss
@@ -1,149 +1,25 @@
 @import "global_variables";
-.inking-canvas {
+
+.inkingCanvas-paths, .inkingCanvas-paths-none {
     position: absolute;
     top: -50000px;
-    left: -50000px; // z-index: 99; //overlays ink on top of everything
-    svg {
-        position:absolute;
-        width: 100000px;
-        height: 100000px;
-        .highlight {
-            mix-blend-mode: multiply;
-        }
-    }
+    left: -50000px;
+    width: 100000px;
+    height: 100000px;
+    //z-index: 10000; // z-index: 99; //overlays ink on top of everything but that messes up blending and events don't propagate down to nested collections
+    .highlight {
+        mix-blend-mode: multiply;
+    }
+    .inkingCanvas-children {
+      transform: translate(50000px, 50000px);
+      pointer-events: none;
+    }
+    cursor:"crosshair";
+    pointer-events: auto;
+    
+}
+.inkingCanvas-paths-none {
+    pointer-events: none;
+    cursor: "arrow";
 }
 
-.inking-control {
-    position: absolute;
-    left: 70px;
-    bottom: 70px;
-    margin: 0;
-    padding: 0;
-    display: flex;
-    label,
-    input,
-    option {
-        font-size: 12px;
-    }
-    input[type="range"] {
-        -webkit-appearance: none;
-        background-color: transparent;
-        vertical-align: middle;
-        margin-top: 8px;
-        &:focus {
-            outline: none;
-        }
-        &::-webkit-slider-runnable-track {
-            width: 100%;
-            height: 3px;
-            border-radius: 1.5px;
-            cursor: pointer;
-            background: $intermediate-color;
-        }
-        &::-webkit-slider-thumb {
-            height: 12px;
-            width: 12px;
-            border: 1px solid $intermediate-color;
-            border-radius: 6px;
-            background: $light-color;
-            cursor: pointer;
-            -webkit-appearance: none;
-            margin-top: -4px;
-        }
-        &::-moz-range-track {
-            width: 100%;
-            height: 3px;
-            border-radius: 1.5px;
-            cursor: pointer;
-            background: $light-color;
-        }
-        &::-moz-range-thumb {
-            height: 12px;
-            width: 12px;
-            border: 1px solid $intermediate-color;
-            border-radius: 6px;
-            background: $light-color;
-            cursor: pointer;
-            -webkit-appearance: none;
-            margin-top: -4px;
-        }
-    }
-    input[type="text"] {
-        border: none;
-        padding: 0 0px;
-        background: transparent;
-        color: $dark-color;
-        font-size: 12px;
-        margin-top: 4px;
-    }
-    .ink-panel {
-        margin: 6px 12px 6px 0;
-        height: 30px;
-        vertical-align: middle;
-        line-height: 36px;
-        padding: 0 10px;
-        color: $intermediate-color;
-        &:first {
-            margin-top: 0;
-        }
-    }
-    .ink-tools {
-        display: flex;
-        background-color: transparent;
-        border-radius: 0;
-        padding: 0;
-        button {
-            height: 36px;
-            padding: 0px;
-            padding-bottom: 3px;
-            margin-left: 10px;
-            background-color: transparent;
-            color: $intermediate-color;
-        }
-        button:hover {
-            transform: scale(1.15);
-        }
-    }
-    .ink-size {
-        display: flex;
-        justify-content: space-between;
-        input[type="text"] {
-            width: 42px;
-        }
-        >* {
-            margin-right: 6px;
-            &:last-child {
-                margin-right: 0;
-            }
-        }
-    }
-    .ink-color {
-        display: flex;
-        position: relative;
-        padding-right: 0;
-        label {
-            margin-right: 6px;
-        }
-        .ink-color-display {
-            border-radius: 11px;
-            width: 22px;
-            height: 22px;
-            margin-top: 6px;
-            cursor: pointer;
-            text-align: center; //   span {
-            //     color: $light-color;
-            //     font-size: 8px;
-            //     user-select: none;
-            //   }
-        }
-        .ink-color-picker {
-            background-color: $light-color;
-            border-radius: 5px;
-            padding: 12px;
-            position: absolute;
-            bottom: 36px;
-            left: -3px;
-            box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 8fb74ec48..a971264b9 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -57,51 +57,62 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     }
 
     @action
-    handleMouseDown = (e: React.PointerEvent): void => {
-        if (e.button != 0 ||
+    onPointerDown = (e: React.PointerEvent): void => {
+        this._isDrawing = false;
+        if (e.button != 0 || e.altKey || e.ctrlKey ||
             InkingControl.Instance.selectedTool === InkTool.None) {
             return;
         }
-        e.stopPropagation()
-        if (InkingControl.Instance.selectedTool === InkTool.Eraser) {
-            return
-        }
-        const point = this.relativeCoordinatesForEvent(e);
-
-        // start the new line, saves a uuid to represent the field of the stroke
-        this._idGenerator = Utils.GenerateGuid();
-        this.inkData.set(this._idGenerator,
-            {
-                pathData: [point],
-                color: InkingControl.Instance.selectedColor,
-                width: InkingControl.Instance.selectedWidth,
-                tool: InkingControl.Instance.selectedTool,
-                page: this.props.Document.GetNumber(KeyStore.CurPage, -1)
-            });
+        document.addEventListener("pointermove", this.onPointerMove, true);
+        document.addEventListener("pointerup", this.onPointerUp, true);
+        e.stopPropagation();
+
         this._isDrawing = true;
+        if (InkingControl.Instance.selectedTool != InkTool.Eraser) {
+            const point = this.relativeCoordinatesForEvent(e.clientX, e.clientY);
+
+            // start the new line, saves a uuid to represent the field of the stroke
+            this._idGenerator = Utils.GenerateGuid();
+            this.inkData.set(this._idGenerator,
+                {
+                    pathData: [point],
+                    color: InkingControl.Instance.selectedColor,
+                    width: InkingControl.Instance.selectedWidth,
+                    tool: InkingControl.Instance.selectedTool,
+                    page: this.props.Document.GetNumber(KeyStore.CurPage, -1)
+                });
+        }
     }
 
-    @action
-    handleMouseMove = (e: React.PointerEvent): void => {
-        if (!this._isDrawing ||
-            InkingControl.Instance.selectedTool === InkTool.None) {
-            return;
+    onPointerUp = (e: PointerEvent): void => {
+        if (this._isDrawing) {
+            document.removeEventListener("pointermove", this.onPointerMove);
+            document.removeEventListener("pointerup", this.onPointerUp);
+            this._isDrawing = false;
+            e.stopPropagation();
         }
-        e.stopPropagation()
-        if (InkingControl.Instance.selectedTool === InkTool.Eraser) {
-            return
-        }
-        const point = this.relativeCoordinatesForEvent(e);
+    }
 
-        // add points to new line as it is being drawn
-        let data = this.inkData;
-        let strokeData = data.get(this._idGenerator);
-        if (strokeData) {
-            strokeData.pathData.push(point);
-            data.set(this._idGenerator, strokeData);
+    @action
+    onPointerMove = (e: PointerEvent): void => {
+        if (this._isDrawing) {
+            e.stopPropagation()
+            e.preventDefault();
+            if (InkingControl.Instance.selectedTool === InkTool.Eraser) {
+                return
+            }
+            const point = this.relativeCoordinatesForEvent(e.clientX, e.clientY);
+
+            // add points to new line as it is being drawn
+            let data = this.inkData;
+            let strokeData = data.get(this._idGenerator);
+            if (strokeData) {
+                strokeData.pathData.push(point);
+                data.set(this._idGenerator, strokeData);
+            }
+
+            this.inkData = data;
         }
-
-        this.inkData = data;
     }
 
     @action
@@ -109,8 +120,8 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
         this._isDrawing = false;
     }
 
-    relativeCoordinatesForEvent = (e: React.MouseEvent): { x: number, y: number } => {
-        let [x, y] = this.props.getScreenTransform().transformPoint(e.clientX, e.clientY);
+    relativeCoordinatesForEvent = (ex: number, ey: number): { x: number, y: number } => {
+        let [x, y] = this.props.getScreenTransform().transformPoint(ex, ey);
         x += InkingCanvas.InkOffset;
         y += InkingCanvas.InkOffset;
         return { x, y };
@@ -124,32 +135,9 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     }
 
     render() {
-        // styling for cursor
-        let canvasStyle = {};
-        if (InkingControl.Instance.selectedTool === InkTool.None) {
-            canvasStyle = { pointerEvents: "none" };
-        } else {
-            canvasStyle = { pointerEvents: "auto", cursor: "crosshair" };
-        }
-
-        // get data from server
-        // let inkField = this.props.Document.GetT(KeyStore.Ink, InkField);
-        // if (!inkField || inkField == FieldWaiting) {
-        //     return (<div className="inking-canvas" style={canvasStyle}
-        //         onMouseDown={this.handleMouseDown} onMouseMove={this.handleMouseMove} >
-        //         <svg>
-        //         </svg>
-        //     </div >)
-        // }
-
-        let lines = this.inkData;
-
         // parse data from server
-        let paths: Array<JSX.Element> = []
         let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1)
-        Array.from(lines).map(item => {
-            let id = item[0];
-            let strokeData = item[1];
+        let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
             if (strokeData.page == -1 || strokeData.page == curPage)
                 paths.push(<InkingStroke key={id} id={id}
                     line={strokeData.pathData}
@@ -157,13 +145,16 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
                     width={strokeData.width}
                     tool={strokeData.tool}
                     deleteCallback={this.removeLine} />)
-        })
+            return paths;
+        }, [] as JSX.Element[]);
+        let svgCanvasStyle = InkingControl.Instance.selectedTool == InkTool.None ? "-none" : "";
 
         return (
-            <div className="inking-canvas" style={canvasStyle} onPointerDown={this.handleMouseDown} onPointerMove={this.handleMouseMove} >
-                <svg>
+            <div className="inkingCanvas" >
+                <svg className={`inkingCanvas-paths${svgCanvasStyle}`} onPointerDown={this.onPointerDown} >
                     {paths}
                 </svg>
+                {this.props.children}
             </div >
         )
     }
diff --git a/src/client/views/InkingControl.scss b/src/client/views/InkingControl.scss
new file mode 100644
index 000000000..0d8fd8784
--- /dev/null
+++ b/src/client/views/InkingControl.scss
@@ -0,0 +1,135 @@
+@import "global_variables";
+.inking-control {
+    position: absolute;
+    left: 70px;
+    bottom: 70px;
+    margin: 0;
+    padding: 0;
+    display: flex;
+    label,
+    input,
+    option {
+        font-size: 12px;
+    }
+    input[type="range"] {
+        -webkit-appearance: none;
+        background-color: transparent;
+        vertical-align: middle;
+        margin-top: 8px;
+        &:focus {
+            outline: none;
+        }
+        &::-webkit-slider-runnable-track {
+            width: 100%;
+            height: 3px;
+            border-radius: 1.5px;
+            cursor: pointer;
+            background: $intermediate-color;
+        }
+        &::-webkit-slider-thumb {
+            height: 12px;
+            width: 12px;
+            border: 1px solid $intermediate-color;
+            border-radius: 6px;
+            background: $light-color;
+            cursor: pointer;
+            -webkit-appearance: none;
+            margin-top: -4px;
+        }
+        &::-moz-range-track {
+            width: 100%;
+            height: 3px;
+            border-radius: 1.5px;
+            cursor: pointer;
+            background: $light-color;
+        }
+        &::-moz-range-thumb {
+            height: 12px;
+            width: 12px;
+            border: 1px solid $intermediate-color;
+            border-radius: 6px;
+            background: $light-color;
+            cursor: pointer;
+            -webkit-appearance: none;
+            margin-top: -4px;
+        }
+    }
+    input[type="text"] {
+        border: none;
+        padding: 0 0px;
+        background: transparent;
+        color: $dark-color;
+        font-size: 12px;
+        margin-top: 4px;
+    }
+    .ink-panel {
+        margin: 6px 12px 6px 0;
+        height: 30px;
+        vertical-align: middle;
+        line-height: 36px;
+        padding: 0 10px;
+        color: $intermediate-color;
+        &:first {
+            margin-top: 0;
+        }
+    }
+    .ink-tools {
+        display: flex;
+        background-color: transparent;
+        border-radius: 0;
+        padding: 0;
+        button {
+            height: 36px;
+            padding: 0px;
+            padding-bottom: 3px;
+            margin-left: 10px;
+            background-color: transparent;
+            color: $intermediate-color;
+        }
+        button:hover {
+            transform: scale(1.15);
+        }
+    }
+    .ink-size {
+        display: flex;
+        justify-content: space-between;
+        input[type="text"] {
+            width: 42px;
+        }
+        >* {
+            margin-right: 6px;
+            &:last-child {
+                margin-right: 0;
+            }
+        }
+    }
+    .ink-color {
+        display: flex;
+        position: relative;
+        padding-right: 0;
+        label {
+            margin-right: 6px;
+        }
+        .ink-color-display {
+            border-radius: 11px;
+            width: 22px;
+            height: 22px;
+            margin-top: 6px;
+            cursor: pointer;
+            text-align: center; //   span {
+            //     color: $light-color;
+            //     font-size: 8px;
+            //     user-select: none;
+            //   }
+        }
+        .ink-color-picker {
+            background-color: $light-color;
+            border-radius: 5px;
+            padding: 12px;
+            position: absolute;
+            bottom: 36px;
+            left: -3px;
+            box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index eb2172d03..c1519dff8 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -2,10 +2,9 @@ import { observable, action, computed } from "mobx";
 
 import { CirclePicker, ColorResult } from 'react-color'
 import React = require("react");
-import "./InkingCanvas.scss"
 import { InkTool } from "../../fields/InkField";
 import { observer } from "mobx-react";
-import "./InkingCanvas.scss"
+import "./InkingControl.scss"
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
index 9af0df7f5..4341c82f7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
@@ -4,4 +4,7 @@
     width: 20000px; 
     height: 20000px;
     pointer-events: none;
+  }
+  .collectionfreeformlinksview-container {
+    pointer-events: none;
   }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index ea392eaea..ef60aa672 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -94,9 +94,12 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
 
     render() {
         return (
-            <svg className="collectionfreeformlinksview-svgCanvas">
-                {this.uniqueConnections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />)}
+            <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} />)}
+                </svg>
                 {this.props.children}
-            </svg>);
+            </div>
+        );
     }
 }
\ 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 c979b27a9..81d21d89a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -7,9 +7,6 @@
   width: 100%;
   height: 100%;
   transform-origin: left top;
-  .inking-canvas {
-    transform-origin: 50000px 50000px;
-  }
 }
 .collectionfreeformview-container {
   .collectionfreeformview > .jsx-parser {
@@ -18,9 +15,6 @@
     width: 100%;
   }
 
-  .inking-canvas {
-    transform-origin: 50000px 50000px;
-  }
   //nested freeform views
   // .collectionfreeformview-container {
     // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),
@@ -48,10 +42,6 @@
     background: $light-color-secondary;
   }
 
-  .inking-canvas {
-    transform-origin: 50000px 50000px;
-  }
-
   opacity: 0.99;
   border: 0px solid transparent;
   border-radius: $border-radius;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cdc5bdfb0..ae1c775e6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -314,16 +314,18 @@ export class CollectionFreeFormView extends CollectionViewBase {
             <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
                 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}>
+                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}
                     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} />
+                        <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];
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7f951864e..d52b662bd 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { computed, trace, reaction, runInAction, observable } from "mobx";
+import { computed } from "mobx";
 import { observer } from "mobx-react";
 import { KeyStore } from "../../../fields/KeyStore";
 import { NumberField } from "../../../fields/NumberField";
@@ -6,8 +6,7 @@ import { Transform } from "../../util/Transform";
 import { DocumentView, DocumentViewProps } from "./DocumentView";
 import "./DocumentView.scss";
 import React = require("react");
-import { Document } from "../../../fields/Document";
-import { DocumentManager } from "../../util/DocumentManager";
+import { thisExpression } from "babel-types";
 
 
 @observer
@@ -75,6 +74,7 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
                 transformOrigin: "left top",
                 transform: this.transform,
+                pointerEvents: "all",
                 width: this.width,
                 height: this.height,
                 position: "absolute",
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index e273b0b4f..28a1f9757 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -87,7 +87,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
     @observable private _interactive: boolean = false;
     @observable private _loaded: boolean = false;
 
-    @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, -1); }
+    @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, 1); }
     @computed private get thumbnailPage() { return this.props.doc.GetNumber(KeyStore.ThumbnailPage, -1); }
 
     componentDidMount() {
@@ -435,8 +435,6 @@ export class PDFBox extends React.Component<FieldViewProps> {
     @computed
     get pdfContent() {
         let page = this.curPage;
-        if (page == 0)
-            page = 1;
         const renderHeight = 2400;
         let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField);
         let xf = this.props.doc.GetNumber(KeyStore.NativeHeight, 0) / renderHeight;
@@ -463,10 +461,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
         return [
             this._pageInfo.area.filter(() => this._pageInfo.area).map((element: any) => element),
             this._currAnno.map((element: any) => element),
-            <div key="pdfBox-contentShell">
-                {this.pdfContent}
-                {proxy}
-            </div>
+            this.pdfContent,
+            proxy
         ];
     }
 
-- 
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/CollectionFreeFormLinksView.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

