From 22d1499703ddb20d0f937364085e46fefcc663da Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 12 Apr 2020 19:23:21 -0400
Subject: added nudging of documents

---
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 5 +++++
 1 file changed, 5 insertions(+)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index f9f5f449c..3503af442 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -59,6 +59,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         }
         return undefined;
     }
+    nudge = (x: number, y: number) => {
+        this.layoutDoc.x = NumCast(this.layoutDoc.x) + x;
+        this.layoutDoc.y = NumCast(this.layoutDoc.y) + y;
+    }
 
     contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
     panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth?.());
@@ -93,6 +97,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
             {!this.props.fitToBox ?
                 <DocumentView {...this.props}
+                    nudge={this.nudge}
                     dragDivName={"collectionFreeFormDocumentView-container"}
                     ContentScaling={this.contentScaling}
                     ScreenToLocalTransform={this.getTransform}
-- 
cgit v1.2.3-70-g09d2


From b235c94614141752c98d24a395682a02297d9e6a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 13 Apr 2020 11:15:59 -0400
Subject: cleaned up rootSelected() to work with templates.  adjusted nudge to
 favor moving documents over panning collection

---
 src/client/views/DocComponent.tsx                       |  4 ++--
 src/client/views/PreviewCursor.tsx                      | 14 +++++---------
 src/client/views/collections/CollectionSubView.tsx      |  2 +-
 src/client/views/collections/CollectionView.tsx         |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx       | 17 +++++++++++------
 .../collections/collectionFreeForm/MarqueeView.tsx      |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx      |  4 ++--
 src/client/views/nodes/DocumentView.tsx                 | 16 +++++++++-------
 8 files changed, 32 insertions(+), 29 deletions(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index b1bd4191c..f19f9308a 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -53,7 +53,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor:
         // key where data is stored
         @computed get fieldKey() { return this.props.fieldKey; }
 
-        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && ((this.props.Document.forceActive && this.props.rootSelected(outsideReaction)) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
+        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
@@ -114,7 +114,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
 
         whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
         active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
-            ((this.props.Document.forceActive && this.props.rootSelected(outsideReaction)) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+            (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
         annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None || (this.props.Document.isBackground && this.props.active()) ||
             (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
     }
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index ecebadd2a..92ba13d2f 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -13,7 +13,7 @@ export class PreviewCursor extends React.Component<{}> {
     static _getTransform: () => Transform;
     static _addLiveTextDoc: (doc: Doc) => void;
     static _addDocument: (doc: Doc) => boolean;
-    static _nudge: (x: number, y: number) => void;
+    static _nudge: (x: number, y: number) => boolean;
     @observable static _clickPoint = [0, 0];
     @observable public static Visible = false;
     constructor(props: any) {
@@ -91,17 +91,13 @@ export class PreviewCursor extends React.Component<{}> {
             }
         } else if (PreviewCursor.Visible) {
             if (e.key === "ArrowRight") {
-                PreviewCursor._nudge?.(1 * (e.shiftKey ? 2 : 1), 0);
-                e.stopPropagation();
+                PreviewCursor._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
             } else if (e.key === "ArrowLeft") {
-                PreviewCursor._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0);
-                e.stopPropagation();
+                PreviewCursor._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
             } else if (e.key === "ArrowUp") {
-                PreviewCursor._nudge?.(0, 1 * (e.shiftKey ? 2 : 1));
-                e.stopPropagation();
+                PreviewCursor._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
             } else if (e.key === "ArrowDown") {
-                PreviewCursor._nudge?.(0, -1 * (e.shiftKey ? 2 : 1));
-                e.stopPropagation();
+                PreviewCursor._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
             }
         }
     }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 6b7c3de56..37cf942c6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -100,7 +100,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         }
 
         rootSelected = (outsideReaction?: boolean) => {
-            return this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument || this.props.Document.forceActive ? this.props.rootSelected(outsideReaction) : false);
+            return this.props.isSelected(outsideReaction) || (this.rootDoc && this.props.rootSelected(outsideReaction));
         }
 
         // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 821840e0b..86df9c080 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -102,7 +102,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         return viewField as any as CollectionViewType;
     }
 
-    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.rootSelected(outsideReaction) && BoolCast(this.props.Document.forceActive)) || this._isChildActive || this.props.renderDepth === 0;
+    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) || this.props.Document.forceActive || this._isChildActive || this.props.renderDepth === 0;
 
     whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7dca19073..a77f09163 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -15,7 +15,7 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
 import { TraceMobx } from "../../../../new_fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnOne, Utils, returnZero } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
@@ -43,6 +43,7 @@ import "./CollectionFreeFormView.scss";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
+import { CollectionViewType } from "../CollectionView";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -860,7 +861,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
             LibraryPath: this.libraryPath,
             FreezeDimensions: this.props.freezeChildDimensions,
             layoutKey: undefined,
-            rootSelected: this.rootSelected,
+            rootSelected: childData ? this.rootSelected : returnFalse,
             dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
             //onClick: undefined, // this.props.onClick,  // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
             onClick: this.onChildClickHandler,
@@ -1105,10 +1106,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
 
     _nudgeTime = 0;
     nudge = action((x: number, y: number) => {
-        this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
-            NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "Ease", true);
-        this._nudgeTime = Date.now();
-        setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document.panTransformType = undefined), 500);
+        if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform) { // bcz: this isn't ideal, but want to try it out...
+            this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
+                NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "Ease", true);
+            this._nudgeTime = Date.now();
+            setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document.panTransformType = undefined), 500);
+            return true;
+        }
+        return false;
     });
     @computed get marqueeView() {
         return <MarqueeView {...this.props} nudge={this.nudge} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 5a6c8573b..9dd3d640b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -31,7 +31,7 @@ interface MarqueeViewProps {
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
     isAnnotationOverlay?: boolean;
-    nudge:(x:number, y:number) => void;
+    nudge: (x: number, y: number) => boolean;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3503af442..e7cb2c015 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -60,8 +60,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return undefined;
     }
     nudge = (x: number, y: number) => {
-        this.layoutDoc.x = NumCast(this.layoutDoc.x) + x;
-        this.layoutDoc.y = NumCast(this.layoutDoc.y) + y;
+        this.props.Document.x = NumCast(this.props.Document.x) + x;
+        this.props.Document.y = NumCast(this.props.Document.y) + y;
     }
 
     contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3784c5c9c..cc3c39154 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -289,13 +289,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let preventDefault = true;
             this.props.Document.isBackground === undefined && this.props.bringToFront(this.props.Document);
             if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
-                const fullScreenAlias = Doc.MakeAlias(this.props.Document);
-                if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
-                    fullScreenAlias.layoutKey = "layout_fullScreen";
+                if (!(e.nativeEvent as any).formattedHandled) {
+                    const fullScreenAlias = Doc.MakeAlias(this.props.Document);
+                    if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
+                        fullScreenAlias.layoutKey = "layout_fullScreen";
+                    }
+                    UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap");
+                    SelectionManager.DeselectAll();
+                    Doc.UnBrushDoc(this.props.Document);
                 }
-                UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap");
-                SelectionManager.DeselectAll();
-                Doc.UnBrushDoc(this.props.Document);
             } else if (this.onClickHandler?.script) {
                 SelectionManager.DeselectAll();
                 const func = () => this.onClickHandler.script.run({
@@ -978,7 +980,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return typeof fallback === "string" ? fallback : "layout";
     }
     rootSelected = (outsideReaction?: boolean) => {
-        return this.isSelected(outsideReaction) || (this.props.Document.forceActive && this.props.rootSelected?.(outsideReaction) ? true : false);
+        return this.isSelected(outsideReaction) || (this.rootDoc && this.props.rootSelected?.(outsideReaction));
     }
     childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
     panelWidth = () => this.props.PanelWidth();
-- 
cgit v1.2.3-70-g09d2


From d5c0ed9a29593583a4180973f854106dc10d4b30 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 15 Apr 2020 02:32:47 -0400
Subject: moved filter to right side of collection.  got rid of back button in
 pivot view - click background.

---
 src/Utils.ts                                       | 10 +++--
 .../views/collections/CollectionTimeView.tsx       | 30 +++++++--------
 src/client/views/collections/CollectionView.scss   |  4 +-
 src/client/views/collections/CollectionView.tsx    | 43 +++++++++++++++-------
 .../views/collections/CollectionViewChromes.scss   |  8 +++-
 .../views/collections/CollectionViewChromes.tsx    | 12 +++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  3 +-
 8 files changed, 69 insertions(+), 43 deletions(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index a8cde0624..58f272ba5 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -475,7 +475,9 @@ export function setupMoveUpEvents(
     e: React.PointerEvent,
     moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean,
     upEvent: (e: PointerEvent) => void,
-    clickEvent: (e: PointerEvent) => void) {
+    clickEvent: (e: PointerEvent) => void,
+    stopPropagation: boolean = true
+) {
     (target as any)._downX = (target as any)._lastX = e.clientX;
     (target as any)._downY = (target as any)._lastY = e.clientY;
 
@@ -499,8 +501,10 @@ export function setupMoveUpEvents(
         document.removeEventListener("pointermove", _moveEvent);
         document.removeEventListener("pointerup", _upEvent);
     };
-    e.stopPropagation();
-    e.preventDefault();
+    if (stopPropagation) {
+        e.stopPropagation();
+        e.preventDefault();
+    }
     document.removeEventListener("pointermove", _moveEvent);
     document.removeEventListener("pointerup", _upEvent);
     document.addEventListener("pointermove", _moveEvent);
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 31b720b81..1760e2419 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -72,8 +72,22 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         }), returnFalse, emptyFunction);
     }
 
+    contentsDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
+            let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
+            if (prevFilterIndex > 0) {
+                prevFilterIndex--;
+                this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
+                this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
+                this.props.Document._prevFilterIndex = prevFilterIndex;
+            } else {
+                this.props.Document._docFilters = new List([]);
+            }
+        }), false);
+    }
+
     @computed get contents() {
-        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }}>
+        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }} onPointerDown={this.contentsDown}>
             <CollectionFreeFormView {...this.props} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
         </div>;
     }
@@ -132,20 +146,6 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             color: "#f1efeb" // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
         };
         return <div className={"pivotKeyEntry"}>
-            <button className="collectionTimeView-backBtn"
-                onClick={action(() => {
-                    let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
-                    if (prevFilterIndex > 0) {
-                        prevFilterIndex--;
-                        this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
-                        this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
-                        this.props.Document._prevFilterIndex = prevFilterIndex;
-                    } else {
-                        this.props.Document._docFilters = new List([]);
-                    }
-                })}>
-                back
-            </button>
             <EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} />
         </div>;
     }
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index a0b73b90f..d43dd387a 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -20,7 +20,7 @@
         top: 55%;
         border: 1px black solid;
         z-index: 2;
-        left: -10px;
+        right: -10px;
     }
     .collectionTimeView-treeView {
         display: flex;
@@ -28,7 +28,7 @@
         width: 200px;
         height: 100%;
         position: absolute;
-        left: 0;
+        right: 0;
         top: 0;
 
         .collectionTimeView-addfacet {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 831691f8c..0a3577bc7 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -13,7 +13,7 @@ import { List } from '../../../new_fields/List';
 import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types';
 import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
-import { Utils, setupMoveUpEvents, returnFalse, returnZero } from '../../../Utils';
+import { Utils, setupMoveUpEvents, returnFalse, returnZero, emptyPath, emptyFunction, returnOne } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
@@ -45,6 +45,7 @@ import { ScriptField, ComputedField } from '../../../new_fields/ScriptField';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { ObjectField } from '../../../new_fields/ObjectField';
 import CollectionMapView from './CollectionMapView';
+import { Transform } from 'prosemirror-transform';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -281,7 +282,6 @@ export class CollectionView extends Touchable<FieldViewProps> {
     set _facetWidth(value) { this.props.Document._facetWidth = value; }
 
     bodyPanelWidth = () => this.props.PanelWidth() - this.facetWidth();
-    getTransform = () => this.props.ScreenToLocalTransform().translate(-this.facetWidth(), 0);
     facetWidth = () => Math.max(0, Math.min(this.props.PanelWidth() - 25, this._facetWidth));
 
     @computed get dataDoc() {
@@ -389,7 +389,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
     onPointerDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
-            this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
+            this._facetWidth = this.props.PanelWidth() - Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
             return false;
         }), returnFalse, action(() => this._facetWidth = this.facetWidth() < 15 ? Math.min(this.props.PanelWidth() - 25, 200) : 0));
     }
@@ -415,27 +415,44 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 <div className="collectionTimeView-addFacet" style={{ width: `${this.facetWidth()}px` }} onPointerDown={e => e.stopPropagation()}>
                     <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
                         <div className="collectionTimeView-button">
-                            <span className="collectionTimeView-span">Facet Filters</span>
                             <FontAwesomeIcon icon={faEdit} size={"lg"} />
+                            <span className="collectionTimeView-span">Facet Filters</span>
                         </div>
                     </Flyout>
                 </div>
                 <div className="collectionTimeView-tree" key="tree">
-                    <CollectionTreeView {...this.props}
+                    <CollectionTreeView
+                        Document={facetCollection}
+                        DataDoc={facetCollection}
+                        fieldKey={`${this.props.fieldKey}-filter`}
                         CollectionView={this}
-                        treeViewHideTitle={true}
+                        ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                        ContainingCollectionView={this.props.ContainingCollectionView}
+                        PanelWidth={this.facetWidth}
+                        PanelHeight={this.props.PanelHeight}
                         NativeHeight={returnZero}
                         NativeWidth={returnZero}
+                        LibraryPath={emptyPath}
+                        rootSelected={this.props.rootSelected}
+                        renderDepth={1}
+                        dropAction={this.props.dropAction}
+                        ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                        addDocTab={returnFalse}
+                        pinToPres={returnFalse}
+                        isSelected={returnFalse}
+                        select={returnFalse}
+                        bringToFront={emptyFunction}
+                        active={this.props.active}
+                        whenActiveChanged={returnFalse}
+                        treeViewHideTitle={true}
+                        ContentScaling={returnOne}
+                        focus={returnFalse}
                         treeViewHideHeaderFields={true}
                         onCheckedClick={this.scriptField!}
                         ignoreFields={this.ignoreFields}
                         annotationsKey={""}
                         dontRegisterView={true}
-                        PanelWidth={this.facetWidth}
-                        DataDoc={facetCollection}
-                        Document={facetCollection}
                         backgroundColor={this.filterBackground}
-                        fieldKey={`${this.props.fieldKey}-filter`}
                         moveDocument={returnFalse}
                         removeDocument={returnFalse}
                         addDocument={returnFalse} />
@@ -461,7 +478,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             }}
             onContextMenu={this.onContextMenu}>
             {this.showIsTagged()}
-            <div style={{ width: `calc(100% - ${this.facetWidth()}px)`, marginLeft: `${this.facetWidth()}px` }}>
+            <div style={{ width: `calc(100% - ${this.facetWidth()}px)` }}>
                 {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
             </div>
             {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d =>
@@ -471,9 +488,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                     :
                     ""))}
             {!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (null) :
-                <div className="collectionTimeView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this.facetWidth()}px, 0px)` }} >
-                    <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
-                </div>
+                <div className="collectionTimeView-dragger" title="library View Dragger" onPointerDown={this.onPointerDown} style={{ right: this.facetWidth() - 10 }} />
             }
             {this.filterView}
         </div>);
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 9529ef525..5203eb55f 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -61,7 +61,8 @@
                 pointer-events: all;
                 // margin-top: 10px;
             }
-            .collectionViewBaseChrome-template {
+            .collectionViewBaseChrome-template,
+            .collectionViewBaseChrome-viewModes {
                 display: grid;
                 background: rgb(238, 238, 238);
                 color:grey;
@@ -69,9 +70,12 @@
                 margin-bottom:auto;
                 margin-left: 5px;
             }
+            .collectionViewBaseChrome-viewModes {
+                margin-left: 25px;
+            }
 
             .collectionViewBaseChrome-viewSpecs {
-                margin-left: 25px;
+                margin-left: 5px;
                 display: grid;
                 
                 .collectionViewBaseChrome-filterIcon {
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index cef85e148..7315d2c4e 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -276,12 +276,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                             title="Collapse collection chrome" onClick={this.toggleCollapse}>
                             <FontAwesomeIcon icon="caret-up" size="2x" />
                         </button>
-                        <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
-                            <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
-                                <FontAwesomeIcon icon="filter" size="2x" />
-                            </div>
-                        </div>
-                        <div className="collectionViewBaseChrome-template" style={{ display: collapsed ? "none" : undefined }}>
+                        <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
                             <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
                                 <div className="commandEntry-drop">
                                     <FontAwesomeIcon icon="bullseye" size="2x"></FontAwesomeIcon>
@@ -303,6 +298,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                                 </select>
                             </div>
                         </div>
+                        <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
+                            <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
+                                <FontAwesomeIcon icon="filter" size="2x" />
+                            </div>
+                        </div>
                         <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}>
                             <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
                                 <div className="commandEntry-drop">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a77f09163..d98fe190c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -907,6 +907,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
 
     onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
         (this.props.Document.onViewDefDivClick as ScriptField)?.script.run({ this: this.props.Document, payload });
+        e.stopPropagation();
     }
     private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
         const { x, y, z } = viewDef;
@@ -991,6 +992,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
                     {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
+                    pointerEvents={this.props.layoutEngine?.() !== undefined ? "none" : undefined}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e7cb2c015..05ad98c43 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -22,6 +22,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     width?: number;
     height?: number;
     jitterRotation: number;
+    pointerEvents?: "none";
     transition?: string;
     fitToBox?: boolean;
 }
@@ -92,7 +93,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 height: this.height,
                 zIndex: this.ZInd,
                 display: this.ZInd === -99 ? "none" : undefined,
-                pointerEvents: this.props.Document.isBackground ? "none" : undefined
+                pointerEvents: this.props.Document.isBackground ? "none" : this.props.pointerEvents
             }} >
 
             {!this.props.fitToBox ?
-- 
cgit v1.2.3-70-g09d2


From d3aebc8404685496673a8611a6d72ff7695ad191 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 18 Apr 2020 13:42:03 -0400
Subject: several pointerevent fixes broken by last change.  added scroll to
 target for webBox links.

---
 src/client/util/DocumentManager.ts                 |  13 ++-
 .../views/collections/CollectionTreeView.tsx       |   1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   6 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   1 -
 src/client/views/nodes/DocumentView.scss           |  10 --
 src/client/views/nodes/DocumentView.tsx            | 118 ++++++++++-----------
 src/client/views/nodes/FormattedTextBoxComment.tsx |   4 +-
 src/client/views/nodes/LinkAnchorBox.scss          |   1 +
 src/client/views/nodes/LinkAnchorBox.tsx           |   2 +
 src/client/views/nodes/LinkBox.tsx                 |   1 -
 src/client/views/nodes/WebBox.tsx                  |  22 ++--
 src/client/views/pdf/Annotation.tsx                |   4 +-
 src/client/views/pdf/PDFViewer.tsx                 |   8 +-
 13 files changed, 100 insertions(+), 91 deletions(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 2d6078cf3..e66e67723 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -156,7 +156,12 @@ export class DocumentManager {
         let annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
         if (annotatedDoc) {
             const first = getFirstDocView(annotatedDoc);
-            if (first) annotatedDoc = first.props.Document;
+            if (first) {
+                annotatedDoc = first.props.Document;
+                if (docView) {
+                    docView.props.focus(annotatedDoc, false);
+                }
+            }
         }
         if (docView) {  // we have a docView already and aren't forced to create a new one ... just focus on the document.  TODO move into view if necessary otherwise just highlight?
             docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish);
@@ -219,9 +224,9 @@ export class DocumentManager {
         if (linkDoc) {
             const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
                 (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
-            const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") : 
-                                    doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number"):
-                (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number"):Cast(linkDoc.anchor1_timecode, "number")));
+            const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
+                doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
+                    (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
             if (target) {
                 const containerDoc = (await Cast(target.annotationOn, Doc)) || target;
                 containerDoc.currentTimecode = targetTimecode;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 510c9924b..5ff8c11ee 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -145,7 +145,6 @@ class TreeView extends React.Component<TreeViewProps> {
         ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)));
     }
 
-    onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
     onPointerEnter = (e: React.PointerEvent): void => {
         this.props.active(true) && Doc.BrushDoc(this.dataDoc);
         if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 02f9bd487..b3409bd57 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -804,7 +804,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
             if (!annotOn) {
                 this.props.focus(doc);
             } else {
-                const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn.height);
+                const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height);
                 const offset = annotOn && (contextHgt / 2 * 96 / 72);
                 this.props.Document.scrollY = NumCast(doc.y) - offset;
             }
@@ -820,7 +820,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
 
             const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
 
-            if (!willZoom) {
+            if (!willZoom && DocumentView._focusHack.length) {
                 Doc.BrushDoc(this.props.Document);
                 !doc.z && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
             } else {
@@ -995,7 +995,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
                     {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
-                    pointerEvents={this.props.layoutEngine?.() !== undefined ? "none" : undefined}
+                    pointerEvents={this.props.layoutEngine?.() !== undefined ? false : undefined}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 05ad98c43..e325a70c0 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -22,7 +22,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     width?: number;
     height?: number;
     jitterRotation: number;
-    pointerEvents?: "none";
     transition?: string;
     fitToBox?: boolean;
 }
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index fc9ee1201..81ae36cc0 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -38,16 +38,6 @@
         display:flex;
         overflow: hidden;
     }
-    .documentView-linkAnchorBoxWrapper {
-        pointer-events: none;
-        position: absolute;
-        transform-origin: top left;
-        width: 100%;
-        height: 100%;
-        top:0;
-        left:0;
-        z-index: 1;
-    }
 
     .documentView-lock {
         width: 20; 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2f22488e4..2bae2fa96 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -80,6 +80,7 @@ export interface DocumentViewProps {
     ContentScaling: () => number;
     PanelWidth: () => number;
     PanelHeight: () => number;
+    pointerEvents?: boolean;
     focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: DocFocusFunc) => void;
     parentActive: (outsideReaction: boolean) => boolean;
     whenActiveChanged: (isActive: boolean) => void;
@@ -994,38 +995,42 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     screenToLocalTransform = () => this.props.ScreenToLocalTransform();
     @computed get contents() {
         TraceMobx();
-        return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
-            ContainingCollectionDoc={this.props.ContainingCollectionDoc}
-            NativeWidth={this.NativeWidth}
-            NativeHeight={this.NativeHeight}
-            Document={this.props.Document}
-            DataDoc={this.props.DataDoc}
-            LayoutDoc={this.props.LayoutDoc}
-            makeLink={this.makeLink}
-            rootSelected={this.rootSelected}
-            dontRegisterView={this.props.dontRegisterView}
-            fitToBox={this.props.fitToBox}
-            LibraryPath={this.props.LibraryPath}
-            addDocument={this.props.addDocument}
-            removeDocument={this.props.removeDocument}
-            moveDocument={this.props.moveDocument}
-            ScreenToLocalTransform={this.screenToLocalTransform}
-            renderDepth={this.props.renderDepth}
-            PanelWidth={this.panelWidth}
-            PanelHeight={this.panelHeight}
-            focus={this.props.focus}
-            parentActive={this.props.parentActive}
-            whenActiveChanged={this.props.whenActiveChanged}
-            bringToFront={this.props.bringToFront}
-            addDocTab={this.props.addDocTab}
-            pinToPres={this.props.pinToPres}
-            backgroundColor={this.props.backgroundColor}
-            ContentScaling={this.childScaling}
-            ChromeHeight={this.chromeHeight}
-            isSelected={this.isSelected}
-            select={this.select}
-            onClick={this.onClickHandler}
-            layoutKey={this.finalLayoutKey} />);
+        return (<>
+            <DocumentContentsView key={1} ContainingCollectionView={this.props.ContainingCollectionView}
+                ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                NativeWidth={this.NativeWidth}
+                NativeHeight={this.NativeHeight}
+                Document={this.props.Document}
+                DataDoc={this.props.DataDoc}
+                LayoutDoc={this.props.LayoutDoc}
+                makeLink={this.makeLink}
+                rootSelected={this.rootSelected}
+                dontRegisterView={this.props.dontRegisterView}
+                fitToBox={this.props.fitToBox}
+                LibraryPath={this.props.LibraryPath}
+                addDocument={this.props.addDocument}
+                removeDocument={this.props.removeDocument}
+                moveDocument={this.props.moveDocument}
+                ScreenToLocalTransform={this.screenToLocalTransform}
+                renderDepth={this.props.renderDepth}
+                PanelWidth={this.panelWidth}
+                PanelHeight={this.panelHeight}
+                focus={this.props.focus}
+                parentActive={this.props.parentActive}
+                whenActiveChanged={this.props.whenActiveChanged}
+                bringToFront={this.props.bringToFront}
+                addDocTab={this.props.addDocTab}
+                pinToPres={this.props.pinToPres}
+                backgroundColor={this.props.backgroundColor}
+                ContentScaling={this.childScaling}
+                ChromeHeight={this.chromeHeight}
+                isSelected={this.isSelected}
+                select={this.select}
+                onClick={this.onClickHandler}
+                layoutKey={this.finalLayoutKey} />
+            {this.anchors}
+        </>
+        );
     }
     linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document);
 
@@ -1049,20 +1054,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get anchors() {
         TraceMobx();
         return this.layoutDoc.presBox ? (null) : DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
-            <div className="documentView-linkAnchorBoxWrapper" key={d[Id]}>
-                <DocumentView {...this.props}
-                    Document={d}
-                    ContainingCollectionView={this.props.ContainingCollectionView}
-                    ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection  Need a better prop for passing the containing document to the LinkAnchorBox
-                    PanelWidth={this.anchorPanelWidth}
-                    PanelHeight={this.anchorPanelHeight}
-                    layoutKey={this.linkEndpoint(d)}
-                    ContentScaling={returnOne}
-                    backgroundColor={returnTransparent}
-                    removeDocument={this.hideLinkAnchor}
-                    LayoutDoc={undefined}
-                />
-            </div>);
+            <DocumentView {...this.props} key={i + 1}
+                Document={d}
+                ContainingCollectionView={this.props.ContainingCollectionView}
+                ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection  Need a better prop for passing the containing document to the LinkAnchorBox
+                PanelWidth={this.anchorPanelWidth}
+                PanelHeight={this.anchorPanelHeight}
+                layoutKey={this.linkEndpoint(d)}
+                ContentScaling={returnOne}
+                backgroundColor={returnTransparent}
+                removeDocument={this.hideLinkAnchor}
+                pointerEvents={false}
+                LayoutDoc={undefined}
+            />);
     }
     @computed get innards() {
         TraceMobx();
@@ -1101,22 +1105,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     SetValue={undoBatch((value: string) => (Doc.GetProto(this.props.DataDoc || this.props.Document)[showTitle] = value) ? true : true)}
                 />
             </div>);
-        return <>
-            {this.anchors}
-            {!showTitle && !showCaption ?
-                this.contents :
-                <div className="documentView-styleWrapper" >
-                    <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? `calc(100% - ${this.chromeHeight()}px)` : "100%", top: showTextTitle ? this.chromeHeight() : undefined }}>
-                        {this.contents}
-                    </div>
-                    {titleView}
-                    {captionView}
+        return !showTitle && !showCaption ?
+            this.contents :
+            <div className="documentView-styleWrapper" >
+                <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? `calc(100% - ${this.chromeHeight()}px)` : "100%", top: showTextTitle ? this.chromeHeight() : undefined }}>
+                    {this.contents}
                 </div>
-            }
-        </>;
+                {titleView}
+                {captionView}
+            </div>;
     }
     @computed get ignorePointerEvents() {
-        return (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || this.props.layoutKey?.includes("layout_key") || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+        return this.props.pointerEvents === false || (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
     }
 
     @observable _animate = 0;
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 35304033f..41df5b3c1 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -16,6 +16,7 @@ import React = require("react");
 import { Docs } from "../../documents/Documents";
 import wiki from "wikijs";
 import { DocumentType } from "../../documents/DocumentTypes";
+import { DocumentView } from "./DocumentView";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -85,8 +86,9 @@ export class FormattedTextBoxComment {
                 const textBox = FormattedTextBoxComment.textBox;
                 if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
                     if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                        textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab":"onRight");
+                        textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
                     } else {
+                        DocumentView._focusHack = [];
                         DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
                             (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
                     }
diff --git a/src/client/views/nodes/LinkAnchorBox.scss b/src/client/views/nodes/LinkAnchorBox.scss
index 24f9c1ea0..710f2178b 100644
--- a/src/client/views/nodes/LinkAnchorBox.scss
+++ b/src/client/views/nodes/LinkAnchorBox.scss
@@ -5,6 +5,7 @@
     height: 15;
     border-radius: 20px;
     user-select: none;
+    pointer-events: all;
 
     .linkAnchorBox-linkCloser {
         position: absolute;
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 13ffc6956..3b1ced815 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -17,6 +17,7 @@ import { LinkEditor } from "../linking/LinkEditor";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { SelectionManager } from "../../util/SelectionManager";
 import { TraceMobx } from "../../../new_fields/util";
+import { DocumentView } from "./DocumentView";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -74,6 +75,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
             anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
             if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
                 this._timeout = setTimeout(action(() => {
+                    DocumentView._focusHack = [];
                     DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "inTab")), false);
                     this._editing = false;
                 }), 300 - (Date.now() - this._lastTap));
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index af4bf420f..740f2ef04 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -17,7 +17,6 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); }
     render() {
         return <div className={`linkBox-container${this.active() ? "-interactive" : ""}`}
-            onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
             style={{ background: this.props.backgroundColor?.(this.props.Document) }} >
 
             <CollectionTreeView {...this.props}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 26e947f5b..510e6be0c 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,6 +1,6 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faStickyNote, faLock, faUnlock } from '@fortawesome/free-solid-svg-icons';
-import { action, computed, observable, trace } from "mobx";
+import { action, computed, observable, trace, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, FieldResult } from "../../../new_fields/Doc";
 import { documentSchema } from "../../../new_fields/documentSchemas";
@@ -13,7 +13,6 @@ import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
-import { SelectionManager } from "../../util/SelectionManager";
 import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
@@ -37,15 +36,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
     @observable private collapsed: boolean = true;
     @observable private url: string = "hello";
+    @observable private _pressX: number = 0;
+    @observable private _pressY: number = 0;
 
     private _longPressSecondsHack?: NodeJS.Timeout;
     private _outerRef = React.createRef<HTMLDivElement>();
     private _iframeRef = React.createRef<HTMLIFrameElement>();
     private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
     private _iframeDragRef = React.createRef<HTMLDivElement>();
-    @observable private _pressX: number = 0;
-    @observable private _pressY: number = 0;
-    private _scrollTop = 0;
+    private _reactionDisposer?: IReactionDisposer;
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
 
     iframeLoaded = action((e: any) => {
@@ -53,6 +52,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         this._iframeRef.current!.contentDocument?.addEventListener('scroll', this.iframeScrolled, false);
         this.layoutDoc.scrollHeight = this._iframeRef.current!.contentDocument?.children?.[0].scrollHeight || 1000;
         this._iframeRef.current!.contentDocument!.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop);
+        this._reactionDisposer?.();
+        this._reactionDisposer = reaction(() => this.layoutDoc.scrollY,
+            (scrollY) => {
+                if (scrollY !== undefined) {
+                    this._outerRef.current!.scrollTop = scrollY;
+                    this.layoutDoc.scrollY = undefined;
+                }
+            },
+            { fireImmediately: true }
+        );
     });
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
     iframedown = (e: PointerEvent) => {
@@ -60,7 +69,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     }
     iframeScrolled = (e: any) => {
         const scroll = (e.target as any)?.children?.[0].scrollTop;
-        this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = this._scrollTop = scroll;
+        this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = scroll;
     }
     async componentDidMount() {
 
@@ -87,6 +96,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     }
 
     componentWillUnmount() {
+        this._reactionDisposer?.();
         document.removeEventListener("pointerup", this.onLongPressUp);
         document.removeEventListener("pointermove", this.onLongPressMove);
         this._iframeRef.current!.contentDocument?.removeEventListener('pointerdown', this.iframedown);
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 71b19f3a6..eaf80d252 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -8,6 +8,7 @@ import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { DocumentManager } from "../../util/DocumentManager";
 import PDFMenu from "./PDFMenu";
 import "./Annotation.scss";
+import { DocumentView } from "../nodes/DocumentView";
 
 interface IAnnotationProps {
     anno: Doc;
@@ -97,7 +98,8 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
         else if (e.button === 0) {
             const annoGroup = await Cast(this.props.document.group, Doc);
             if (annoGroup) {
-                DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation), false,  undefined);
+                DocumentView._focusHack = [];
+                DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation), false, undefined);
                 e.stopPropagation();
             }
         }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 948d2300d..75be08e9f 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -9,7 +9,7 @@ import { List } from "../../../new_fields/List";
 import { makeInterface, createSchema } from "../../../new_fields/Schema";
 import { ScriptField, ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, returnZero } from "../../../Utils";
+import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, returnZero, emptyPath } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { CompiledScript, CompileScript } from "../../util/Scripting";
@@ -607,7 +607,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
 
 
     getCoverImage = () => {
-        if (!this.props.Document[HeightSym]() || !this.props.Document.nativeHeight) {
+        if (!this.props.Document[HeightSym]() || !this.props.Document._nativeHeight) {
             setTimeout((() => {
                 this.Document._height = this.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
                 this.Document._nativeHeight = (this.Document._nativeWidth || 0) * this._coverPath.height / this._coverPath.width;
@@ -632,7 +632,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
 
     @computed get annotationLayer() {
         TraceMobx();
-        return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.Document.nativeHeight), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
+        return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.Document._nativeHeight), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
             {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
                 <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)}
         </div>;
@@ -643,7 +643,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     @computed get overlayLayer() {
         return <div className={`pdfViewer-overlay${InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : ""}`} id="overlay" style={{ transform: `scale(${this._zoomed})` }}>
             <CollectionFreeFormView {...this.props}
-                LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? []}
+                LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? emptyPath}
                 annotationsKey={this.annotationKey}
                 setPreviewCursor={this.setPreviewCursor}
                 PanelHeight={this.panelWidth}
-- 
cgit v1.2.3-70-g09d2


From 9f8e76451b807ed8f51ef32aab9b2255dfccd525 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 18 Apr 2020 18:37:27 -0400
Subject: fixed pointer events for templated PDFs.  changed clicking on a
 document with a rootDocument to pass the event on up

---
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 2 +-
 src/client/views/nodes/DocumentView.tsx                   | 7 +++++--
 src/client/views/pdf/PDFViewer.scss                       | 5 ++---
 src/client/views/pdf/PDFViewer.tsx                        | 4 +++-
 4 files changed, 11 insertions(+), 7 deletions(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e325a70c0..3a7e005ac 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -92,7 +92,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 height: this.height,
                 zIndex: this.ZInd,
                 display: this.ZInd === -99 ? "none" : undefined,
-                pointerEvents: this.props.Document.isBackground ? "none" : this.props.pointerEvents
+                pointerEvents: this.props.Document.isBackground ? "none" : this.props.pointerEvents ? "all" : undefined
             }} >
 
             {!this.props.fitToBox ?
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2bae2fa96..e3013698d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -315,7 +315,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             } else if (this.Document.isLinkButton) {
                 DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
             } else {
-                if ((this.props.Document.onDragStart || (this.props.Document.rootDocument && this.props.Document.isTemplateForField)) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
+                if ((this.props.Document.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                 } else {
                     DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
@@ -480,7 +480,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             return;
         }
-        if (!e.nativeEvent.cancelBubble || this.onClickHandler || this.Document.onDragStart) {
+        if ((!e.nativeEvent.cancelBubble || this.onClickHandler || this.Document.onDragStart) &&
+            // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking
+            !((this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0))) {
             this._downX = e.clientX;
             this._downY = e.clientY;
             if ((this.active || this.Document.onDragStart || this.onClickHandler) &&
@@ -489,6 +491,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 !this.Document.lockedPosition &&
                 !this.Document.inOverlay) {
                 e.stopPropagation(); // events stop at the lowest document that is active.  if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
+
             }
             document.removeEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 26c31a80e..760f64a72 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -57,12 +57,11 @@
         display: inline-block;
         width:100%;
         pointer-events: none;
-    }
-    .pdfViewer-overlay-inking {
-        .collectionfreeformview-container {
+        .collectionFreeFormDocumentView-container {
             pointer-events: all;
         }
     }
+    
     .pdfViewer-annotationLayer {
         position: absolute;
         transform-origin: left top;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index d54390236..10cfaa2f1 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -573,7 +573,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         clipDoc._scrollTop = this.marqueeY();
         const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
         Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
+        clipDoc.rootDocument = targetDoc;
         DocumentView.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined);
+        targetDoc.layoutKey = "layout";
         // const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
         // Doc.GetProto(targetDoc).snipped = this.dataDoc[this.props.fieldKey][Copy]();
         // const snipLayout = Docs.Create.PdfDocument("http://www.msn.com", { title: "snippetView", isTemplateDoc: true, isTemplateForField: "snipped", _fitWidth: true, _width: this.marqueeWidth(), _height: this.marqueeHeight(), _scrollTop: this.marqueeY() });
@@ -642,7 +644,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document._nativeWidth || 0);
     @computed get overlayLayer() {
         return <div className={`pdfViewer-overlay${InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : ""}`} id="overlay"
-            style={{ transform: `scale(${this._zoomed})`, pointerEvents: this.active(false) ? "all" : undefined }}>
+            style={{ transform: `scale(${this._zoomed})` }}>
             <CollectionFreeFormView {...this.props}
                 LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? emptyPath}
                 annotationsKey={this.annotationKey}
-- 
cgit v1.2.3-70-g09d2


From 72f614f023a4bac6a2f0b63a1b1a16e6f4545c7a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 23 Apr 2020 00:53:08 -0400
Subject: fixed browser back after link navigation. simplified menus more.
 fixed jitter mode to be seeded. fixed menu gear for  collections. .

---
 src/client/util/History.ts                         |  6 +++-
 src/client/util/ProsemirrorExampleTransfer.ts      |  4 +--
 src/client/views/DocumentDecorations.tsx           |  6 +++-
 .../views/collections/CollectionDockingView.tsx    |  6 +++-
 .../views/collections/CollectionStackingView.tsx   |  2 +-
 .../views/collections/CollectionTimeView.tsx       |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/CollectionView.tsx    |  4 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 21 +++++------
 src/client/views/nodes/AudioBox.tsx                |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  9 +++--
 src/client/views/nodes/DocumentBox.tsx             |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 41 ++++++++++++++++++++--
 src/client/views/nodes/KeyValueBox.tsx             | 23 ++++++++----
 src/client/views/nodes/LinkAnchorBox.tsx           |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  2 +-
 src/client/views/nodes/ScreenshotBox.tsx           |  2 +-
 src/client/views/nodes/SliderBox.tsx               |  2 +-
 src/client/views/nodes/VideoBox.tsx                |  2 +-
 20 files changed, 103 insertions(+), 39 deletions(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 545e8acb4..2c53d7e52 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -40,8 +40,12 @@ export namespace HistoryUtil {
         // }
     }
 
+    let _lastStatePush = 0;
     export function pushState(state: ParsedUrl) {
-        history.pushState(state, "", createUrl(state));
+        if (Date.now() - _lastStatePush > 1000) {
+            history.pushState(state, "", createUrl(state));
+        }
+        _lastStatePush = Date.now();
     }
 
     export function replaceState(state: ParsedUrl) {
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 42247f177..680f48f70 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -154,7 +154,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
         const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (originalDoc instanceof Doc) {
             const newDoc = Docs.Create.TextDocument("", {
-                title: "", layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
                 x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
             });
             FormattedTextBox.SelectOnLoad = newDoc[Id];
@@ -172,7 +172,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
         const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (force || props.Document._singleLine) {
             const newDoc = Docs.Create.TextDocument("", {
-                title: "", layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
                 x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
             });
             FormattedTextBox.SelectOnLoad = newDoc[Id];
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 43c7751fa..6e699bc68 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -134,7 +134,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @action onSettingsClick = (e: PointerEvent): void => {
         if (e.button === 0 && !e.altKey && !e.ctrlKey) {
             let child = SelectionManager.SelectedDocuments()[0].ContentDiv!.children[0];
-            while (child.children.length) child = child.children[0];
+            while (child.children.length) {
+                const next = Array.from(child.children).find(c => !c.className.includes("collectionViewChrome"));
+                if (next) child = next;
+                else break;
+            }
             simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
         }
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 5e77bc0bb..0d859c3f1 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -29,6 +29,7 @@ import "./CollectionDockingView.scss";
 import { SubCollectionViewProps } from "./CollectionSubView";
 import { DockingViewButtonSelector } from './ParentDocumentSelector';
 import React = require("react");
+import { CollectionViewType } from './CollectionView';
 library.add(faFile);
 const _global = (window /* browser */ || global /* node */) as any;
 
@@ -93,6 +94,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     @undoBatch
     @action
     public OpenFullScreen(docView: DocumentView, libraryPath?: Doc[]) {
+        if (docView.props.Document._viewType === CollectionViewType.Docking && docView.props.Document.layoutKey === "layout") {
+            return MainView.Instance.openWorkspace(docView.props.Document);
+        }
         const document = Doc.MakeAlias(docView.props.Document);
         const newItemStackConfig = {
             type: 'stack',
@@ -771,7 +775,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
 
     addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => {
         SelectionManager.DeselectAll();
-        if (doc.dockingConfig) {
+        if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") {
             return MainView.Instance.openWorkspace(doc);
         } else if (location === "onRight") {
             return CollectionDockingView.AddRightSplit(doc, libraryPath);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index b15649d83..24a3119cc 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -397,7 +397,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         if (!e.isPropagationStopped()) {
             const subItems: ContextMenuProps[] = [];
             subItems.push({ description: `${this.props.Document.fillColumn ? "Variable Size" : "Autosize"} Column`, event: () => this.props.Document.fillColumn = !this.props.Document.fillColumn, icon: "plus" });
-            ContextMenu.Instance.addItem({ description: "Stacking Options ...", subitems: subItems, icon: "eye" });
+            ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" });
         }
     }
 
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 742a818bc..06ebf6d2d 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -102,7 +102,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         layoutItems.push({ description: "Auto Time/Pivot layout", event: () => { doc._forceRenderEngine = undefined; }, icon: "compress-arrows-alt" });
         layoutItems.push({ description: "Sync with presentation", event: () => CollectionTimeView.SyncTimelineToPresentation(doc), icon: "compress-arrows-alt" });
 
-        ContextMenu.Instance.addItem({ description: "Pivot/Time Options ...", subitems: layoutItems, icon: "eye" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
     }
     @computed get _allFacets() {
         const facets = new Set<string>();
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f589c2c76..a052d045c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -711,7 +711,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
             layoutItems.push({ description: (this.props.Document.treeViewPreventOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.props.Document.treeViewPreventOpen = !this.props.Document.treeViewPreventOpen, icon: "paint-brush" });
             layoutItems.push({ description: (this.props.Document.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.treeViewHideHeaderFields = !this.props.Document.treeViewHideHeaderFields, icon: "paint-brush" });
             layoutItems.push({ description: (this.props.Document.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.props.Document.treeViewHideTitle = !this.props.Document.treeViewHideTitle, icon: "paint-brush" });
-            ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
+            ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
         }
         ContextMenu.Instance.addItem({
             description: "Buxton Layout", icon: "eye", event: () => {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ade54f2c9..4f1242c33 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -229,7 +229,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 return newRendition;
             }, false);
 
-            const existing = ContextMenu.Instance.findByDescription("Layout...");
+            const existing = ContextMenu.Instance.findByDescription("Options...");
             const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
             layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
             if (this.props.Document.childLayout instanceof Doc) {
@@ -240,7 +240,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             }
             layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
-            !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
+            !existing && ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
 
             const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
             const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3b5101a4d..55e55e56c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1048,16 +1048,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     onContextMenu = (e: React.MouseEvent) => {
         if (this.props.children && this.props.annotationsKey) return;
-        const layoutItems: ContextMenuProps[] = [];
-
-        layoutItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
-        layoutItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
-        layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
-        layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
-        layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
+        const options = ContextMenu.Instance.findByDescription("Options...");
+        const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+
+        optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+        optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+        optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+        optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
+        optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
-        layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
-        layoutItems.push({
+        optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
+        optionItems.push({
             description: "Import document", icon: "upload", event: ({ x, y }) => {
                 const input = document.createElement("input");
                 input.type = "file";
@@ -1085,7 +1086,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }
         });
 
-        ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
     }
 
     private childViews = () => {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 8f40ea2be..6ff6d1b42 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -162,7 +162,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
         const funcs: ContextMenuProps[] = [];
         funcs.push({ description: (this.layoutDoc.playOnSelect ? "Don't play" : "Play") + " when document selected", event: () => this.layoutDoc.playOnSelect = !this.layoutDoc.playOnSelect, icon: "expand-arrows-alt" });
 
-        ContextMenu.Instance.addItem({ description: "Audio Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
 
     stopRecording = action(() => {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3a7e005ac..615b05e43 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,3 @@
-import anime from "animejs";
 import { computed, IReactionDisposer, observable, reaction, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
@@ -29,8 +28,14 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
     @observable _animPos: number[] | undefined = undefined;
+    random(min: number, max: number) { // min should not be equal to max
+        const mseed = Math.abs(this.X * this.Y);
+        const seed = (mseed * 9301 + 49297) % 233280;
+        var rnd = seed / 233280;
+        return min + rnd * (max - min);
+    }
     get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
-    get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${anime.random(-1, 1) * this.props.jitterRotation}deg)`; }
+    get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${this.random(-1, 1) * this.props.jitterRotation}deg)`; }
     get X() { return this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
     get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
     get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); }
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index ac562f19a..7583aa070 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -45,7 +45,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
         funcs.push({ description: (this.props.Document.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => Doc.GetProto(this.props.Document).excludeCollections = !this.props.Document.excludeCollections, icon: "expand-arrows-alt" });
         funcs.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
 
-        ContextMenu.Instance.addItem({ description: "DocumentBox Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
     @computed get contentDoc() {
         return (this.props.Document.isTemplateDoc || this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document));
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a19ba6506..cab7b965a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -741,7 +741,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const existingOnClick = cm.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
-        onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(this, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
+        onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
         onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 2c89d53d8..1329f7dcf 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
 import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../new_fields/DateField';
-import { DataSym, Doc, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { DataSym, Doc, DocListCastAsync, Field, HeightSym, Opt, WidthSym, DocListCast } from "../../../new_fields/Doc";
 import { documentSchema } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { InkTool } from '../../../new_fields/InkField';
@@ -48,6 +48,7 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './Format
 import React = require("react");
 import { PrefetchProxy } from '../../../new_fields/Proxy';
 import { makeTemplate } from '../../util/DropConverter';
+import { DocumentView } from './DocumentView';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -396,6 +397,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null);
     }
     specificContextMenu = (e: React.MouseEvent): void => {
+        const cm = ContextMenu.Instance;
+
         const funcs: ContextMenuProps[] = [];
         this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" });
         funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
@@ -407,10 +410,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         });
         funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" });
         funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Audio", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" });
         funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
+
+        const highlighting: ContextMenuProps[] = [];
         ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
-            funcs.push({
+            highlighting.push({
                 description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
                     e.stopPropagation();
                     if (FormattedTextBox._highlights.indexOf(option) === -1) {
@@ -421,8 +426,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     this.updateHighlights();
                 }, icon: "expand-arrows-alt"
             }));
+        funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" });
 
         ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+
+        const change = cm.findByDescription("Change Perspective...");
+        const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : [];
+
+        const noteTypesDoc = Cast(Doc.UserDoc().noteTypes, Doc, null);
+        const noteTypes = DocListCast(noteTypesDoc?.data);
+        noteTypes.forEach(note => {
+            changeItems.push({
+                description: StrCast(note.title), event: () => {
+                    Doc.setNativeView(this.props.Document);
+                    DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
+                }, icon: "eye"
+            })
+        });
+        changeItems.push({ description: "FreeForm", event: () => DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" })
+        !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
+
+        const open = cm.findByDescription("Open New Perspective...");
+        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+
+        openItems.push({
+            description: "FreeForm", event: () => {
+                const alias = Doc.MakeAlias(this.rootDoc);
+                DocumentView.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
+                this.props.addDocTab(alias, "onRight");
+            }, icon: "eye"
+        })
+        !open && cm.addItem({ description: "Open New Perspective...", subitems: openItems, icon: "external-link-alt" });
+
     }
 
     recordDictation = () => {
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 7aad6f90e..2970674a2 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -16,6 +16,8 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./KeyValueBox.scss";
 import { KeyValuePair } from "./KeyValuePair";
 import React = require("react");
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
 
 export type KVPScript = {
     script: CompiledScript;
@@ -34,11 +36,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     @observable private _keyInput: string = "";
     @observable private _valueInput: string = "";
     @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
-    get fieldDocToLayout() { return this.props.fieldKey ? FieldValue(Cast(this.props.Document[this.props.fieldKey], Doc)) : this.props.Document; }
-
-    constructor(props: FieldViewProps) {
-        super(props);
-    }
+    get fieldDocToLayout() { return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document; }
 
     @action
     onEnterKey = (e: React.KeyboardEvent): void => {
@@ -234,13 +232,26 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         return new Doc;
     }
 
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const cm = ContextMenu.Instance;
+        const open = cm.findByDescription("Change Perspective...");
+        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+        openItems.push({
+            description: "Default Perspective", event: () => {
+                this.props.addDocTab(this.fieldDocToLayout, "inTab");
+                this.props.addDocTab(this.props.Document, "close");
+            }, icon: "image"
+        });
+        !open && cm.addItem({ description: "Change Perspective...", subitems: openItems, icon: "external-link-alt" });
+    }
+
     render() {
         const dividerDragger = this.splitPercentage === 0 ? (null) :
             <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}>
                 <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} />
             </div>;
 
-        return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel} ref={this._mainCont}>
+        return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel} onContextMenu={this.specificContextMenu} ref={this._mainCont}>
             <table className="keyValueBox-table">
                 <tbody className="keyValueBox-tbody">
                     <tr className="keyValueBox-header">
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 8e3c15908..eb647d0e4 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -109,7 +109,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         funcs.push({ description: "Open Link Editor", event: () => this.openLinkEditor(e), icon: "eye" });
         funcs.push({ description: "Toggle Always Show Link", event: () => this.props.Document.linkDisplay = !this.props.Document.linkDisplay, icon: "eye" });
 
-        ContextMenu.Instance.addItem({ description: "Link Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
 
     render() {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 6db36e43c..3712c648e 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -210,7 +210,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
         pdfUrl && funcs.push({ description: "Copy path", event: () => Utils.CopyText(pdfUrl.url.pathname), icon: "expand-arrows-alt" });
         funcs.push({ description: "Toggle Fit Width " + (this.Document._fitWidth ? "Off" : "On"), event: () => this.Document._fitWidth = !this.Document._fitWidth, icon: "expand-arrows-alt" });
 
-        ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
 
     @computed get contentScaling() { return this.props.ContentScaling(); }
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 11b24b059..125690dc7 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -120,7 +120,7 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
                     this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
                 }), icon: "expand-arrows-alt"
             });
-            ContextMenu.Instance.addItem({ description: "Screenshot Funcs...", subitems: subitems, icon: "video" });
+            ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" });
         }
     }
 
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index 746ea0b64..b2d451ea8 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -40,7 +40,7 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocume
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
         funcs.push({ description: "Edit Thumb Change Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Thumb Change ...", this.props.Document, "onThumbChange", obj.x, obj.y) });
-        ContextMenu.Instance.addItem({ description: "Slider Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
     onChange = (values: readonly number[]) => runInAction(() => {
         this.dataDoc[this.minThumbKey] = values[0];
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 588068334..613929bca 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -209,7 +209,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                     this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
                 }), icon: "expand-arrows-alt"
             });
-            ContextMenu.Instance.addItem({ description: "Video Funcs...", subitems: subitems, icon: "video" });
+            ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" });
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 5bd4d4804f9feea0fd16d8aabf040ca14cf205d1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 23 Apr 2020 02:09:10 -0400
Subject: fixed performance of panning with something selected by making
 TemplateMenu ot render unless it has explicitly been activated.

---
 src/client/views/DocumentButtonBar.tsx             | 62 ++++++----------------
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/TemplateMenu.tsx                  |  9 ++--
 src/client/views/Templates.tsx                     | 45 ++--------------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  8 +--
 src/client/views/nodes/FormattedTextBox.tsx        |  6 +--
 src/client/views/webcam/DashWebRTCVideo.tsx        |  2 +-
 9 files changed, 37 insertions(+), 101 deletions(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 93987b751..5936bbfb8 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -6,7 +6,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../new_fields/Doc";
 import { RichTextField } from '../../new_fields/RichTextField';
 import { NumCast, StrCast } from "../../new_fields/Types";
-import { emptyFunction } from "../../Utils";
+import { emptyFunction, setupMoveUpEvents } from "../../Utils";
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
 import { UndoManager } from "../util/UndoManager";
 import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
@@ -108,10 +108,8 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
     get view0() { return this.props.views?.[0]; }
 
     @action
-    onLinkButtonMoved = (e: PointerEvent): void => {
-        if (this._linkButton.current !== null && (Math.abs(e.clientX - this._downX) > 3 || Math.abs(e.clientY - this._downY) > 3)) {
-            document.removeEventListener("pointermove", this.onLinkButtonMoved);
-            document.removeEventListener("pointerup", this.onLinkButtonUp);
+    onLinkButtonMoved = (e: PointerEvent) => {
+        if (this._linkButton.current !== null) {
             const linkDrag = UndoManager.StartBatch("Drag Link");
             this.view0 && DragManager.StartLinkDrag(this._linkButton.current, this.view0.props.Document, e.pageX, e.pageY, {
                 dragComplete: dropEv => {
@@ -131,26 +129,16 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
                 },
                 hideSource: false
             });
+            return true;
         }
-        e.stopPropagation();
+        return false;
     }
 
 
     onLinkButtonDown = (e: React.PointerEvent): void => {
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        document.removeEventListener("pointermove", this.onLinkButtonMoved);
-        document.addEventListener("pointermove", this.onLinkButtonMoved);
-        document.removeEventListener("pointerup", this.onLinkButtonUp);
-        document.addEventListener("pointerup", this.onLinkButtonUp);
-        e.stopPropagation();
+        setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, emptyFunction);
     }
 
-    onLinkButtonUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onLinkButtonMoved);
-        document.removeEventListener("pointerup", this.onLinkButtonUp);
-        e.stopPropagation();
-    }
 
     @computed
     get considerGoogleDocsPush() {
@@ -257,29 +245,12 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
         }} />;
     }
 
-    private _downx = 0;
-    private _downy = 0;
-    onAliasButtonUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onAliasButtonMoved);
-        document.removeEventListener("pointerup", this.onAliasButtonUp);
-        e.stopPropagation();
-    }
-
+    @observable _aliasDown = false;
     onAliasButtonDown = (e: React.PointerEvent): void => {
-        this._downx = e.clientX;
-        this._downy = e.clientY;
-        e.stopPropagation();
-        e.preventDefault();
-        document.removeEventListener("pointermove", this.onAliasButtonMoved);
-        document.addEventListener("pointermove", this.onAliasButtonMoved);
-        document.removeEventListener("pointerup", this.onAliasButtonUp);
-        document.addEventListener("pointerup", this.onAliasButtonUp);
-    }
-    onAliasButtonMoved = (e: PointerEvent): void => {
-        if (this._dragRef.current !== null && (Math.abs(e.clientX - this._downx) > 4 || Math.abs(e.clientY - this._downy) > 4)) {
-            document.removeEventListener("pointermove", this.onAliasButtonMoved);
-            document.removeEventListener("pointerup", this.onAliasButtonUp);
-
+        setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction);
+    };
+    onAliasButtonMoved = () => {
+        if (this._dragRef.current) {
             const dragDocView = this.props.views[0]!;
             const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
             const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
@@ -290,9 +261,10 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
                 offsetY: dragData.offset[1],
                 hideSource: false
             });
+            return true;
         }
-        e.stopPropagation();
-    }
+        return false;
+    };
 
     @computed
     get templateButton() {
@@ -301,9 +273,9 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
         Array.from(Object.values(Templates.TemplateList)).map(template =>
             templates.set(template, this.props.views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean)));
         return !view0 ? (null) : <div title="Tap: Customize layout.  Drag: Create alias" className="documentButtonBar-linkFlyout" ref={this._dragRef}>
-            <Flyout anchorPoint={anchorPoints.LEFT_TOP}
-                content={<TemplateMenu docViews={this.props.views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
-                <div className={"documentButtonBar-linkButton-" + "empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
+            <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
+                content={!this._aliasDown ? (null) : <TemplateMenu docViews={this.props.views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
+                <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
                     {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
                 </div>
             </Flyout>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6e699bc68..e1348a317 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -191,7 +191,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (e.button === 0) {
             const selectedDocs = SelectionManager.SelectedDocuments();
             if (selectedDocs.length) {
-                CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0], selectedDocs[0].props.LibraryPath)
+                CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0], selectedDocs[0].props.LibraryPath);
             }
         }
         SelectionManager.DeselectAll();
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index b76137f06..6894500dd 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -124,7 +124,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
         templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
         if (noteTypesDoc) {
-            addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(self)`));
+            addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(self,firstDoc)`, {}, { firstDoc }));
             this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
                 <OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
         }
@@ -175,11 +175,10 @@ Scripting.addGlobal(function switchView(doc: Doc, template: Doc | undefined) {
     return templateTitle && DocumentView.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template);
 });
 
-Scripting.addGlobal(function templateIsUsed(templateDoc: Doc) {
-    const firstDoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0].props.Document : undefined;
-    if (firstDoc) {
+Scripting.addGlobal(function templateIsUsed(templateDoc: Doc, selDoc: Doc) {
+    if (selDoc) {
         const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
-        return StrCast(firstDoc.layoutKey) === "layout_" + template ? 'check' : 'unchecked';
+        return StrCast(selDoc.layoutKey) === "layout_" + template ? 'check' : 'unchecked';
     }
     return false;
 });
\ No newline at end of file
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 8c60f1c36..a6dbaa650 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -1,45 +1,23 @@
-import React = require("react");
-
-export enum TemplatePosition {
-    InnerTop,
-    InnerBottom,
-    InnerRight,
-    InnerLeft,
-    TopRight,
-    OutterTop,
-    OutterBottom,
-    OutterRight,
-    OutterLeft,
-}
-
 export class Template {
-    constructor(name: string, position: TemplatePosition, layout: string) {
+    constructor(name: string, layout: string) {
         this._name = name;
-        this._position = position;
         this._layout = layout;
     }
 
     private _name: string;
-    private _position: TemplatePosition;
     private _layout: string;
 
     get Name(): string {
         return this._name;
     }
 
-    get Position(): TemplatePosition {
-        return this._position;
-    }
-
     get Layout(): string {
         return this._layout;
     }
 }
 
 export namespace Templates {
-    // export const BasicLayout = new Template("Basic layout", "{layout}");
-
-    export const Caption = new Template("Caption", TemplatePosition.OutterBottom,
+    export const Caption = new Template("Caption",
         `<div>
             <div style="height:100%; width:100%;">{layout}</div>
             <div style="bottom: 0; font-size:14px; width:100%; position:absolute">
@@ -47,16 +25,7 @@ export namespace Templates {
             </div>
         </div>` );
 
-    export const Title = new Template("Title", TemplatePosition.InnerTop,
-        `<div>
-            <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; z-index: 100">
-                <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
-            </div>
-            <div style="height:calc(100% - 25px);">
-                <div style="width:100%;overflow:auto">{layout}</div>
-            </div>
-        </div>` );
-    export const TitleHover = new Template("TitleHover", TemplatePosition.InnerTop,
+    export const Title = new Template("Title",
         `<div>
             <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; z-index: 100">
                 <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
@@ -65,14 +34,8 @@ export namespace Templates {
                 <div style="width:100%;overflow:auto">{layout}</div>
             </div>
         </div>` );
+    export const TitleHover = new Template("TitleHover", Title.Layout);
 
     export const TemplateList: Template[] = [Title, TitleHover, Caption];
-
-    export function sortTemplates(a: Template, b: Template) {
-        if (a.Position < b.Position) { return -1; }
-        if (a.Position > b.Position) { return 1; }
-        return 0;
-    }
-
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 55e55e56c..8ead1c300 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1057,7 +1057,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
         optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
-        optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
+        optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = (this.props.Document.jitterRotation ? 0 : 10)), icon: "paint-brush" });
         optionItems.push({
             description: "Import document", icon: "upload", event: ({ x, y }) => {
                 const input = document.createElement("input");
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 615b05e43..4b282b0c9 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -31,7 +31,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     random(min: number, max: number) { // min should not be equal to max
         const mseed = Math.abs(this.X * this.Y);
         const seed = (mseed * 9301 + 49297) % 233280;
-        var rnd = seed / 233280;
+        const rnd = seed / 233280;
         return min + rnd * (max - min);
     }
     get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cab7b965a..f05366a15 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -315,10 +315,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if ((this.props.Document.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                 } else {
-                    DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
-                    DocumentView._focusHack = [DocumentView._focusHack[0] + NumCast(this.props.Document.x), DocumentView._focusHack[1] + NumCast(this.props.Document.y)];
+                    if (this.props.Document.type === DocumentType.RTF) {
+                        DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
+                        DocumentView._focusHack = [DocumentView._focusHack[0] + NumCast(this.props.Document.x), DocumentView._focusHack[1] + NumCast(this.props.Document.y)];
 
-                    this.props.focus(this.props.Document, false);
+                        this.props.focus(this.props.Document, false);
+                    }
                     SelectionManager.SelectDoc(this, e.ctrlKey);
                 }
                 preventDefault = false;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 1329f7dcf..fd7462a10 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -441,9 +441,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     Doc.setNativeView(this.props.Document);
                     DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
                 }, icon: "eye"
-            })
+            });
         });
-        changeItems.push({ description: "FreeForm", event: () => DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" })
+        changeItems.push({ description: "FreeForm", event: () => DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
         !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
 
         const open = cm.findByDescription("Open New Perspective...");
@@ -455,7 +455,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 DocumentView.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
                 this.props.addDocTab(alias, "onRight");
             }, icon: "eye"
-        })
+        });
         !open && cm.addItem({ description: "Open New Perspective...", subitems: openItems, icon: "external-link-alt" });
 
     }
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index 1d52ba38f..2ea011316 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -3,7 +3,7 @@ import React = require("react");
 import { CollectionFreeFormDocumentViewProps } from "../nodes/CollectionFreeFormDocumentView";
 import { FieldViewProps, FieldView } from "../nodes/FieldView";
 import { observable, action } from "mobx";
-import { DocumentDecorations, CloseCall } from "../DocumentDecorations";
+import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
 import "../../views/nodes/WebBox.scss";
 import "./DashWebRTCVideo.scss";
-- 
cgit v1.2.3-70-g09d2


From 896c0fef965458bd23ff098c7c95b09799528033 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 24 Apr 2020 23:00:26 -0400
Subject: changed pivot view to support lists of values for keys (docs can
 appear mulltiple times as replicas)

---
 .../CollectionFreeFormLayoutEngines.tsx            | 122 +++++++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  47 ++++----
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   5 +-
 3 files changed, 100 insertions(+), 74 deletions(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 9f7919ada..d8bd18ec7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -9,6 +9,7 @@ import React = require("react");
 import { Id, ToString } from "../../../../new_fields/FieldSymbols";
 import { ObjectField } from "../../../../new_fields/ObjectField";
 import { RefField } from "../../../../new_fields/RefField";
+import { listSpec } from "../../../../new_fields/Schema";
 
 export interface ViewDefBounds {
     type: string;
@@ -24,6 +25,8 @@ export interface ViewDefBounds {
     highlight?: boolean;
     color?: string;
     payload: any;
+    replica?: string;
+    pair?: { layout: Doc, data?: Doc };
 }
 
 export interface PoolData {
@@ -36,6 +39,8 @@ export interface PoolData {
     color?: string;
     transition?: string;
     highlight?: boolean;
+    replica?: string;
+    pair: { layout: Doc, data?: Doc }
 }
 
 export interface ViewDefResult {
@@ -72,100 +77,111 @@ function getTextWidth(text: string, font: string): number {
 
 interface PivotColumn {
     docs: Doc[];
+    replicas: string[];
     filters: string[];
 }
 
 export function computerPassLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    childDocs: Doc[],
     filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
-    const docMap = new Map<Doc, ViewDefBounds>();
-    childDocs.forEach((doc, i) => {
-        docMap.set(doc, {
+    const docMap = new Map<string, ViewDefBounds>();
+    childPairs.forEach(({ layout, data }, i) => {
+        docMap.set(layout[Id], {
             type: "doc",
-            x: NumCast(doc.x),
-            y: NumCast(doc.y),
-            width: doc[WidthSym](),
-            height: doc[HeightSym](),
-            payload: undefined
+            x: NumCast(layout.x),
+            y: NumCast(layout.y),
+            width: layout[WidthSym](),
+            height: layout[HeightSym](),
+            payload: undefined,
+            pair: { layout, data },
+            replica: ""
         });
     });
-    return normalizeResults(panelDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+    return normalizeResults(panelDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childPairs.filter(c => !filterDocs.includes(c.layout)));
 }
 
 export function computerStarburstLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    childDocs: Doc[],
     filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
-    const docMap = new Map<Doc, ViewDefBounds>();
+    const docMap = new Map<string, ViewDefBounds>();
     const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])];
     const docScale = NumCast(pivotDoc._starburstDocScale);
     const docSize = docScale * 100; // assume a icon sized at 100
     const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize];
-    childDocs.forEach((doc, i) => {
-        const deg = i / childDocs.length * Math.PI * 2;
-        docMap.set(doc, {
+    childPairs.forEach((pair, i) => {
+        const deg = i / childPairs.length * Math.PI * 2;
+        docMap.set(pair.layout[Id], {
             type: "doc",
-            x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * doc[WidthSym]() / 2,
-            y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * doc[HeightSym]() / 2,
-            width: docScale * doc[WidthSym](),
-            height: docScale * doc[HeightSym](),
-            payload: undefined
+            x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * pair.layout[WidthSym]() / 2,
+            y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * pair.layout[HeightSym]() / 2,
+            width: docScale * pair.layout[WidthSym](),
+            height: docScale * pair.layout[HeightSym](),
+            payload: undefined,
+            replica: ""
         });
     });
-    return normalizeResults(scaleDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+    return normalizeResults(scaleDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childPairs.filter(c => !filterDocs.includes(c.layout)));
 }
 
 
 export function computePivotLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    childDocs: Doc[],
     filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
-    const docMap = new Map<Doc, ViewDefBounds>();
+    const docMap = new Map<string, ViewDefBounds>();
     const fieldKey = "data";
     const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
 
     const pivotFieldKey = toLabel(pivotDoc._pivotField);
     for (const doc of filterDocs) {
+        const lval = Cast(doc[pivotFieldKey], listSpec("string"), null);
         const val = Field.toString(doc[pivotFieldKey] as Field);
-        if (val) {
-            !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val] });
+        if (lval) {
+            lval.forEach((val, i) => {
+                !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
+                pivotColumnGroups.get(val)!.docs.push(doc);
+                pivotColumnGroups.get(val)!.replicas.push(i.toString());
+            });
+        } else if (val) {
+            !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
             pivotColumnGroups.get(val)!.docs.push(doc);
+            pivotColumnGroups.get(val)!.replicas.push("");
         } else {
-            docMap.set(doc, {
+            docMap.set(doc[Id], {
                 type: "doc",
                 x: 0,
                 y: 0,
                 zIndex: -99,
                 width: 0,
                 height: 0,
-                payload: undefined
+                payload: undefined,
+                pair: { layout: doc },
+                replica: ""
             })
         }
     }
     let nonNumbers = 0;
-    childDocs.map(doc => {
-        const num = toNumber(doc[pivotFieldKey]);
+    childPairs.map(pair => {
+        const num = toNumber(pair.layout[pivotFieldKey]);
         if (num === undefined || Number.isNaN(num)) {
             nonNumbers++;
         }
     });
-    const pivotNumbers = nonNumbers / childDocs.length < .1;
+    const pivotNumbers = nonNumbers / childPairs.length < .1;
     if (pivotColumnGroups.size > 10) {
         const arrayofKeys = Array.from(pivotColumnGroups.keys());
         const sortedKeys = pivotNumbers ? arrayofKeys.sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : arrayofKeys.sort();
@@ -177,6 +193,7 @@ export function computePivotLayout(
                 const newgrp = pivotColumnGroups.get(sortedKeys[j])!;
                 curgrp.docs.push(...newgrp.docs);
                 curgrp.filters.push(...newgrp.filters);
+                curgrp.replicas.push(...newgrp.replicas);
                 pivotColumnGroups.delete(sortedKeys[j]);
             }
         }
@@ -226,7 +243,7 @@ export function computePivotLayout(
             fontSize,
             payload: val
         });
-        for (const doc of val.docs) {
+        val.docs.forEach((doc, i) => {
             const layoutDoc = Doc.Layout(doc);
             let wid = pivotAxisWidth;
             let hgt = layoutDoc._nativeWidth ? (NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
@@ -234,27 +251,29 @@ export function computePivotLayout(
                 hgt = pivotAxisWidth;
                 wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
             }
-            docMap.set(doc, {
+            docMap.set(doc[Id] + (val.replicas || ""), {
                 type: "doc",
                 x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0),
                 y: -y + (pivotAxisWidth - hgt) / 2,
                 width: wid,
                 height: hgt,
-                payload: undefined
+                payload: undefined,
+                pair: { layout: doc },
+                replica: val.replicas[i]
             });
             xCount++;
             if (xCount >= numCols) {
                 xCount = 0;
                 y += pivotAxisWidth * expander;
             }
-        }
+        });
         x += pivotAxisWidth * (numCols * expander + gap);
     });
 
     const dividers = sortedPivotKeys.map((key, i) =>
         ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
     groupNames.push(...dividers);
-    return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+    return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childPairs.filter(c => !filterDocs.includes(c.layout)));
 }
 
 function toNumber(val: FieldResult<Field>) {
@@ -264,7 +283,6 @@ function toNumber(val: FieldResult<Field>) {
 export function computeTimelineLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    childDocs: Doc[],
     filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
@@ -272,7 +290,7 @@ export function computeTimelineLayout(
 ) {
     const fieldKey = "data";
     const pivotDateGroups = new Map<number, Doc[]>();
-    const docMap = new Map<Doc, ViewDefBounds>();
+    const docMap = new Map<string, ViewDefBounds>();
     const groupNames: ViewDefBounds[] = [];
     const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field);
     const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]);
@@ -351,7 +369,7 @@ export function computeTimelineLayout(
     }
 
     const divider = { type: "div", color: Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
-    return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childDocs.filter(c => !filterDocs.includes(c)));
+    return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childPairs.filter(c => !filterDocs.includes(c.layout)));
 
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
         keyDocs.forEach(doc => {
@@ -363,43 +381,51 @@ export function computeTimelineLayout(
                 hgt = pivotAxisWidth;
                 wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
             }
-            docMap.set(doc, {
+            docMap.set(doc[Id], {
                 type: "doc",
                 x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2,
-                zIndex: (curTime === key ? 1000 : zind++), highlight: curTime === key, width: wid / (Math.max(stack, 1)), height: hgt / (Math.max(stack, 1)), payload: undefined
+                zIndex: (curTime === key ? 1000 : zind++),
+                highlight: curTime === key,
+                width: wid / (Math.max(stack, 1)),
+                height: hgt / (Math.max(stack, 1)),
+                payload: undefined,
+                pair: { layout: doc },
+                replica: ""
             });
             stacking[stack] = x + pivotAxisWidth;
         });
     }
 }
 
-function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>,
+function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<string, ViewDefBounds>,
     poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[],
-    extraDocs: Doc[]): ViewDefResult[] {
+    extraDocs: { data?: Doc, layout: Doc }[]): ViewDefResult[] {
     const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
-    const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds);
+    const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
     const aggBounds = aggregateBounds(docEles.concat(grpEles).filter(e => e.zIndex !== -99), 0, 0);
     aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
     const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
     let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
     if (Number.isNaN(scale)) scale = 1;
 
-    childPairs.filter(d => docMap.get(d.layout)).map(pair => {
-        const newPosRaw = docMap.get(pair.layout);
+    Array.from(docMap.entries()).filter(ele => ele[1].pair).map(ele => {
+        const newPosRaw = ele[1];
         if (newPosRaw) {
             const newPos = {
                 x: newPosRaw.x * scale,
                 y: newPosRaw.y * scale,
                 z: newPosRaw.z,
+                replica: newPosRaw.replica,
                 highlight: newPosRaw.highlight,
                 zIndex: newPosRaw.zIndex,
                 width: (newPosRaw.width || 0) * scale,
-                height: newPosRaw.height! * scale
+                height: newPosRaw.height! * scale,
+                pair: ele[1].pair!
             };
-            poolData.set(pair.layout[Id], { transition: "transform 1s", ...newPos });
+            poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "transform 1s", ...newPos });
         }
     });
-    extraDocs.map(ed => poolData.set(ed[Id], { x: 0, y: 0, zIndex: -99 }));
+    extraDocs.map(ed => poolData.set(ed.layout[Id], { x: 0, y: 0, zIndex: -99, pair: ed }));
 
     return viewDefsToJSX(extras.concat(groupNames).map(gname => ({
         type: gname.type,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b3cf2b97a..5555d9e84 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -85,8 +85,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private _clusterDistance: number = 75;
     private _hitCluster = false;
     private _layoutComputeReaction: IReactionDisposer | undefined;
-    private _layoutPoolData = new ObservableMap<string, any>();
-    private _cachedPool: Map<string, any> = new Map();
+    private _layoutPoolData = new ObservableMap<string, PoolData>();
+    private _cachedPool: Map<string, PoolData> = new Map();
     @observable private _pullCoords: number[] = [0, 0];
     @observable private _pullDirection: string = "";
 
@@ -728,7 +728,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         if (!this.isAnnotationOverlay && clamp) {
             // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
             const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
-            const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc)).map(doc => this.childDataProvider(doc));
+            const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc, "")).map(doc => this.childDataProvider(doc, ""));
             if (measuredDocs.length) {
                 const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) =>  // computes range of content
                     ({
@@ -899,16 +899,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
         return this.props.addDocTab(doc, where);
     });
-    getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
+    getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
         const result = this.Document.arrangeScript?.script.run(params, console.log);
         if (result?.success) {
-            return { ...result, transition: "transform 1s" };
+            return { ...result, pair: params.pair, transition: "transform 1s" };
         }
-        const layoutDoc = Doc.Layout(params.doc);
-        const { x, y, z, color, zIndex } = params.doc;
+        const layoutDoc = Doc.Layout(params.pair.layout);
+        const { x, y, z, color, zIndex } = params.pair.layout;
         return {
             x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
-            width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number")
+            width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair
         };
     }
 
@@ -946,20 +946,19 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
     }
 
-    childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) {
-        return this._layoutPoolData.get(doc[Id]);
+    childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: Opt<string>) {
+        return this._layoutPoolData.get(doc[Id] + (replica || ""));
     }.bind(this));
 
     doEngineLayout(poolData: Map<string, PoolData>,
         engine: (
             poolData: Map<string, PoolData>,
             pivotDoc: Doc,
-            childDocs: Doc[],
             filterDocs: Doc[],
             childPairs: { layout: Doc, data?: Doc }[],
             panelDim: number[],
             viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]) {
-        return engine(poolData, this.props.Document, this.childDocs, this.childDocs,
+        return engine(poolData, this.props.Document, this.childDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
@@ -970,14 +969,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
 
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
-            const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
+            const pos = this.getCalculatedPositions({ pair, index: i, collection: this.Document, docs: layoutDocs, state });
             poolData.set(pair.layout[Id], pos);
         });
         return elements;
     }
 
     @computed get doInternalLayoutComputation() {
-        const newPool = new Map<string, any>();
+        const newPool = new Map<string, PoolData>();
         const engine = StrCast(this.layoutDoc._layoutEngine) || this.props.layoutEngine?.();
         switch (engine) {
             case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
@@ -992,22 +991,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     get doLayoutComputation() {
         const { newPool, computedElementData } = this.doInternalLayoutComputation;
         runInAction(() =>
-            Array.from(newPool.keys()).map(key => {
-                const lastPos = this._cachedPool.get(key); // last computed pos
-                const newPos = newPool.get(key);
+            Array.from(newPool.entries()).map(entry => {
+                const lastPos = this._cachedPool.get(entry[0]); // last computed pos
+                const newPos = entry[1];
                 if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) {
-                    this._layoutPoolData.set(key, newPos);
+                    this._layoutPoolData.set(entry[0], newPos);
                 }
             }));
         this._cachedPool.clear();
-        Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k)));
+        Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
         const elements: ViewDefResult[] = computedElementData.slice();
-        const engine = this.props.layoutEngine?.() || this.props.Document._layoutEngine;
-        this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
+        Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry =>
             elements.push({
                 ele: <CollectionFreeFormDocumentView
-                    key={pair.layout[Id]}
-                    {...this.getChildDocumentViewProps(pair.layout, pair.data)}
+                    key={entry[1].pair.layout[Id] + (entry[1].replica || "")}
+                    {...this.getChildDocumentViewProps(entry[1].pair.layout, entry[1].pair.data)}
+                    replica={entry[1].replica}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
                     pointerEvents={this.props.viewDefDivClick ? false : undefined}
@@ -1015,7 +1014,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
                 />,
-                bounds: this.childDataProvider(pair.layout)
+                bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
             }));
 
         return elements;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 4b282b0c9..baeac311e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,7 +12,7 @@ import { TraceMobx } from "../../../new_fields/util";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string } | undefined;
+    dataProvider?: (doc: Doc, replica?: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined;
     x?: number;
     y?: number;
     z?: number;
@@ -23,6 +23,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     jitterRotation: number;
     transition?: string;
     fitToBox?: boolean;
+    replica?: string;
 }
 
 @observer
@@ -46,7 +47,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
     }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
-    @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document) ? this.props.dataProvider(this.props.Document) : undefined; }
+    @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); }
     @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
     @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
 
-- 
cgit v1.2.3-70-g09d2


From c6d8ac7eef3480a7e19687b066456d9d34519587 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Apr 2020 20:25:50 -0400
Subject: several fixes for performance and for working with isBackground
 objects.

---
 .../CollectionStackingViewFieldColumn.tsx           |  2 +-
 src/client/views/collections/CollectionView.tsx     |  2 +-
 .../CollectionFreeFormLayoutEngines.tsx             |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx   | 21 ++++++++++++++-------
 .../views/nodes/CollectionFreeFormDocumentView.tsx  |  4 ++--
 src/client/views/nodes/DocumentView.tsx             | 21 ++++++++++++++-------
 src/client/views/nodes/ImageBox.scss                |  9 +--------
 src/client/views/nodes/ImageBox.tsx                 |  8 ++++----
 src/new_fields/util.ts                              |  2 +-
 9 files changed, 39 insertions(+), 32 deletions(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 5d926b7c7..8992de9d1 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -364,7 +364,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             <div className="collectionStackingViewFieldColumn" key={heading}
                 style={{
                     width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`,
-                    height: SelectionManager.GetIsDragging() ? "100%" : undefined,
+                    height: undefined, // SelectionManager.GetIsDragging() ? "100%" : undefined,
                     background: this._background
                 }}
                 ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2bd37bda5..801704673 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -493,7 +493,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             {!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (null) :
                 <div className="collectionTimeView-dragger" title="library View Dragger" onPointerDown={this.onPointerDown} style={{ right: this.facetWidth() - 10 }} />
             }
-            {this.filterView}
+            {this.facetWidth() < 10 ? (null) : this.filterView}
         </div>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 9ca747113..9a864078a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -39,7 +39,7 @@ export interface PoolData {
     color?: string;
     transition?: string;
     highlight?: boolean;
-    replica?: string;
+    replica: string;
     pair: { layout: Doc, data?: Doc };
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6a3764bb7..a13c81a15 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -508,7 +508,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
             if (Date.now() - this._lastTap < 300) {
                 const docpt = this.getTransform().transformPoint(e.clientX, e.clientY);
-                this.scaleAtPt(docpt, NumCast(this.layoutDoc.targetScale, NumCast(this.layoutDoc.scale)));
+                this.scaleAtPt(docpt, 1);
                 e.stopPropagation();
                 e.preventDefault();
             }
@@ -853,7 +853,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
     @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
     backgroundHalo = () => BoolCast(this.Document.useClusters);
-
+    @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
+    parentActive = () => this.props.active() || this.backgroundActive ? true : false;
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
         return {
             ...this.props,
@@ -879,7 +880,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             focus: this.focusDocument,
             backgroundColor: this.getClusterColor,
             backgroundHalo: this.backgroundHalo,
-            parentActive: this.props.active,
+            parentActive: this.parentActive,
             bringToFront: this.bringToFront,
             addDocTab: this.addDocTab,
         };
@@ -946,7 +947,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
     }
 
-    childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: Opt<string>) {
+    childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) {
         return this._layoutPoolData.get(doc[Id] + (replica || ""));
     }.bind(this));
 
@@ -975,6 +976,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     @computed get doInternalLayoutComputation() {
+        TraceMobx();
+
+
         const newPool = new Map<string, PoolData>();
         const engine = this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
         switch (engine) {
@@ -1009,8 +1013,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     replica={entry[1].replica}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
-                    pointerEvents={this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true)) ?
-                        false : undefined}
+                    pointerEvents={
+                        this.backgroundActive ?
+                            true :
+                            (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? false : undefined}
                     jitterRotation={NumCast(this.props.Document._jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
@@ -1164,6 +1170,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale ? wscale : hscale;
     }
+    @computed get backgroundEvents() { return this.layoutDoc.isBackground && SelectionManager.GetIsDragging(); }
     render() {
         TraceMobx();
         const clientRect = this._mainCont?.getBoundingClientRect();
@@ -1179,7 +1186,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             onWheel={this.onPointerWheel} onClick={this.onClick}  //pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
             onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
             style={{
-                pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+                pointerEvents: this.backgroundEvents ? "all" : undefined,
                 transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
                 transformOrigin: this.contentScaling ? "left top" : "",
                 width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index baeac311e..7ef8e43f7 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,7 +12,7 @@ import { TraceMobx } from "../../../new_fields/util";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, replica?: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined;
+    dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined;
     x?: number;
     y?: number;
     z?: number;
@@ -23,7 +23,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     jitterRotation: number;
     transition?: string;
     fitToBox?: boolean;
-    replica?: string;
+    replica: string;
 }
 
 @observer
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f9d9ca9a1..7ae77fc65 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -281,12 +281,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    onClick = (e: React.MouseEvent | React.PointerEvent) => {
+    onClick = action((e: React.MouseEvent | React.PointerEvent) => {
         if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             let stopPropagate = true;
             let preventDefault = true;
-            this.props.Document.isBackground === undefined && this.props.bringToFront(this.props.Document);
+            !this.props.Document.isBackground && this.props.bringToFront(this.props.Document);
             if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 if (!(e.nativeEvent as any).formattedHandled) {
                     const fullScreenAlias = Doc.MakeAlias(this.props.Document);
@@ -329,7 +329,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             stopPropagate && e.stopPropagation();
             preventDefault && e.preventDefault();
         }
-    }
+    });
 
     // follows a link - if the target is on screen, it highlights/pans to it.
     // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place
@@ -347,8 +347,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this.props.addDocTab(doc, where) && this.props.focus(doc, BoolCast(this.Document.followLinkZoom, true), undefined, hackToCallFinishAfterFocus); //  add the target and focus on it.
                 return where !== "inPlace"; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
             };
-            // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
-            this.props.focus(this.props.Document, BoolCast(this.Document.followLinkZoom, true), 1, targetFocusAfterDocFocus);
+            if (!this.Document.followLinkZoom) {
+                targetFocusAfterDocFocus();
+            } else {
+                // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
+                this.props.focus(this.props.Document, BoolCast(this.Document.followLinkZoom, true), 1, targetFocusAfterDocFocus);
+            }
         };
         await DocumentManager.Instance.FollowLink(undefined, this.props.Document, createViewFunc, shiftKey, this.props.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
     }
@@ -642,6 +646,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), title: StrCast(this.props.Document.title) + ".portal" });
             DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
         }
+        this.Document.followLinkZoom = true;
         this.Document.isLinkButton = true;
     }
 
@@ -1069,7 +1074,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const titleView = (!showTitle ? (null) :
             <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} style={{
                 position: showTextTitle ? "relative" : "absolute",
-                pointerEvents: SelectionManager.GetIsDragging() || this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
+                pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
             }}>
                 <EditableView ref={this._titleRef}
                     contents={(this.props.DataDoc || this.props.Document)[showTitle]?.toString()}
@@ -1089,7 +1094,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             </div>;
     }
     @computed get ignorePointerEvents() {
-        return this.props.pointerEvents === false || (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+        return this.props.pointerEvents === false ||
+            (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) ||
+            (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
     }
     @undoBatch
     @action
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 49425c2c2..15148d01d 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,5 +1,4 @@
-.imageBox,
-.imageBox-dragging {
+.imageBox {
     border-radius: inherit;
     width: 100%;
     height: 100%;
@@ -11,12 +10,6 @@
     }
 }
 
-.imageBox-dragging {
-    .imageBox-fader {
-        pointer-events: none;
-    }
-}
-
 #upload-icon {
     position: absolute;
     bottom: 0;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index e189f5814..14b367868 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -360,7 +360,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     }
 
     @computed get nativeSize() {
-        const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
+        TraceMobx();
+        const pw = this.props.PanelWidth?.() || 50;
         const nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], pw);
         const nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1);
         return { nativeWidth, nativeHeight };
@@ -374,7 +375,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     @computed get paths() {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
         const alts = DocListCast(this.dataDoc[this.fieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
-        const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url.href).filter(url => url); // access the primary layout data of the alternate documents
+        const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
         const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
         return paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
     }
@@ -438,8 +439,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     contentFunc = () => [this.content];
     render() {
         TraceMobx();
-        const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
-        return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu}
+        return (<div className={`imageBox`} onContextMenu={this.specificContextMenu}
             style={{
                 transform: this.props.PanelWidth() ? `translate(0px, ${this.ycenter}px)` : `scale(${this.props.ContentScaling()})`,
                 width: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 8c719ccd8..480a55da0 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,7 +12,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = false;
+const tracing = true;
 export function TraceMobx() {
     tracing && trace();
 }
-- 
cgit v1.2.3-70-g09d2


From e66812a1e19023d777d90ee382cb3849c5f63fc0 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Apr 2020 20:59:28 -0400
Subject: added a sizeProvider computedFn for efficiency since size doesn't
 change as often as position

---
 .../collectionFreeForm/CollectionFreeFormView.tsx          | 14 +++++++++++---
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx  | 12 +++++++-----
 src/new_fields/util.ts                                     |  2 +-
 3 files changed, 19 insertions(+), 9 deletions(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a13c81a15..6a6879fd4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -86,6 +86,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private _hitCluster = false;
     private _layoutComputeReaction: IReactionDisposer | undefined;
     private _layoutPoolData = new ObservableMap<string, PoolData>();
+    private _layoutSizeData = new ObservableMap<string, { width?: number, height?: number }>();
     private _cachedPool: Map<string, PoolData> = new Map();
     @observable private _pullCoords: number[] = [0, 0];
     @observable private _pullDirection: string = "";
@@ -903,13 +904,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
         const result = this.Document.arrangeScript?.script.run(params, console.log);
         if (result?.success) {
-            return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair };
+            return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
         }
         const layoutDoc = Doc.Layout(params.pair.layout);
         const { x, y, z, color, zIndex } = params.pair.layout;
         return {
             x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
-            width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair
+            width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
         };
     }
 
@@ -950,6 +951,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) {
         return this._layoutPoolData.get(doc[Id] + (replica || ""));
     }.bind(this));
+    childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) {
+        return this._layoutSizeData.get(doc[Id] + (replica || ""));
+    }.bind(this));
 
     doEngineLayout(poolData: Map<string, PoolData>,
         engine: (
@@ -997,9 +1001,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             Array.from(newPool.entries()).map(entry => {
                 const lastPos = this._cachedPool.get(entry[0]); // last computed pos
                 const newPos = entry[1];
-                if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) {
+                if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
                     this._layoutPoolData.set(entry[0], newPos);
                 }
+                if (!lastPos || newPos.height !== lastPos.height) {
+                    this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
+                }
             }));
         this._cachedPool.clear();
         Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
@@ -1012,6 +1019,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     {...this.getChildDocumentViewProps(entry[1].pair.layout, entry[1].pair.data)}
                     replica={entry[1].replica}
                     dataProvider={this.childDataProvider}
+                    sizeProvider={this.childSizeProvider}
                     LayoutDoc={this.childLayoutDocFunc}
                     pointerEvents={
                         this.backgroundActive ?
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7ef8e43f7..1c7d116c5 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,7 +12,8 @@ import { TraceMobx } from "../../../new_fields/util";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined;
+    dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, z: number, transition?: string } | undefined;
+    sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined;
     x?: number;
     y?: number;
     z?: number;
@@ -41,13 +42,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
     get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); }
     get Highlight() { return this.dataProvider?.highlight; }
-    get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); }
+    get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.sizeProvider && this.sizeProvider ? this.sizeProvider.width : this.layoutDoc[WidthSym](); }
     get height() {
-        const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym]();
+        const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.sizeProvider && this.sizeProvider ? this.sizeProvider.height : this.layoutDoc[HeightSym]();
         return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
     }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
     @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); }
+    @computed get sizeProvider() { return this.props.sizeProvider?.(this.props.Document, this.props.replica); }
     @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
     @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
 
@@ -71,8 +73,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
-    panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth?.());
-    panelHeight = () => (this.dataProvider?.height || this.props.PanelHeight?.());
+    panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.());
+    panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.());
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
         .scale(1 / this.contentScaling())
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 480a55da0..8c719ccd8 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,7 +12,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = true;
+const tracing = false;
 export function TraceMobx() {
     tracing && trace();
 }
-- 
cgit v1.2.3-70-g09d2

