From c5ae3ee0f24b980ac6d9669aded7cd3d51047cb4 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 24 Sep 2019 16:32:31 -0400
Subject: fixed moving to not redraw objects.  pdf changes started.

---
 .../views/nodes/CollectionFreeFormDocumentView.tsx       | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 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 9685f9bca..0d9ace473 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,6 +12,7 @@ import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { random } from "animejs";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
+    dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined
     x?: number;
     y?: number;
     width?: number;
@@ -32,11 +33,12 @@ export const PositionDocument = makeInterface(documentSchema, positionSchema);
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
     _disposer: IReactionDisposer | undefined = undefined;
-    @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; }
-    @computed get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; }
-    @computed get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; }
-    @computed get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.Document[WidthSym](); }
-    @computed get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.Document[HeightSym](); }
+    get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; }
+    get X() { return this._animPos !== undefined ? this._animPos[0] : 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._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : this.Document.y || 0; }
+    get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.props.Document[WidthSym](); }
+    get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.props.Document[HeightSym](); }
+    @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document, this.props.DataDoc) ? this.props.dataProvider(this.props.Document, this.props.DataDoc) : undefined; }
     @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); }
     @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); }
     @computed get scaleToOverridingWidth() { return this.width / FieldValue(this.Document.width, this.width); }
@@ -99,6 +101,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @observable _animPos: number[] | undefined = undefined;
 
     render() {
+        console.log("this.props =" + this.props.dataProvider);
+        trace();
         return (
             <div className="collectionFreeFormDocumentView-container"
                 style={{
@@ -110,7 +114,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                                         StrCast(this.layoutDoc.boxShadow, ""),
                     borderRadius: this.borderRounding(),
                     transform: this.transform,
-                    transition: this.Document.isAnimating !== undefined ? ".5s ease-in" : this.props.transition ? this.props.transition : StrCast(this.layoutDoc.transition),
+                    transition: this.Document.isAnimating !== undefined ? ".5s ease-in" : this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition),
                     width: this.width,
                     height: this.height,
                     zIndex: this.Document.zIndex || 0,
-- 
cgit v1.2.3-70-g09d2


From b69106994f5450e2ac2c515f28dacceb2e13b78b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 25 Sep 2019 00:16:55 -0400
Subject: refactored collection view to be cleaner and more modular for
 supporting things like PivotView. fixed pivot view selection bug.

---
 src/Utils.ts                                       |  15 +
 src/client/util/DocumentManager.ts                 |   9 +-
 src/client/util/SelectionManager.ts                |  16 -
 .../views/collections/CollectionBaseView.tsx       |   6 +-
 src/client/views/collections/CollectionView.tsx    |   9 +-
 .../views/collections/CollectionViewChromes.tsx    |   3 +-
 .../CollectionFreeFormLayoutEngines.tsx            | 119 +++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 567 ++++++---------------
 .../collections/collectionFreeForm/MarqueeView.tsx |   5 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  12 +-
 src/client/views/nodes/FormattedTextBox.scss       |  20 +-
 src/client/views/nodes/PresBox.tsx                 |   6 +-
 src/new_fields/Doc.ts                              |  13 +
 13 files changed, 340 insertions(+), 460 deletions(-)
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 65eb3cffd..4fac53c7d 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -235,6 +235,21 @@ export function timenow() {
     return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm;
 }
 
+export function aggregateBounds(boundsList: { x: number, y: number, width: number, height: number }[]) {
+    return boundsList.reduce((bounds, b) => {
+        var [sptX, sptY] = [b.x, b.y];
+        let [bptX, bptY] = [sptX + b.width, sptY + b.height];
+        return {
+            x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
+            r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
+        };
+    }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE });
+}
+export function intersectRect(r1: { left: number, top: number, width: number, height: number },
+    r2: { left: number, top: number, width: number, height: number }) {
+    return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
+}
+
 export function percent2frac(percent: string) {
     return Number(percent.substr(0, percent.length - 1)) / 100;
 }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index a3c7429b9..5ade2ebb3 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -188,13 +188,8 @@ export class DocumentManager {
 
     @action
     zoomIntoScale = (docDelegate: Doc, scale: number) => {
-        let doc = Doc.GetProto(docDelegate);
-
-        let docView: DocumentView | null;
-        docView = DocumentManager.Instance.getDocumentView(doc);
-        if (docView) {
-            docView.props.zoomToScale(scale);
-        }
+        let docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate));
+        docView && docView.props.zoomToScale(scale);
     }
 
     getScaleOfDocView = (docDelegate: Doc) => {
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 4c97a1056..a02a270ee 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -88,20 +88,4 @@ export namespace SelectionManager {
     export function SelectedDocuments(): Array<DocumentView> {
         return manager.SelectedDocuments.slice();
     }
-    export function ViewsSortedHorizontally(): DocumentView[] {
-        let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
-            if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1;
-            if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
-            return 0;
-        });
-        return sorted;
-    }
-    export function ViewsSortedVertically(): DocumentView[] {
-        let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
-            if (NumCast(doc1.props.Document.y) > NumCast(doc2.props.Document.y)) return 1;
-            if (NumCast(doc1.props.Document.y) < NumCast(doc2.props.Document.y)) return -1;
-            return 0;
-        });
-        return sorted;
-    }
 }
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 56d12bd84..0168c466f 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -20,7 +20,8 @@ export enum CollectionViewType {
     Docking,
     Tree,
     Stacking,
-    Masonry
+    Masonry,
+    Pivot,
 }
 
 export namespace CollectionViewType {
@@ -32,7 +33,8 @@ export namespace CollectionViewType {
         ["docking", CollectionViewType.Docking],
         ["tree", CollectionViewType.Tree],
         ["stacking", CollectionViewType.Stacking],
-        ["masonry", CollectionViewType.Masonry]
+        ["masonry", CollectionViewType.Masonry],
+        ["pivot", CollectionViewType.Pivot]
     ]);
 
     export const valueOf = (value: string) => {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5f4742834..d3072ff1e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -18,6 +18,7 @@ import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
@@ -59,8 +60,10 @@ export class CollectionView extends React.Component<FieldViewProps> {
             case CollectionViewType.Tree: return (<CollectionTreeView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
             case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
             case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
+            case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (<CollectionFreeFormView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
             case CollectionViewType.Freeform:
             default:
+                this.props.Document.freeformLayoutEngine = undefined;
                 return (<CollectionFreeFormView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
         }
         return (null);
@@ -89,7 +92,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
         if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             let existingVm = ContextMenu.Instance.findByDescription("View Modes...");
             let subItems: ContextMenuProps[] = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
-            subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; delete this.props.Document.usePivotLayout; }, icon: "signature" });
+            subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; }, icon: "signature" });
             if (CollectionBaseView.InSafeMode()) {
                 ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" });
             }
@@ -103,10 +106,10 @@ export class CollectionView extends React.Component<FieldViewProps> {
                 }, icon: "ellipsis-v"
             });
             subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" });
+            subItems.push({ description: "Pivot", event: () => this.props.Document.viewType = CollectionViewType.Pivot, icon: "columns" });
             switch (this.props.Document.viewType) {
                 case CollectionViewType.Freeform: {
-                    subItems.push({ description: "Custom", icon: "fingerprint", event: CollectionFreeFormView.AddCustomLayout(this.props.Document, this.props.fieldKey) });
-                    subItems.push({ description: "Pivot", icon: "copy", event: () => this.props.Document.usePivotLayout = true });
+                    subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
                     break;
                 }
             }
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 7510b86a0..47b300efc 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -262,7 +262,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     @observable private pivotKeyDisplay = this.pivotKey;
     getPivotInput = () => {
-        if (!this.document.usePivotLayout) {
+        if (StrCast(this.document.freeformLayoutEngine) !== "pivot") {
             return (null);
         }
         return (<input className="collectionViewBaseChrome-viewSpecsInput"
@@ -398,6 +398,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                             <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
                             <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
                             <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="7">Pivot View</option>
                         </select>
                         <div className="collectionViewBaseChrome-viewSpecs" style={{ display: collapsed ? "none" : "grid" }}>
                             <input className="collectionViewBaseChrome-viewSpecsInput"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
new file mode 100644
index 000000000..21855b168
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -0,0 +1,119 @@
+import { Doc, Field, FieldResult } from "../../../../new_fields/Doc";
+import { NumCast, StrCast, Cast } from "../../../../new_fields/Types";
+import { ScriptBox } from "../../ScriptBox";
+import { CompileScript } from "../../../util/Scripting";
+import { ScriptField } from "../../../../new_fields/ScriptField";
+import { OverlayView, OverlayElementOptions } from "../../OverlayView";
+import { emptyFunction } from "../../../../Utils";
+import React = require("react");
+
+interface PivotData {
+    type: string;
+    text: string;
+    x: number;
+    y: number;
+    width: number;
+    height: number;
+    fontSize: number;
+}
+
+export interface ViewDefBounds {
+    x: number;
+    y: number;
+    z?: number;
+    width: number;
+    height: number;
+    transition?: string;
+}
+
+export interface ViewDefResult {
+    ele: JSX.Element;
+    bounds?: ViewDefBounds;
+}
+
+export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
+    let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map();
+    const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 200);
+    const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>();
+
+    for (const doc of childDocs) {
+        const val = doc[StrCast(pivotDoc.pivotField, "title")];
+        if (val) {
+            !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, []);
+            pivotColumnGroups.get(val)!.push(doc);
+        }
+    }
+
+    const minSize = Array.from(pivotColumnGroups.entries()).reduce((min, pair) => Math.min(min, pair[1].length), Infinity);
+    const numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize)));
+    const docMap = new Map<Doc, ViewDefBounds>();
+    const groupNames: PivotData[] = [];
+
+    let x = 0;
+    pivotColumnGroups.forEach((val, key) => {
+        let y = 0;
+        let xCount = 0;
+        groupNames.push({
+            type: "text",
+            text: String(key),
+            x,
+            y: pivotAxisWidth + 50,
+            width: pivotAxisWidth * 1.25 * numCols,
+            height: 100,
+            fontSize: NumCast(pivotDoc.pivotFontSize, 10)
+        });
+        for (const doc of val) {
+            docMap.set(doc, {
+                x: x + xCount * pivotAxisWidth * 1.25,
+                y: -y,
+                width: pivotAxisWidth,
+                height: doc.nativeWidth ? (NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth
+            });
+            xCount++;
+            if (xCount >= numCols) {
+                xCount = 0;
+                y += pivotAxisWidth * 1.25;
+            }
+        }
+        x += pivotAxisWidth * 1.25 * (numCols + 1);
+    });
+
+    childPairs.map(pair => {
+        let defaultPosition = {
+            x: NumCast(pair.layout.x),
+            y: NumCast(pair.layout.y),
+            z: NumCast(pair.layout.z),
+            width: NumCast(pair.layout.width),
+            height: NumCast(pair.layout.height)
+        };
+        const pos = docMap.get(pair.layout) || defaultPosition;
+        layoutPoolData.set(pair, { transition: "transform 1s", ...pos });
+    });
+    return { map: layoutPoolData, elements: viewDefsToJSX(groupNames) };
+};
+
+
+
+export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
+    return () => {
+        let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
+            let overlayDisposer: () => void = emptyFunction; // filled in below after we have a reference to the scriptingBox
+            const scriptField = Cast(doc[key], ScriptField);
+            // tslint:disable-next-line: no-unnecessary-callback-wrapper
+            let scriptingBox = <ScriptBox initialText={scriptField && scriptField.script.originalScript}
+                onCancel={() => overlayDisposer()}  // don't get rid of the function wrapper-- we don't want to use the current value of overlayDiposer, but the one set below
+                onSave={(text, onError) => {
+                    const script = CompileScript(text, { params, requiredType, typecheck: false });
+                    if (!script.compiled) {
+                        onError(script.errors.map(error => error.messageText).join("\n"));
+                    } else {
+                        doc[key] = new ScriptField(script);
+                        overlayDisposer();
+                    }
+                }} />;
+            overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
+        };
+        addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
+        addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
+    };
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 438529596..af84a1d73 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,22 +1,23 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, reaction, trace, ObservableMap, untracked } from "mobx";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue, DateCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils";
+import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
+import { aggregateBounds, emptyFunction, intersectRect, returnEmptyString, returnOne, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
 import { DocumentType } from "../../../documents/DocumentTypes";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager } from "../../../util/DragManager";
 import { HistoryUtil } from "../../../util/History";
-import { CompileScript } from "../../../util/Scripting";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { Transform } from "../../../util/Transform";
 import { undoBatch, UndoManager } from "../../../util/UndoManager";
@@ -26,20 +27,17 @@ import { ContextMenuProps } from "../../ContextMenuItem";
 import { InkingCanvas } from "../../InkingCanvas";
 import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentContentsView } from "../../nodes/DocumentContentsView";
-import { DocumentViewProps, documentSchema } from "../../nodes/DocumentView";
+import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView";
+import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
-import { OverlayElementOptions, OverlayView } from "../../OverlayView";
 import PDFMenu from "../../pdf/PDFMenu";
-import { ScriptBox } from "../../ScriptBox";
 import { CollectionSubView } from "../CollectionSubView";
+import { computePivotLayout, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import { DocServer } from "../../../DocServer";
-import { FormattedTextBox } from "../../nodes/FormattedTextBox";
-import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
 
@@ -51,33 +49,10 @@ export const panZoomSchema = createSchema({
     arrangeInit: ScriptField,
     useClusters: "boolean",
     isRuleProvider: "boolean",
-    fitToBox: "boolean"
+    fitToBox: "boolean",
+    panTransformType: "string"
 });
 
-export interface ViewDefBounds {
-    x: number;
-    y: number;
-    z?: number;
-    width: number;
-    height: number;
-    transition?: string;
-}
-
-interface PivotData {
-    type: string;
-    text: string;
-    x: number;
-    y: number;
-    width: number;
-    height: number;
-    fontSize: number;
-}
-
-export interface ViewDefResult {
-    ele: JSX.Element;
-    bounds?: ViewDefBounds;
-}
-
 type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
 const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
 
@@ -85,49 +60,25 @@ const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSch
 export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _lastX: number = 0;
     private _lastY: number = 0;
-    private get _pwidth() { return this.props.PanelWidth(); }
-    private get _pheight() { return this.props.PanelHeight(); }
-    private get parentScaling() {
-        return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1;
-    }
-
-    ComputeContentBounds(boundsList: { x: number, y: number, width: number, height: number }[]) {
-        let bounds = boundsList.reduce((bounds, b) => {
-            var [sptX, sptY] = [b.x, b.y];
-            let [bptX, bptY] = [sptX + NumCast(b.width, 1), sptY + NumCast(b.height, 1)];
-            return {
-                x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
-                r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
-            };
-        }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE });
-        return bounds;
-    }
-
-    @computed get actualContentBounds() {
-        return this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined;
-    }
-
-    @computed get contentBounds() {
-        let bounds = this.actualContentBounds;
-        let res = {
-            panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
-            panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
-            scale: (bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1) / this.parentScaling
-        };
-        if (res.scale === 0) res.scale = 1;
-        return res;
-    }
-
-    @computed get fitToBox() { return this.props.fitToBox || this.Document.fitToBox; }
-    @computed get nativeWidth() { return this.fitToBox ? 0 : this.Document.nativeWidth || 0; }
-    @computed get nativeHeight() { return this.fitToBox ? 0 : this.Document.nativeHeight || 0; }
-    public get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt')
+    private _clusterDistance: number = 75;
+    @observable _clusterSets: (Doc[])[] = [];
+
+    @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
+    @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
+    @computed get contentBounds() { return aggregateBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); }
+    @computed get nativeWidth() { return this.fitToContent ? 0 : this.Document.nativeWidth || 0; }
+    @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; }
+    private get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt')
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
-    private panX = () => this.contentBounds.panX;
-    private panY = () => this.contentBounds.panY;
-    private zoomScaling = () => this.contentBounds.scale;
-    private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this._pwidth / 2 / this.parentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
-    private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this._pheight / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
+    private easing = () => this.props.Document.panTransformType === "Ease";
+    private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document.panX || 0;
+    private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document.panY || 0;
+    private zoomScaling = () => (this.fitToContent ?
+        Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
+        this.Document.scale || 1)
+        / this.parentScaling;
+    private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
+    private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
     private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
     private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1);
     private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
@@ -145,47 +96,31 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         this.addDocument(newBox, false);
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
-        this.props.addDocument(newBox, false);
-        this.bringToFront(newBox);
-        this.updateCluster(newBox);
-        return true;
+        let added = this.props.addDocument(newBox, false);
+        added && this.bringToFront(newBox);
+        added && this.updateCluster(newBox);
+        return added;
     }
     private selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
-        docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>
-            SelectionManager.SelectDoc(dv!, true));
+        docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
     }
+    public isCurrent(doc: Doc) { return !this.props.Document.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); }
+
     public getActiveDocuments = () => {
-        const curPage = FieldValue(this.Document.curPage, -1);
-        return this.childLayoutPairs.filter(pair => {
-            var page = NumCast(pair.layout!.page, -1);
-            return page === curPage || page === -1;
-        }).map(pair => pair.layout);
+        return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
     }
 
     @computed get fieldExtensionDoc() {
         return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
     }
 
-    intersectRect(r1: { left: number, top: number, width: number, height: number },
-        r2: { left: number, top: number, width: number, height: number }) {
-        return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
-    }
-    _clusterDistance = 75;
-    boundsOverlap(doc: Doc, doc2: Doc) {
-        var x2 = NumCast(doc2.x) - this._clusterDistance;
-        var y2 = NumCast(doc2.y) - this._clusterDistance;
-        var w2 = NumCast(doc2.width) + this._clusterDistance;
-        var h2 = NumCast(doc2.height) + this._clusterDistance;
-        var x = NumCast(doc.x) - this._clusterDistance;
-        var y = NumCast(doc.y) - this._clusterDistance;
-        var w = NumCast(doc.width) + this._clusterDistance;
-        var h = NumCast(doc.height) + this._clusterDistance;
-        if (doc.z === doc2.z && this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
-            return true;
-        }
-        return false;
+    @action
+    onDrop = (e: React.DragEvent): void => {
+        var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
+        super.onDrop(e, { x: pt[0], y: pt[1] });
     }
+
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
@@ -236,18 +171,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return false;
     }
 
-    tryDragCluster(e: PointerEvent) {
-        let probe = this.getTransform().transformPoint(e.clientX, e.clientY);
-        let cluster = this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => {
+    pickCluster(probe: number[]) {
+        return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => {
             let cx = NumCast(cd.x) - this._clusterDistance;
             let cy = NumCast(cd.y) - this._clusterDistance;
             let cw = NumCast(cd.width) + 2 * this._clusterDistance;
             let ch = NumCast(cd.height) + 2 * this._clusterDistance;
-            if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) {
-                return NumCast(cd.cluster);
-            }
-            return cluster;
+            return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ?
+                NumCast(cd.cluster) : cluster;
         }, -1);
+    }
+    tryDragCluster(e: PointerEvent) {
+        let cluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
         if (cluster !== -1) {
             let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster);
 
@@ -272,36 +207,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
         return false;
     }
-    @observable sets: (Doc[])[] = [];
 
     @undoBatch
-    @action
     updateClusters(useClusters: boolean) {
-        this.Document.useClusters = useClusters;
-        this.sets.length = 0;
-        this.childLayoutPairs.map(pair => pair.layout).map(c => {
-            let included = [];
-            for (let i = 0; i < this.sets.length; i++) {
-                for (let member of this.sets[i]) {
-                    if (this.boundsOverlap(c, member)) {
-                        included.push(i);
-                        break;
-                    }
-                }
-            }
-            if (included.length === 0) {
-                this.sets.push([c]);
-            } else if (included.length === 1) {
-                this.sets[included[0]].push(c);
-            } else {
-                this.sets[included[0]].push(c);
-                for (let s = 1; s < included.length; s++) {
-                    this.sets[included[0]].push(...this.sets[included[s]]);
-                    this.sets[included[s]].length = 0;
-                }
-            }
-        });
-        this.sets.map((set, i) => set.map(member => member.cluster = i));
+        this.props.Document.useClusters = useClusters;
+        this._clusterSets.length = 0;
+        this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
     }
 
     @undoBatch
@@ -309,28 +220,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     updateCluster(doc: Doc) {
         let childLayouts = this.childLayoutPairs.map(pair => pair.layout);
         if (this.props.Document.useClusters) {
-            this.sets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
+            this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
             let preferredInd = NumCast(doc.cluster);
             doc.cluster = -1;
-            this.sets.map((set, i) => set.map(member => {
-                if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && this.boundsOverlap(doc, member)) {
+            this._clusterSets.map((set, i) => set.map(member => {
+                if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
                     doc.cluster = i;
                 }
             }));
-            if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
+            if (doc.cluster === -1 && preferredInd !== -1 && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
                 doc.cluster = preferredInd;
             }
-            this.sets.map((set, i) => {
+            this._clusterSets.map((set, i) => {
                 if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
                     doc.cluster = i;
                 }
             });
             if (doc.cluster === -1) {
-                doc.cluster = this.sets.length;
-                this.sets.push([doc]);
+                doc.cluster = this._clusterSets.length;
+                this._clusterSets.push([doc]);
             } else {
-                for (let i = this.sets.length; i <= doc.cluster; i++) !this.sets[i] && this.sets.push([]);
-                this.sets[doc.cluster].push(doc);
+                for (let i = this._clusterSets.length; i <= doc.cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
+                this._clusterSets[doc.cluster].push(doc);
             }
         }
     }
@@ -339,13 +250,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         let clusterColor = "";
         let cluster = NumCast(doc.cluster);
         if (this.Document.useClusters) {
-            if (this.sets.length <= cluster) {
+            if (this._clusterSets.length <= cluster) {
                 setTimeout(() => this.updateCluster(doc), 0);
             } else {
                 // choose a cluster color from a palette
                 let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
                 clusterColor = colors[cluster % colors.length];
-                let set = this.sets.length > cluster ? this.sets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor)) : undefined;
+                let set = this._clusterSets[cluster] && this._clusterSets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor));
                 // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document
                 set && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
                 set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
@@ -354,8 +265,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return clusterColor;
     }
 
+    _hitCluster = false;
     @action
     onPointerDown = (e: React.PointerEvent): void => {
+        this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
         if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
             document.removeEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
@@ -374,7 +287,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     onPointerMove = (e: PointerEvent): void => {
         if (!e.cancelBubble) {
-            if (this.props.Document.useClusters && this.tryDragCluster(e)) {
+            if (this._hitCluster && this.tryDragCluster(e)) {
                 e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
                 e.preventDefault();
                 document.removeEventListener("pointermove", this.onPointerMove);
@@ -409,8 +322,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }
 
                 let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
-                let panelDim = this.props.ScreenToLocalTransform().transformDirection(this._pwidth / this.zoomScaling() * cscale,
-                    this._pheight / this.zoomScaling() * cscale);
+                let panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale,
+                    this.props.PanelHeight() / this.zoomScaling() * cscale);
                 if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2;
                 if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2;
                 if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2;
@@ -426,7 +339,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (BoolCast(this.props.Document.lockedPosition)) return;
+        if (this.props.Document.lockedPosition) return;
         if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
             e.stopPropagation();
         }
@@ -449,7 +362,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     setPan(panX: number, panY: number) {
-        if (!BoolCast(this.props.Document.lockedPosition)) {
+        if (!this.props.Document.lockedPosition) {
             this.props.Document.panTransformType = "None";
             var scale = this.getLocalTransform().inverse().Scale;
             const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
@@ -459,12 +372,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
     }
 
-    @action
-    onDrop = (e: React.DragEvent): void => {
-        var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
-        super.onDrop(e, { x: pt[0], y: pt[1] });
-    }
-
     bringToFront = (doc: Doc, sendToBack?: boolean) => {
         if (sendToBack || doc.isBackground) {
             doc.zIndex = 0;
@@ -502,51 +409,34 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
         HistoryUtil.pushState(newState);
 
-        let px = this.Document.panX;
-        let py = this.Document.panY;
-        let s = this.Document.scale;
-        this.setPan(newPanX, newPanY);
+        let savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType };
 
-        this.props.Document.panTransformType = "Ease";
+        this.setPan(newPanX, newPanY);
+        this.Document.panTransformType = "Ease";
         this.props.focus(this.props.Document);
-        if (willZoom) {
-            this.setScaleToZoom(doc, scale);
-        }
-        console.log("Focused " + this.Document.title + " " + s);
+        willZoom && this.setScaleToZoom(doc, scale);
+
         afterFocus && setTimeout(() => {
             if (afterFocus && afterFocus()) {
-                console.log("UnFocused " + this.Document.title + " " + s);
-                this.Document.panX = px;
-                this.Document.panY = py;
-                this.Document.scale = s;
+                this.Document.panX = savedState.px;
+                this.Document.panY = savedState.py;
+                this.Document.scale = savedState.s;
+                this.Document.panTransformType = savedState.pt;
             }
         }, 1000);
     }
 
     setScaleToZoom = (doc: Doc, scale: number = 0.5) => {
-        let p = this.props;
-        let PanelHeight = p.PanelHeight();
-        let panelWidth = p.PanelWidth();
-
-        let docHeight = NumCast(doc.height);
-        let docWidth = NumCast(doc.width);
-        let targetHeight = scale * PanelHeight;
-        let targetWidth = scale * panelWidth;
-
-        let maxScaleX: number = targetWidth / docWidth;
-        let maxScaleY: number = targetHeight / docHeight;
-        let maxApplicableScale = Math.min(maxScaleX, maxScaleY);
-        this.Document.scale = maxApplicableScale;
+        this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc.width), this.props.PanelHeight() / NumCast(doc.height));
     }
 
     zoomToScale = (scale: number) => {
         this.Document.scale = scale;
     }
 
-    getScale = () => this.Document.scale ? this.Document.scale : 1;
+    getScale = () => this.Document.scale || 1;
 
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
-        trace();
         return {
             DataDoc: childData,
             Document: childLayout,
@@ -611,215 +501,109 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     viewDefsToJSX = (views: any[]) => {
-        let elements: ViewDefResult[] = [];
-        if (Array.isArray(views)) {
-            elements = views.reduce<typeof elements>((prev, ele) => {
-                const jsx = this.viewDefToJSX(ele);
-                jsx && prev.push(jsx);
-                return prev;
-            }, elements);
-        }
-        return elements;
+        return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
     }
 
     private viewDefToJSX(viewDef: any): Opt<ViewDefResult> {
         if (viewDef.type === "text") {
-            const text = Cast(viewDef.text, "string");
+            const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
             const x = Cast(viewDef.x, "number");
             const y = Cast(viewDef.y, "number");
             const z = Cast(viewDef.z, "number");
             const width = Cast(viewDef.width, "number");
             const height = Cast(viewDef.height, "number");
             const fontSize = Cast(viewDef.fontSize, "number");
-            if ([text, x, y, width, height].some(val => val === undefined)) {
-                return undefined;
-            }
-
-            return {
-                ele: <div className="collectionFreeform-customText" style={{
-                    transform: `translate(${x}px, ${y}px)`,
-                    width, height, fontSize
-                }}>{text}</div>, bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
-            };
+            return [text, x, y, width, height].some(val => val === undefined) ? undefined :
+                {
+                    ele: <div className="collectionFreeform-customText" style={{ width, height, fontSize, transform: `translate(${x}px, ${y}px)` }}>
+                        {text}
+                    </div>,
+                    bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
+                };
         }
     }
 
     lookupLayout = (doc: Doc, dataDoc?: Doc) => {
         let data: any = undefined;
-        let compute = this.Document.usePivotLayout ? this.doPivotComputation.map : this._doComputation.map;
-        compute.forEach((value: any, key: { layout: Doc, data?: Doc }) => {
+        let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] };
+        switch (this.Document.freeformLayoutEngine) {
+            case "pivot": computedElementData = this.doPivotLayout; break;
+            default: computedElementData = this.doFreeformLayout; break;
+        }
+        computedElementData.map.forEach((value: any, key: { layout: Doc, data?: Doc }) => {
             if (key.layout === doc && key.data === dataDoc) {
                 data = value;
             }
         });
-        return data ? { x: data.x, y: data.y, z: data.z, width: data.width, height: data.height, transition: data.transition } : undefined;
+        return data && { x: data.x, y: data.y, z: data.z, width: data.width, height: data.height, transition: data.transition };
     }
 
-    @computed get doPivotComputation() {
-        let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map();
-        const field = StrCast(this.props.Document.pivotField) || "title";
-        const width = NumCast(this.props.Document.pivotWidth) || 200;
-        const groups = new Map<FieldResult<Field>, Doc[]>();
-
-        for (const doc of this.childDocs) {
-            const val = doc[field];
-            if (val === undefined) continue;
-
-            const l = groups.get(val);
-            if (l) {
-                l.push(doc);
-            } else {
-                groups.set(val, [doc]);
-            }
-        }
-
-        let minSize = Infinity;
-
-        groups.forEach((val, key) => minSize = Math.min(minSize, val.length));
-
-        const numCols = NumCast(this.props.Document.pivotNumColumns) || Math.ceil(Math.sqrt(minSize));
-        const fontSize = NumCast(this.props.Document.pivotFontSize);
-
-        const docMap = new Map<Doc, ViewDefBounds>();
-        const groupNames: PivotData[] = [];
-
-        let x = 0;
-        groups.forEach((val, key) => {
-            let y = 0;
-            let xCount = 0;
-            groupNames.push({
-                type: "text",
-                text: String(key),
-                x,
-                y: width + 50,
-                width: width * 1.25 * numCols,
-                height: 100, fontSize: fontSize
-            });
-            for (const doc of val) {
-                docMap.set(doc, {
-                    x: x + xCount * width * 1.25,
-                    y: -y,
-                    width,
-                    height: width,
-                });
-                xCount++;
-                if (xCount >= numCols) {
-                    xCount = 0;
-                    y += width * 1.25;
-                }
-            }
-            x += width * 1.25 * (numCols + 1);
-        });
-
-        let elements = this.viewDefsToJSX(groupNames);
-        let pairs = this.childLayoutPairs;
-        pairs.map((pair, i) => {
-            let minim = BoolCast(pair.layout.isMinimized);
-            if (minim === undefined || !minim) {
-                let defaultPosition = (): ViewDefBounds => {
-                    return {
-                        x: NumCast(pair.layout.x),
-                        y: NumCast(pair.layout.y),
-                        z: NumCast(pair.layout.z),
-                        width: NumCast(pair.layout.width),
-                        height: NumCast(pair.layout.height),
-                        transition: "transform 1s"
-                    };
-                };
-                const pos = docMap.get(pair.layout) || defaultPosition();
-                layoutPoolData.set(pair, { transition: "transform 1s", ...pos });
-            }
-        });
-        return { map: layoutPoolData, elements: elements };
-    };
-
+    @computed
+    get doPivotLayout() {
+        return computePivotLayout(this.props.Document, this.childDocs,
+            this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), this.viewDefsToJSX);
+    }
 
     @computed
-    get _doComputation() {
+    get doFreeformLayout() {
         let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map();
-        let curPage = FieldValue(this.Document.curPage, -1);
-        const initScript = this.Document.arrangeInit;
-        let state: any = undefined;
-        let pairs = this.childLayoutPairs;
-        let elements: ViewDefResult[] = [];
-        if (initScript) {
-            const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document }, console.log);
-            if (initResult.success) {
-                const result = initResult.result;
-                const { state: scriptState, views } = result;
-                state = scriptState;
-                elements = this.viewDefsToJSX(views);
-            }
-        }
-        pairs.map((pair, i) => {
-            var page = NumCast(pair.layout.page, -1);
-            if (!pair.layout.isMinimized && ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1)) {
-                const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: pairs.map(pair => pair.layout), state });
-                state = pos.state === undefined ? state : pos.state;
-                layoutPoolData.set(pair, pos);
-            }
+        let layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
+        const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
+        let state = initResult && initResult.success ? initResult.result.scriptState : undefined;
+        let 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 });
+            state = pos.state === undefined ? state : pos.state;
+            layoutPoolData.set(pair, pos);
         });
         return { map: layoutPoolData, elements: elements };
     }
 
     @computed
-    get doComputation() {
-        let dc = this.Document.usePivotLayout ? this.doPivotComputation : this._doComputation;
-        let curPage = FieldValue(this.Document.curPage, -1);
-        let pairs = this.childLayoutPairs;
-        let docviews = pairs.reduce((prev, pair) => {
-            var page = NumCast(pair.layout.page, -1);
-            if (!pair.layout.isMinimized && ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1)) {
-                prev.push({
-                    ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.lookupLayout}
-                        ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
-                        jitterRotation={NumCast(this.props.Document.jitterRotation)} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
-                    bounds: this.lookupLayout(pair.layout, pair.data)
-                });
-            }
-            return prev;
-        }, dc.elements);
+    get doLayoutComputation() {
+        let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] };
+        switch (this.Document.freeformLayoutEngine) {
+            case "pivot": computedElementData = this.doPivotLayout; break;
+            default: computedElementData = this.doFreeformLayout; break;
+        }
+        this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
+            computedElementData.elements.push({
+                ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.lookupLayout}
+                    ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
+                    jitterRotation={NumCast(this.props.Document.jitterRotation)} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
+                bounds: this.lookupLayout(pair.layout, pair.data)
+            }));
 
-        return { map: dc.map, elements: docviews };
-    }
-    @computed.struct
-    get elements() {
-        return this.doComputation.elements;
+        return computedElementData;
     }
 
-    @computed.struct
-    get views() {
-        return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
-    }
-    @computed.struct
-    get overlayViews() {
-        return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
-    }
+    @computed.struct get elements() { return this.doLayoutComputation.elements; }
+    @computed.struct get views() { return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
+    @computed.struct get overlayViews() { return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); }
 
     @action
     onCursorMove = (e: React.PointerEvent) => {
         super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
     }
 
-    arrangeContents = async () => {
-        const docs = await DocListCastAsync(this.Document[this.props.fieldKey]);
+    layoutDocsInGrid = () => {
         UndoManager.RunInBatch(() => {
-            if (docs) {
-                let startX = this.Document.panX || 0;
-                let x = startX;
-                let y = this.Document.panY || 0;
-                let i = 0;
-                const width = Math.max(...docs.map(doc => NumCast(doc.width)));
-                const height = Math.max(...docs.map(doc => NumCast(doc.height)));
-                for (const doc of docs) {
-                    doc.x = x;
-                    doc.y = y;
-                    x += width + 20;
-                    if (++i === 6) {
-                        i = 0;
-                        x = startX;
-                        y += height + 20;
-                    }
+            const docs = DocListCast(this.Document[this.props.fieldKey]);
+            let startX = this.Document.panX || 0;
+            let x = startX;
+            let y = this.Document.panY || 0;
+            let i = 0;
+            const width = Math.max(...docs.map(doc => NumCast(doc.width)));
+            const height = Math.max(...docs.map(doc => NumCast(doc.height)));
+            for (const doc of docs) {
+                doc.x = x;
+                doc.y = y;
+                x += width + 20;
+                if (++i === 6) {
+                    i = 0;
+                    x = startX;
+                    y += height + 20;
                 }
             }
         }, "arrange contents");
@@ -854,10 +638,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
         }
         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.fitToBox ? "Unset" : "Set"} Fit To Container`, event: async () => this.Document.fitToBox = !this.fitToBox, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
+        layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: async () => 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: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" });
-        layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
+        layoutItems.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({
@@ -905,53 +689,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
         ...this.views
     ]
-
-    public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
-        return () => {
-            let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
-                let overlayDisposer: () => void = emptyFunction;
-                const script = Cast(doc[key], ScriptField);
-                let originalText: string | undefined = undefined;
-                if (script) originalText = script.script.originalScript;
-                // tslint:disable-next-line: no-unnecessary-callback-wrapper
-                let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
-                    const script = CompileScript(text, {
-                        params,
-                        requiredType,
-                        typecheck: false
-                    });
-                    if (!script.compiled) {
-                        onError(script.errors.map(error => error.messageText).join("\n"));
-                        return;
-                    }
-                    doc[key] = new ScriptField(script);
-                    overlayDisposer();
-                }} />;
-                overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
-            };
-            addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
-            addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
-        };
-    }
-
     render() {
         // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
-        this.props.Document.fitX = this.actualContentBounds && this.actualContentBounds.x;
-        this.props.Document.fitY = this.actualContentBounds && this.actualContentBounds.y;
-        this.props.Document.fitW = this.actualContentBounds && (this.actualContentBounds.r - this.actualContentBounds.x);
-        this.props.Document.fitH = this.actualContentBounds && (this.actualContentBounds.b - this.actualContentBounds.y);
+        this.props.Document.fitX = this.contentBounds && this.contentBounds.x;
+        this.props.Document.fitY = this.contentBounds && this.contentBounds.y;
+        this.props.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x);
+        this.props.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
         // if fieldExt is set, then children will be stored in the extension document for the fieldKey. 
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         Doc.UpdateDocumentExtensionForField(this.props.DataDoc || this.props.Document, this.props.fieldKey);
-        const easing = () => this.props.Document.panTransformType === "Ease";
         return (
             <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}
                 onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}>
                 <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
                     addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
-                    getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
+                    getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
                     <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
-                        easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+                        easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
                         <CollectionFreeFormLinksView {...this.props} key="freeformLinks">
                             <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.fieldExtensionDoc} inkFieldKey={"ink"} >
                                 {this.childViews}
@@ -969,23 +723,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
 @observer
 class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
-    @computed get overlayView() {
-        return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
-            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
-    }
     render() {
-        return this.overlayView;
+        return <DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
+            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />
     }
 }
 
 @observer
 class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
-    @computed get backgroundView() {
-        return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
-            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
-    }
     render() {
-        return this.props.Document.backgroundLayout ? this.backgroundView : (null);
+        return !this.props.Document.backgroundLayout ? (null) :
+            (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
+                renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />)
     }
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index bbea4a555..c85c3e55b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -30,6 +30,7 @@ interface MarqueeViewProps {
     removeDocument: (doc: Doc) => boolean;
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
+    isAnnotationOverlay: boolean;
 }
 
 @observer
@@ -297,8 +298,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 y: bounds.top,
                 panX: 0,
                 panY: 0,
-                backgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
-                defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
+                backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
+                defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
                 width: bounds.width,
                 height: bounds.height,
                 title: "a nested collection",
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 0d9ace473..b30055071 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -41,7 +41,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document, this.props.DataDoc) ? this.props.dataProvider(this.props.Document, this.props.DataDoc) : undefined; }
     @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); }
     @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); }
-    @computed get scaleToOverridingWidth() { return this.width / FieldValue(this.Document.width, this.width); }
 
     @computed get renderScriptDim() {
         if (this.Document.renderScript) {
@@ -74,7 +73,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     panelHeight = () => this.props.PanelHeight();
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
-        .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth)
+        .scale(1 / this.contentScaling())
 
     borderRounding = () => {
         let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
@@ -100,9 +99,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     @observable _animPos: number[] | undefined = undefined;
 
+    finalPanelWidh = () => { return this.dataProvider ? this.dataProvider.width : this.panelWidth(); }
+    finalPanelHeight = () => { return this.dataProvider ? this.dataProvider.height : this.panelHeight(); }
+
     render() {
-        console.log("this.props =" + this.props.dataProvider);
-        trace();
         return (
             <div className="collectionFreeFormDocumentView-container"
                 style={{
@@ -123,8 +123,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     ContentScaling={this.contentScaling}
                     ScreenToLocalTransform={this.getTransform}
                     backgroundColor={this.clusterColorFunc}
-                    PanelWidth={this.panelWidth}
-                    PanelHeight={this.panelHeight}
+                    PanelWidth={this.finalPanelWidh}
+                    PanelHeight={this.finalPanelHeight}
                 />
             </div>
         );
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 435f5c055..45e516015 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -164,13 +164,13 @@ ol { counter-reset: deci1 0;}
 .upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18 }
 .lower-roman-ol {counter-reset: lroman;  p { display: inline }; font-size: 14; }
 .lower-alpha-ol {counter-reset: lalpha;  p { display: inline }; font-size: 10;}
-.decimal1:before { content: counter(deci1) ")"; counter-increment: deci1; display:inline-block; min-width: 30;}
-.decimal2:before { content: counter(deci1) "." counter(deci2) ")"; counter-increment: deci2; display:inline-block; min-width: 35}
-.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ")"; counter-increment: deci3; display:inline-block; min-width: 35}
-.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ")"; counter-increment: deci4; display:inline-block; min-width: 40}
-.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ")"; counter-increment: deci5; display:inline-block; min-width: 40}
-.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ")"; counter-increment: deci6; display:inline-block; min-width: 45}
-.decimal7:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) "." counter(deci7) ")"; counter-increment: deci7; display:inline-block; min-width: 50}
-.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ")"; counter-increment: ualph; display:inline-block; min-width: 35 }
-.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ")"; counter-increment: lroman;display:inline-block; min-width: 50 }
-.lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ")"; counter-increment: lalpha; display:inline-block; min-width: 35}
+.decimal1:before { content: counter(deci1) ") "; counter-increment: deci1; display:inline-block; min-width: 30;}
+.decimal2:before { content: counter(deci1) "." counter(deci2) ") "; counter-increment: deci2; display:inline-block; min-width: 35}
+.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ") "; counter-increment: deci3; display:inline-block; min-width: 35}
+.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ") "; counter-increment: deci4; display:inline-block; min-width: 40}
+.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ") "; counter-increment: deci5; display:inline-block; min-width: 40}
+.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ") "; counter-increment: deci6; display:inline-block; min-width: 45}
+.decimal7:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) "." counter(deci7) ") "; counter-increment: deci7; display:inline-block; min-width: 50}
+.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ") "; counter-increment: ualph; display:inline-block; min-width: 35 }
+.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ") "; counter-increment: lroman;display:inline-block; min-width: 50 }
+.lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ") "; counter-increment: lalpha; display:inline-block; min-width: 35}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index e376fbddb..5afd85430 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -161,10 +161,8 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
             if (zoomOut || this.presElementsMappings.get(docAtCurrent)!.showButton) {
                 let prevScale = NumCast(this.childrenDocs[prevSelected].viewScale, null);
                 let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[current]);
-                if (prevScale !== undefined) {
-                    if (prevScale !== curScale) {
-                        DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale);
-                    }
+                if (prevScale !== undefined && prevScale !== curScale) {
+                    DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale);
                 }
             }
         }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index b875e7896..1b3c8b0b0 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -13,6 +13,7 @@ import { listSpec } from "./Schema";
 import { ComputedField } from "./ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast, ToConstructor } from "./Types";
 import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
+import { intersectRect } from "../Utils";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -614,6 +615,18 @@ export namespace Doc {
         }), 0);
     }
 
+    export function overlapping(doc: Doc, doc2: Doc, clusterDistance: number) {
+        var x2 = NumCast(doc2.x) - clusterDistance;
+        var y2 = NumCast(doc2.y) - clusterDistance;
+        var w2 = NumCast(doc2.width) + clusterDistance;
+        var h2 = NumCast(doc2.height) + clusterDistance;
+        var x = NumCast(doc.x) - clusterDistance;
+        var y = NumCast(doc.y) - clusterDistance;
+        var w = NumCast(doc.width) + clusterDistance;
+        var h = NumCast(doc.height) + clusterDistance;
+        return doc.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
+    }
+
     export function isBrushedHighlightedDegree(doc: Doc) {
         if (Doc.IsHighlighted(doc)) {
             return 3;
-- 
cgit v1.2.3-70-g09d2


From b57741dbed846313e1b9d0dad3b6c8add2566277 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 25 Sep 2019 11:03:11 -0400
Subject: fixing up pdf rendering...

---
 .../views/collections/CollectionPDFView.scss       |   7 +-
 src/client/views/collections/CollectionPDFView.tsx |   3 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   1 +
 src/client/views/nodes/PDFBox.scss                 | 152 +++++++++--------
 src/client/views/nodes/PDFBox.tsx                  |  10 +-
 src/client/views/pdf/PDFViewer.tsx                 | 182 +++++----------------
 6 files changed, 130 insertions(+), 225 deletions(-)

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

diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss
index 50201bae8..a853e5ca6 100644
--- a/src/client/views/collections/CollectionPDFView.scss
+++ b/src/client/views/collections/CollectionPDFView.scss
@@ -29,12 +29,7 @@
     top: 0;
     left: 0;
     z-index: -1;
-}
-
-.collectionPdfView-cont-dragging {
-    span {
-        user-select: none;
-    }
+    overflow: hidden !important;
 }
 
 .collectionPdfView-backward {
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 8eda4d9ee..8f052db3c 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,4 +1,4 @@
-import { computed } from "mobx";
+import { computed, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { emptyFunction } from "../../../Utils";
@@ -46,6 +46,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
     }
 
     render() {
+        trace();
         return (
             <CollectionBaseView {...this.props} className={"collectionPdfView-cont"} onContextMenu={this.onContextMenu}>
                 {this.subView}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 9685f9bca..91037df7f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -99,6 +99,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @observable _animPos: number[] | undefined = undefined;
 
     render() {
+        trace();
         return (
             <div className="collectionFreeFormDocumentView-container"
                 style={{
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index b55ca8ba5..cbea47e20 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -9,103 +9,101 @@
 
 .pdfBox-cont {
     pointer-events: none;
-}
-
-.pdfBox-cont-interactive {
-    pointer-events: all;
-
     .pdfPage-textlayer {
         span {
-            pointer-events: all !important;
-            user-select: text;
+            pointer-events: none !important;
+            user-select: none; 
         }
     }
 }
 
-.pdfViewer-text {
-    .textLayer {
-        span {
-            user-select: text;
+.pdfBox-cont-interactive {
+    pointer-events: all;
+    .pdfViewer-text {
+        .textLayer {
+            span {
+                user-select: text;
+            }
         }
     }
+}
 
-    .react-pdf__Page {
-        transform-origin: left top;
-        position: absolute;
-        top: 0;
-        left: 0;
-    }
+.react-pdf__Page {
+    transform-origin: left top;
+    position: absolute;
+    top: 0;
+    left: 0;
+}
 
-    .react-pdf__Page__textContent span {
-        user-select: text;
-    }
+.react-pdf__Page__textContent span {
+    user-select: text;
+}
 
-    .react-pdf__Document {
-        position: absolute;
-    }
+.react-pdf__Document {
+    position: absolute;
+}
 
 
-    .pdfBox-settingsCont {
-        position: absolute;
-        right: 0;
-        top: 0;
+.pdfBox-settingsCont {
+    position: absolute;
+    right: 0;
+    top: 0;
+
+    .pdfBox-settingsButton {
+        border-bottom-left-radius: 50%;
+        display: flex;
+        justify-content: space-evenly;
+        align-items: center;
+        height: 70px;
+        background: none;
+        padding: 0;
+
+        .pdfBox-settingsButton-arrow {
+            width: 0;
+            height: 0;
+            border-top: 25px solid transparent;
+            border-bottom: 25px solid transparent;
+            border-right: 25px solid #121721;
+            transition: all 0.5s;
+        }
 
-        .pdfBox-settingsButton {
-            border-bottom-left-radius: 50%;
+        .pdfBox-settingsButton-iconCont {
+            background: #121721;
+            height: 50px;
+            width: 70px;
             display: flex;
-            justify-content: space-evenly;
+            justify-content: center;
             align-items: center;
-            height: 70px;
-            background: none;
-            padding: 0;
-
-            .pdfBox-settingsButton-arrow {
-                width: 0;
-                height: 0;
-                border-top: 25px solid transparent;
-                border-bottom: 25px solid transparent;
-                border-right: 25px solid #121721;
-                transition: all 0.5s;
-            }
-
-            .pdfBox-settingsButton-iconCont {
-                background: #121721;
-                height: 50px;
-                width: 70px;
-                display: flex;
-                justify-content: center;
-                align-items: center;
-                margin-left: -2px;
-                border-radius: 3px;
-            }
-        }
-
-        .pdfBox-settingsButton:hover {
-            background: none;
+            margin-left: -2px;
+            border-radius: 3px;
         }
+    }
 
-        .pdfBox-settingsFlyout {
-            width: 600px;
-            position: absolute;
-            background: #323232;
-            box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-            left: -400px;
-            border-radius: 7px;
-            padding: 20px;
-            display: flex;
-            flex-direction: column;
-            font-size: 30px;
-            transition: all 0.5s;
+    .pdfBox-settingsButton:hover {
+        background: none;
+    }
 
-            .pdfBox-settingsFlyout-title {
-                color: white;
-            }
+    .pdfBox-settingsFlyout {
+        width: 600px;
+        position: absolute;
+        background: #323232;
+        box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+        left: -400px;
+        border-radius: 7px;
+        padding: 20px;
+        display: flex;
+        flex-direction: column;
+        font-size: 30px;
+        transition: all 0.5s;
+
+        .pdfBox-settingsFlyout-title {
+            color: white;
+        }
 
-            .pdfBox-settingsFlyout-kvpInput {
-                margin-top: 20px;
-                display: grid;
-                grid-template-columns: 47.5% 5% 47.5%;
-            }
+        .pdfBox-settingsFlyout-kvpInput {
+            margin-top: 20px;
+            display: grid;
+            grid-template-columns: 47.5% 5% 47.5%;
         }
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 8471aefe0..a582ef2cc 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -176,10 +176,12 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
         let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
         return (!(pdfUrl instanceof PdfField) || !this._pdf ?
             <div>{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}</div> :
-            <div className={classname}
-                onScroll={this.onScroll}
-                style={{ marginTop: `${(this.Document.panY || 0)}px` }}
-                ref={this._mainCont}>
+            <div className={classname} onWheel={(e: React.WheelEvent) => e.stopPropagation()} onPointerDown={(e: React.PointerEvent) => {
+                let hit = document.elementFromPoint(e.clientX, e.clientY);
+                if (hit && hit.localName === "span") {
+                    e.button === 0 && e.stopPropagation();
+                }
+            }}>
                 <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} panY={this.Document.panY || 0}
                     Document={this.props.Document} DataDoc={this.dataDoc}
                     addDocTab={this.props.addDocTab} setPanY={this.setPanY} GoToPage={this.GotoPage}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index c16cb7eb5..f8205a566 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -31,7 +31,6 @@ interface IViewerProps {
     fieldExtensionDoc: Doc;
     fieldKey: string;
     loaded: (nw: number, nh: number, np: number) => void;
-    panY: number;
     scrollTo: (y: number) => void;
     active: () => boolean;
     setPanY?: (n: number) => void;
@@ -46,8 +45,6 @@ interface IViewerProps {
  */
 @observer
 export class PDFViewer extends React.Component<IViewerProps> {
-    @observable.shallow private _visibleElements: JSX.Element[] = []; // _visibleElements is the array of JSX elements that gets rendered
-    @observable private _isPage: string[] = [];// _isPage is an array that tells us whether or not an index is rendered as a page or as a placeholder
     @observable private _pageSizes: { width: number, height: number }[] = [];
     @observable private _annotations: Doc[] = [];
     @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
@@ -59,7 +56,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @observable private _marqueeWidth: number = 0;
     @observable private _marqueeHeight: number = 0;
 
-    private _pageBuffer: number = 1;
+    private _resizeReaction: IReactionDisposer | undefined;
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _reactionDisposer?: IReactionDisposer;
     private _annotationReactionDisposer?: IReactionDisposer;
@@ -67,8 +64,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
     private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     public _pdfViewer: any;
-    private _simpleLinkService: SimpleLinkService | undefined;
-    private _pdfFindController: any;
     private _searchString: string = "";
     private _selectionText: string = "";
     private _marquee: React.RefObject<HTMLDivElement> = React.createRef();
@@ -76,15 +71,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
     private _startY: number = 0;
     private _startX: number = 0;
 
-    @computed get panY(): number { return this.props.panY; }
-
-    // startIndex: where to start rendering pages
-    @computed get startIndex(): number { return Math.max(0, this.getPageFromScroll(this.panY) - this._pageBuffer); }
-
-    // endIndex: where to end rendering pages
-    @computed get endIndex(): number {
-        return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.panY + (this._pageSizes[0] ? this._pageSizes[0].height : 0)) + this._pageBuffer);
-    }
 
     @computed get allAnnotations() {
         return DocListCast(this.props.fieldExtensionDoc.annotations).filter(
@@ -95,25 +81,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return this._annotations.filter(anno => this._script.run({ this: anno }, console.log, true).result);
     }
 
-    componentDidUpdate = (prevProps: IViewerProps) => {
-        if (this.panY !== prevProps.panY && this._simpleLinkService) {
-            let p = this.getPageFromScroll(this.panY);
-            for (let i = Math.max(0, p - 1); i <= Math.min(this.props.pdf.numPages - 1, p + 1); i++) {
-                this._pdfViewer._ensurePdfPageLoaded(this._pdfViewer._pages[i]).then(() => {
-                    this._pdfViewer.renderingQueue.renderView(this._pdfViewer._pages[i]);
-                });
-            }
-        }
-    }
-
     componentDidMount = async () => {
         await this.initialLoad();
 
-        // this._reactionDisposer = reaction(
-        //     () => [this.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0],
-        //     () => this.renderPages(),
-        //     { fireImmediately: true });
-
         this._annotationReactionDisposer = reaction(
             () => this.props.fieldExtensionDoc && DocListCast(this.props.fieldExtensionDoc.annotations),
             annotations => annotations && annotations.length && this.renderAnnotations(annotations, true),
@@ -138,9 +108,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
         document.removeEventListener("copy", this.copy);
         document.addEventListener("copy", this.copy);
+        setTimeout(this.setupPdfJsViewer, 1000);
     }
 
     componentWillUnmount = () => {
+        this._resizeReaction && this._resizeReaction();
         this._reactionDisposer && this._reactionDisposer();
         this._annotationReactionDisposer && this._annotationReactionDisposer();
         this._filterReactionDisposer && this._filterReactionDisposer();
@@ -166,43 +138,46 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
 
-    pageLoaded = (page: Pdfjs.PDFPageViewport): void => this.props.loaded(page.width, page.height, this.props.pdf.numPages);
-
     setSelectionText = (text: string) => this._selectionText = text;
 
-    getIndex = () => this.Index;
-
     @action
     initialLoad = async () => {
-        this._pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages);
-        if (this._mainCont.current) {
+        if (this._pageSizes.length === 0) {
+            this._pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages);
             await Promise.all(this._pageSizes.map<Pdfjs.PDFPromise<any>>((val, i) =>
-                this.props.pdf.getPage(i + 1).then((page: Pdfjs.PDFPageProxy) => {
-                    runInAction(() => {
-                        this._pageSizes.splice(i, 1, {
-                            width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]),
-                            height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0])
-                        });
+                this.props.pdf.getPage(i + 1).then(action((page: Pdfjs.PDFPageProxy) => {
+                    this._pageSizes.splice(i, 1, {
+                        width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]),
+                        height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0])
                     });
-                })
-            ));
-            this.props.loaded(Math.max(...this._pageSizes.map(i => i.width)), this._pageSizes[0].height, this.props.pdf.numPages);
-            this._simpleLinkService = new SimpleLinkService(this);
-            document.addEventListener("pagesinit", () => this._pdfViewer.currentScaleValue = (this.props.Document[WidthSym]() / this._pageSizes[0].width));
-            document.addEventListener("pagerendered", () => console.log("rendered"));
-            this._pdfViewer = new PDFJSViewer.PDFViewer({
-                container: this._mainCont.current,
-                viewer: this._viewer.current,
-                linkService: this._simpleLinkService,
-                renderer: "svg"
-            });
-            this._simpleLinkService.setPDFJSview(this._pdfViewer);
-            this._pdfViewer.setDocument(this.props.pdf);
-            this._pdfFindController = new PDFJSViewer.PDFFindController(this._pdfViewer);
-            this._pdfViewer.findController = this._pdfFindController;
+                    i === this.props.pdf.numPages - 1 && this.props.loaded((page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]),
+                        (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]), i);
+                }))));
         }
     }
 
+    @action
+    setupPdfJsViewer = () => {
+        this._reactionDisposer = reaction(() => this.props.Document[WidthSym](),
+            () => this._pdfViewer.currentScaleValue = (this.props.Document[WidthSym]() / this._pageSizes[0].width));
+        document.addEventListener("pagesinit", () => this._pdfViewer.currentScaleValue = (this.props.Document[WidthSym]() / this._pageSizes[0].width));
+        document.addEventListener("pagerendered", () => console.log("rendered"));
+        var pdfLinkService = new PDFJSViewer.PDFLinkService();
+        let pdfFindController = new PDFJSViewer.PDFFindController({
+            linkService: pdfLinkService,
+        });
+        this._pdfViewer = new PDFJSViewer.PDFViewer({
+            container: this._mainCont.current,
+            viewer: this._viewer.current,
+            linkService: pdfLinkService,
+            findController: pdfFindController,
+            renderer: "svg"
+        });
+        pdfLinkService.setViewer(this._pdfViewer);
+        pdfLinkService.setDocument(this.props.pdf, null);
+        this._pdfViewer.setDocument(this.props.pdf);
+    }
+
     @action
     makeAnnotationDocument = (sourceDoc: Doc | undefined, color: string, createLink: boolean = true): Doc => {
         let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {});
@@ -336,7 +311,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @action
     search = (searchString: string) => {
         if (this._pdfViewer._pageViewsReady) {
-            this._pdfFindController.executeCommand('findagain', {
+            this._pdfViewer.findController.executeCommand('findagain', {
                 caseSensitive: false,
                 findPrevious: undefined,
                 highlightAll: true,
@@ -346,7 +321,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
         else if (this._mainCont.current) {
             let executeFind = () => {
-                this._pdfFindController.executeCommand('find', {
+                this._pdfViewer.findController.executeCommand('find', {
                     caseSensitive: false,
                     findPrevious: undefined,
                     highlightAll: true,
@@ -364,27 +339,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
     toggleSearch = (e: React.MouseEvent) => {
         e.stopPropagation();
         this._searching = !this._searching;
-
-        if (this._searching) {
-            if (!this._pdfFindController && this._mainCont.current && this._viewer.current && !this._pdfViewer) {
-                let simpleLinkService = new SimpleLinkService(this);
-                this._pdfViewer = new PDFJSViewer.PDFViewer({
-                    container: this._mainCont.current,
-                    viewer: this._viewer.current,
-                    linkService: simpleLinkService
-                });
-                simpleLinkService.setPDFJSview(this._pdfViewer);
-                document.addEventListener("pagesinit", () => this._pdfViewer.currentScaleValue = this.props.Document[WidthSym]() / this._pageSizes[0].width);
-                document.addEventListener("pagerendered", () => console.log("rendered"));
-                this._pdfViewer.setDocument(this.props.pdf);
-                this._pdfFindController = new PDFJSViewer.PDFFindController(this._pdfViewer);
-                this._pdfViewer.findController = this._pdfFindController;
-            }
-        }
-    }
-    @computed get visibleElementWrapper() {
-        trace();
-        return this._visibleElements;
     }
 
     @action
@@ -572,16 +526,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     render() {
         let scaling = this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width / this.props.Document[WidthSym]() : 1;
-        return (<div className="pdfViewer-viewer" ref={this._mainCont} onPointerDown={this.onPointerDown} onWheel={(e) => {
-            runInAction(() => {
-                if (e.currentTarget) {
-                    this.props.Document.panTransformType = "None";
-                    this.props.Document.panY = e.currentTarget.scrollTop;
-                }
-                e.stopPropagation();
-            });
-        }}>
-            <div className="pdfViewer-text" ref={this._viewer} style={{ transform: `scale(${scaling})` }} />
+        return (<div className="pdfViewer-viewer" ref={this._mainCont}>
+            <div className="pdfViewer-text" style={{ transform: `scale(${scaling})` }}>
+                <div key="viewerReal" ref={this._viewer} />
+            </div>
             <div className="pdfPage-dragAnnotationBox" ref={this._marquee}
                 style={{
                     left: `${this._marqueeX}px`, top: `${this._marqueeY}px`,
@@ -594,7 +542,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     <Annotation {...this.props} anno={anno} key={`${anno[Id]}-annotation`} />)}
             </div>
             <div className="pdfViewer-overlayCont" onPointerDown={(e) => e.stopPropagation()}
-                style={{ bottom: -this.props.panY, left: `${this._searching ? 0 : 100}%` }}>
+                style={{ bottom: 0, left: `${this._searching ? 0 : 100}%` }}>
                 <button className="pdfViewer-overlayButton" title="Open Search Bar" />
                 <input className="pdfViewer-overlaySearchBar" placeholder="Search" onChange={this.searchStringChanged}
                     onKeyDown={(e: React.KeyboardEvent) => e.keyCode === KeyCodes.ENTER ? this.search(this._searchString) : e.keyCode === KeyCodes.BACKSPACE ? e.stopPropagation() : true} />
@@ -602,17 +550,17 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     <FontAwesomeIcon icon="search" size="3x" color="white" /></button>
             </div>
             <button className="pdfViewer-overlayButton" onClick={this.prevAnnotation} title="Previous Annotation"
-                style={{ bottom: -this.props.panY + 280, right: 10, display: this.props.active() ? "flex" : "none" }}>
+                style={{ bottom: 280, right: 10, display: this.props.active() ? "flex" : "none" }}>
                 <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
                     <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="3x" /></div>
             </button>
             <button className="pdfViewer-overlayButton" onClick={this.nextAnnotation} title="Next Annotation"
-                style={{ bottom: -this.props.panY + 200, right: 10, display: this.props.active() ? "flex" : "none" }}>
+                style={{ bottom: 200, right: 10, display: this.props.active() ? "flex" : "none" }}>
                 <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
                     <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="3x" /></div>
             </button>
             <button className="pdfViewer-overlayButton" onClick={this.toggleSearch} title="Open Search Bar"
-                style={{ bottom: -this.props.panY + 10, right: 0, display: this.props.active() ? "flex" : "none" }}>
+                style={{ bottom: 10, right: 0, display: this.props.active() ? "flex" : "none" }}>
                 <div className="pdfViewer-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div>
                 <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
                     <FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="3x" /></div>
@@ -621,44 +569,4 @@ export class PDFViewer extends React.Component<IViewerProps> {
     }
 }
 
-export enum AnnotationTypes { Region }
-
-class SimpleLinkService {
-    _viewer: PDFViewer;
-    _pdfjsView: any;
-
-    constructor(viewer: PDFViewer) {
-        this._viewer = viewer;
-    }
-    setPDFJSview(v: any) { this._pdfjsView = v; }
-
-    navigateTo() { }
-
-    getDestinationHash() { return "#"; }
-
-    getAnchorUrl() { return "#"; }
-
-    setHash() { }
-
-    isPageVisible(page: number) { return true; }
-
-    executeNamedAction() { }
-
-    cachePageRef() { }
-
-    get pagesCount() { return this._viewer._pdfViewer.pagesCount; }
-
-    get page() { return this._viewer.getPageFromScroll(NumCast(this._viewer.props.panY)) + 1; }
-    set page(p: number) {
-        this._pdfjsView._ensurePdfPageLoaded(this._pdfjsView._pages[p - 1]).then(() => {
-            this._pdfjsView.renderingQueue.renderView(this._pdfjsView._pages[p - 1]);
-            if (this._viewer.props.GoToPage) {
-                this._viewer.props.GoToPage(p);
-            }
-        });
-    }
-
-
-    get rotation() { return 0; }
-    set rotation(value: any) { }
-}
\ No newline at end of file
+export enum AnnotationTypes { Region }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From f4b628c2a6810c1af51508685f12287a300d6e6f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 25 Sep 2019 22:26:54 -0400
Subject: all pdf annotations work?

---
 src/client/documents/Documents.ts                  |   7 +-
 src/client/views/DocumentButtonBar.tsx             |   5 +-
 src/client/views/MainView.tsx                      |   7 +-
 src/client/views/TemplateMenu.tsx                  |   4 +-
 .../views/collections/CollectionDockingView.tsx    |   6 +-
 .../views/collections/CollectionSchemaCells.tsx    |   6 +-
 .../views/collections/CollectionSchemaView.tsx     |   4 +-
 .../views/collections/CollectionStackingView.tsx   |   8 +-
 .../CollectionStackingViewFieldColumn.tsx          |   6 +-
 src/client/views/collections/CollectionSubView.tsx |   5 +-
 .../CollectionFreeFormLayoutEngines.tsx            |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  18 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |  13 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   6 +-
 src/client/views/nodes/DocumentView.tsx            |   3 +-
 src/client/views/nodes/FormattedTextBox.tsx        |   6 +-
 src/client/views/nodes/PDFBox.scss                 |   3 +
 src/client/views/nodes/PDFBox.tsx                  |  20 ++--
 src/client/views/pdf/PDFViewer.scss                |   5 +-
 src/client/views/pdf/PDFViewer.tsx                 | 132 +++++++++++++--------
 20 files changed, 159 insertions(+), 109 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ea7a3a8b6..392dca373 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -95,12 +95,13 @@ export namespace Docs {
 
     export namespace Prototypes {
 
-        type LayoutSource = { LayoutString: () => string };
+        type LayoutSource = { LayoutString: (ext?: string) => string };
         type CollectionLayoutSource = { LayoutString: (fieldStr: string, fieldExt?: string) => string };
         type CollectionViewType = [CollectionLayoutSource, string, string?];
         type PrototypeTemplate = {
             layout: {
                 view: LayoutSource,
+                ext?: string, // optional extension field for layout source
                 collectionView?: CollectionViewType
             },
             options?: Partial<DocumentOptions>
@@ -144,7 +145,7 @@ export namespace Docs {
                 options: { height: 32 }
             }],
             [DocumentType.PDF, {
-                layout: { view: PDFBox },
+                layout: { view: PDFBox, ext: anno },
                 options: { nativeWidth: 1200, curPage: 1 }
             }],
             [DocumentType.ICON, {
@@ -254,7 +255,7 @@ export namespace Docs {
             // synthesize the default options, the type and title from computed values and
             // whatever options pertain to this specific prototype
             let options = { title: title, type: type, baseProto: true, ...defaultOptions, ...(template.options || {}) };
-            let primary = layout.view.LayoutString();
+            let primary = layout.view.LayoutString(layout.ext);
             let collectionView = layout.collectionView;
             if (collectionView) {
                 options.layout = collectionView[0].LayoutString(collectionView[1], collectionView[2]);
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index b482e3298..9ca54f738 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -23,6 +23,7 @@ import React = require("react");
 import { DocumentView } from './nodes/DocumentView';
 import { ParentDocSelector } from './collections/ParentDocumentSelector';
 import { CollectionDockingView } from './collections/CollectionDockingView';
+import { DocumentDecorations } from './DocumentDecorations';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -225,7 +226,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
         return (
             <div className={"linkButtonWrapper"}>
                 <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => {
-                    DocumentDecorations.hasPushedHack = false;
+                    DocumentButtonBar.hasPushedHack = false;
                     this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1;
                 }}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon={icon} size={published ? "sm" : "xs"} />
@@ -259,7 +260,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
                             window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`);
                         } else {
                             this.clearPullColor();
-                            DocumentDecorations.hasPulledHack = false;
+                            DocumentButtonBar.hasPulledHack = false;
                             this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1;
                             dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
                         }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 003919866..244b217ed 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -230,7 +230,7 @@ export class MainView extends React.Component {
         } else {
             DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field => {
                 field instanceof Doc ? this.openWorkspace(field) :
-                    this.createNewWorkspace(CurrentUserUtils.MainDocId)
+                    this.createNewWorkspace(CurrentUserUtils.MainDocId);
             });
         }
     }
@@ -371,8 +371,9 @@ export class MainView extends React.Component {
     }
     flyoutWidthFunc = () => this.flyoutWidth;
     addDocTabFunc = (doc: Doc, data: Opt<Doc>, where: string) => {
-        if (where === "close")
+        if (where === "close") {
             return CollectionDockingView.CloseRightSplit(doc);
+        }
         if (doc.dockingConfig) {
             this.openWorkspace(doc);
             return true;
@@ -564,7 +565,7 @@ export class MainView extends React.Component {
         let next = () => PresBox.CurrentPresentation.next();
         let back = () => PresBox.CurrentPresentation.back();
         let startOrResetPres = () => PresBox.CurrentPresentation.startOrResetPres();
-        let closePresMode = action(() => { PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document); });
+        let closePresMode = action(() => { PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document, undefined, "onRight"); });
         return !PresBox.CurrentPresentation || !PresBox.CurrentPresentation.presMode ? (null) : <PresModeMenu next={next} back={back} presStatus={PresBox.CurrentPresentation.presStatus} startOrResetPres={startOrResetPres} closePresMode={closePresMode} > </PresModeMenu>;
     }
 
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e4ef8313d..9e5e62e03 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -117,13 +117,13 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @action
     toggleChrome = (): void => {
         this.props.docs.map(dv => {
-            let layout = dv.Document.layout instanceof Doc ? dv.Document.layout as Doc : dv.Document;
+            let layout = dv.Document.layout instanceof Doc ? dv.Document.layout : dv.Document;
             layout.chromeStatus = (layout.chromeStatus !== "disabled" ? "disabled" : "enabled");
         });
     }
 
     render() {
-        let layout = this.props.docs[0].Document.layout instanceof Doc ? this.props.docs[0].Document.layout as Doc : this.props.docs[0].Document;
+        let layout = this.props.docs[0].Document.layout instanceof Doc ? this.props.docs[0].Document.layout : this.props.docs[0].Document;
         let templateMenu: Array<JSX.Element> = [];
         this.props.templates.forEach((checked, template) =>
             templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index e5d652648..2d9faee6b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -603,9 +603,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
 
     contentScaling = () => {
         if (this._document!.type === DocumentType.PDF) {
-            if (this._panelHeight / NumCast(this._document!.nativeHeight) > this._panelWidth / NumCast(this._document!.nativeWidth))
+            if (this._panelHeight / NumCast(this._document!.nativeHeight) > this._panelWidth / NumCast(this._document!.nativeWidth)) {
                 return this._panelWidth / NumCast(this._document!.nativeWidth);
-            else return this._panelHeight / NumCast(this._document!.nativeHeight);
+            } else {
+                return this._panelHeight / NumCast(this._document!.nativeHeight);
+            }
         }
         const nativeH = this.nativeHeight();
         const nativeW = this.nativeWidth();
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 4dac27e60..179e44266 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -34,7 +34,7 @@ export interface CellProps {
     row: number;
     col: number;
     rowProps: CellInfo;
-    CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+    CollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     ContainingCollection: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     Document: Doc;
     fieldKey: string;
@@ -151,7 +151,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             fieldExt: "",
             ruleProvider: undefined,
             ContainingCollectionView: this.props.CollectionView,
-            ContainingCollectionDoc: this.props.CollectionView.props.Document,
+            ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
             isSelected: returnFalse,
             select: emptyFunction,
             renderDepth: this.props.renderDepth + 1,
@@ -301,7 +301,7 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
     render() {
         let reference = React.createRef<HTMLDivElement>();
         let onItemDown = (e: React.PointerEvent) => {
-            (!this.props.CollectionView.props.isSelected() ? undefined :
+            (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined :
                 SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
         };
         return (
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 7bd2a1971..8d931f812 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -246,7 +246,7 @@ export interface SchemaTableProps {
     PanelHeight: () => number;
     PanelWidth: () => number;
     childDocs?: Doc[];
-    CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+    CollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     ContainingCollectionDoc: Opt<Doc>;
     fieldKey: string;
@@ -804,7 +804,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
         csv.substring(0, csv.length - 1);
         let dbName = StrCast(this.props.Document.title);
         let res = await Gateway.Instance.PostSchema(csv, dbName);
-        if (self.props.CollectionView.props.addDocument) {
+        if (self.props.CollectionView && self.props.CollectionView.props.addDocument) {
             let schemaDoc = await Docs.Create.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document });
             if (schemaDoc) {
                 //self.props.CollectionView.props.addDocument(schemaDoc, false);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ccf131797..597f3f745 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -42,7 +42,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
     @computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); }
     @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
-    @computed get showAddAGroup() { return (this.sectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')); }
+    @computed get showAddAGroup() { return (this.sectionFilter && this.props.ContainingCollectionDoc && (this.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.ContainingCollectionDoc.chromeStatus !== 'disabled')); }
     @computed get columnWidth() {
         return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin,
             this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250));
@@ -347,7 +347,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     onToggle = (checked: Boolean) => {
-        this.props.CollectionView.props.Document.chromeStatus = checked ? "collapsed" : "view-mode";
+        this.props.ContainingCollectionDoc && (this.props.ContainingCollectionDoc.chromeStatus = checked ? "collapsed" : "view-mode");
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
@@ -391,10 +391,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                         style={{ width: this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
                         <EditableView {...editableViewProps} />
                     </div>}
-                {this.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? <Switch
+                {this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.chromeStatus !== 'disabled' ? <Switch
                     onChange={this.onToggle}
                     onClick={this.onToggle}
-                    defaultChecked={this.props.CollectionView.props.Document.chromeStatus !== 'view-mode'}
+                    defaultChecked={this.props.ContainingCollectionDoc.chromeStatus !== 'view-mode'}
                     checkedChildren="edit"
                     unCheckedChildren="view"
                 /> : null}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index b3b7b40dd..240adf428 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -266,7 +266,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 style={{
                     width: (style.columnWidth) /
                         ((uniqueHeadings.length +
-                            ((this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0)) || 1)
+                            ((this.props.parent.props.ContainingCollectionDoc && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'disabled') ? 1 : 0)) || 1)
                 }}>
                 {/* the default bucket (no key value) has a tooltip that describes what it is.
                     Further, it does not have a color and cannot be deleted. */}
@@ -297,7 +297,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             </div> : (null);
         for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `;
         return (
-            <div className="collectionStackingViewFieldColumn" key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, background: this._background }}
+            <div className="collectionStackingViewFieldColumn" key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((this.props.parent.props.ContainingCollectionDoc && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, background: this._background }}
                 ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
                 {headingView}
                 <div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`}
@@ -315,7 +315,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                     {this.children(this.props.docList)}
                     {singleColumn ? (null) : this.props.parent.columnDragger}
                 </div>
-                {(this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ?
+                {(this.props.parent.props.ContainingCollectionDoc && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'disabled') ?
                     <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
                         style={{ width: style.columnWidth / style.numGroupColumns }}>
                         <EditableView {...newEditableViewProps} />
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 774e6b1b9..ce80526b2 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, IReactionDisposer, reaction } from "mobx";
 import * as rp from 'request-promise';
 import CursorField from "../../../new_fields/CursorField";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
@@ -30,10 +30,11 @@ export interface CollectionViewProps extends FieldViewProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     chromeCollapsed: boolean;
+    setPreviewCursor?: (func: (x: number, y: number) => void) => void;
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
-    CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+    CollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     ruleProvider: Doc | undefined;
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 21855b168..6135f3e45 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -90,9 +90,7 @@ export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs:
         layoutPoolData.set(pair, { transition: "transform 1s", ...pos });
     });
     return { map: layoutPoolData, elements: viewDefsToJSX(groupNames) };
-};
-
-
+}
 
 export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
     return () => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 45c021c5f..075914e29 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -50,7 +50,7 @@ export const panZoomSchema = createSchema({
     useClusters: "boolean",
     isRuleProvider: "boolean",
     fitToBox: "boolean",
-    panTransformType: "string"
+    panTransformType: "string",
 });
 
 type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
@@ -61,6 +61,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _lastX: number = 0;
     private _lastY: number = 0;
     private _clusterDistance: number = 75;
+    private _hitCluster = false;
     @observable _clusterSets: (Doc[])[] = [];
 
     @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
@@ -265,7 +266,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return clusterColor;
     }
 
-    _hitCluster = false;
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
@@ -286,7 +286,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerMove = (e: PointerEvent): void => {
-        if (!e.cancelBubble && this.props.layoutKey) {
+        if (!e.cancelBubble && !this.isAnnotationOverlay) {
             if (this._hitCluster && this.tryDragCluster(e)) {
                 e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
                 e.preventDefault();
@@ -451,7 +451,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             PanelHeight: childLayout[HeightSym],
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
-            ContainingCollectionDoc: this.props.CollectionView.props.Document,
+            ContainingCollectionDoc: this.props.ContainingCollectionDoc,
             focus: this.focusDocument,
             backgroundColor: this.getClusterColor,
             parentActive: this.props.active,
@@ -478,7 +478,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             PanelHeight: layoutDoc[HeightSym],
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
-            ContainingCollectionDoc: this.props.CollectionView.props.Document,
+            ContainingCollectionDoc: this.props.ContainingCollectionDoc,
             focus: this.focusDocument,
             backgroundColor: returnEmptyString,
             parentActive: this.props.active,
@@ -699,10 +699,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         Doc.UpdateDocumentExtensionForField(this.props.DataDoc || this.props.Document, this.props.fieldKey);
         return (
-            <div className={"collectionfreeformview-container"} style={{ height: this.props.layoutKey ? "100%" : this.props.PanelHeight() }} ref={this.createDropTarget} onWheel={this.onPointerWheel}
+            <div className={"collectionfreeformview-container"} style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? "100%" : this.props.PanelHeight() }} ref={this.createDropTarget} onWheel={this.onPointerWheel}
                 onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}>
                 <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
-                    addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
+                    addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} setPreviewCursor={this.props.setPreviewCursor}
                     getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
                     <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
                         easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
@@ -725,7 +725,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
     render() {
         return <DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
-            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />
+            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />;
     }
 }
 
@@ -734,7 +734,7 @@ class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps
     render() {
         return !this.props.Document.backgroundLayout ? (null) :
             (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
-                renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />)
+                renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c85c3e55b..689a55ec4 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -31,6 +31,7 @@ interface MarqueeViewProps {
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
     isAnnotationOverlay: boolean;
+    setPreviewCursor?: (func: (x: number, y: number) => void) => void;
 }
 
 @observer
@@ -44,6 +45,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     @observable _visible: boolean = false;
     _commandExecuted = false;
 
+    componentDidMount() {
+        this.props.setPreviewCursor && this.props.setPreviewCursor(this.setPreviewCursor);
+    }
+
     @action
     cleanupInteractions = (all: boolean = false) => {
         if (all) {
@@ -203,11 +208,17 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         }
     }
 
+    setPreviewCursor = (x: number, y: number) => {
+        this._downX = x;
+        this._downY = y;
+        PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
+    }
+
     @action
     onClick = (e: React.MouseEvent): void => {
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
+            this.setPreviewCursor(e.clientX, e.clientY);
             // let the DocumentView stopPropagation of this event when it selects this document
         } else {  // why do we get a click event when the cursor have moved a big distance?
             // let's cut it off here so no one else has to deal with it.
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index bcb26b4c4..cd183a984 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,7 +12,7 @@ import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { random } from "animejs";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined
+    dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined;
     x?: number;
     y?: number;
     width?: number;
@@ -99,8 +99,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     @observable _animPos: number[] | undefined = undefined;
 
-    finalPanelWidh = () => { return this.dataProvider ? this.dataProvider.width : this.panelWidth(); }
-    finalPanelHeight = () => { return this.dataProvider ? this.dataProvider.height : this.panelHeight(); }
+    finalPanelWidh = () => this.dataProvider ? this.dataProvider.width : this.panelWidth();
+    finalPanelHeight = () => this.dataProvider ? this.dataProvider.height : this.panelHeight();
 
     render() {
         trace();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e89fddd25..759c064b4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -264,7 +264,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (e.cancelBubble && this.active) {
             document.removeEventListener("pointermove", this.onPointerMove);
         }
-        else if (!e.cancelBubble && this.active) {
+        else if (!e.cancelBubble && (SelectionManager.IsSelected(this) ||
+            this.props.parentActive())) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
                 if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.Document.lockedPosition)) {
                     document.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 47b64e260..923dd1544 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -153,7 +153,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                         }
                     });
                     return { frag: Fragment.fromArray(nodes), start: start };
-                }
+                };
                 let findLinkNode = (node: Node, editor: EditorView) => {
                     if (!node.isText) {
                         const content = findLinkFrag(node.content, editor);
@@ -162,7 +162,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     const marks = [...node.marks];
                     const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
                     return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
-                }
+                };
 
                 let start = -1;
 
@@ -748,7 +748,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     let ref = editorView.domAtPos(editorView.state.selection.from);
                     let refNode = ref.node as any;
                     while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement;
-                    let r1 = refNode && (refNode as any).getBoundingClientRect();
+                    let r1 = refNode && refNode.getBoundingClientRect();
                     let r3 = self._ref.current!.getBoundingClientRect();
                     r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale);
                     return true;
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index b7ff84d4a..2147292d6 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -16,6 +16,9 @@
             user-select: none; 
         }
     }
+    .collectionFreeFormView-none {
+        pointer-events: none;
+    }
 }
 
 .pdfBox-cont-interactive {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index e00635408..69e438d4f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from 'mobx';
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
@@ -25,7 +25,7 @@ const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
 
 @observer
 export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {
-    public static LayoutString() { return FieldView.LayoutString(PDFBox); }
+    public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(PDFBox, "data", fieldExt); }
     private _reactionDisposer?: IReactionDisposer;
     private _keyValue: string = "";
     private _valueValue: string = "";
@@ -73,24 +73,24 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     }
 
     public GetPage() {
-        return this._pdfViewer!._pdfViewer.currentPageNumber;
+        return this._pdfViewer!.pdfViewer.currentPageNumber;
     }
 
     @action
     public BackPage() {
-        this._pdfViewer!._pdfViewer.scrollPageIntoView({ pageNumber: Math.max(1, this.GetPage() - 1) });
+        this._pdfViewer!.pdfViewer.scrollPageIntoView({ pageNumber: Math.max(1, this.GetPage() - 1) });
         this.props.Document.curPage = this.GetPage();
     }
 
     @action
     public GotoPage = (p: number) => {
-        this._pdfViewer!._pdfViewer.scrollPageIntoView(p);
+        this._pdfViewer!.pdfViewer.scrollPageIntoView(p);
         this.props.Document.curPage = this.GetPage();
     }
 
     @action
     public ForwardPage() {
-        this._pdfViewer!._pdfViewer.scrollPageIntoView({ pageNumber: Math.min(this._pdfViewer!._pdfViewer.pagesCount, this.GetPage() + 1) });
+        this._pdfViewer!.pdfViewer.scrollPageIntoView({ pageNumber: Math.min(this._pdfViewer!.pdfViewer.pagesCount, this.GetPage() + 1) });
         this.props.Document.curPage = this.GetPage();
     }
 
@@ -153,7 +153,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                     <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="sm" />
                 </button>
                 <button className="pdfBox-overlayButton-iconCont" key="back" title="Page Back"
-                    onPointerDown={(e) => { e.stopPropagation() }}
+                    onPointerDown={(e) => e.stopPropagation()}
                     onClick={() => this.BackPage()}
                     style={{ left: 20, top: 5, height: "30px", position: "absolute", pointerEvents: "all", display: this.props.active() ? "flex" : "none" }}>
                     <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-left"} size="sm" />
@@ -211,7 +211,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
 
     render() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
-        let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
+        let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive");
         return (!(pdfUrl instanceof PdfField) || !this._pdf ?
             <div>{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}</div> :
             <div className={classname} onWheel={(e: React.WheelEvent) => e.stopPropagation()} onPointerDown={(e: React.PointerEvent) => {
@@ -221,11 +221,13 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                 }
             }}>
                 <PDFViewer {...this.props} pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded}
-                    setPdfViewer={this.setPdfViewer}
+                    setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
+                    renderDepth={this.props.renderDepth}
                     Document={this.props.Document} DataDoc={this.dataDoc}
                     addDocTab={this.props.addDocTab} GoToPage={this.GotoPage}
                     pinToPres={this.props.pinToPres} addDocument={this.props.addDocument}
                     ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                    isSelected={this.props.isSelected}
                     fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} />
                 {this.settingsPanel()}
             </div>);
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index a561be94d..4ec2118ee 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -18,9 +18,8 @@
     .page {
         position: relative;
     }
-
-    .pdfViewer-text {
-        transform-origin: top left;
+    .collectionfreeformview-container {
+        pointer-events: none;
     }
 
     .pdfViewer-dragAnnotationBox {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index ea5e00d73..783495e5a 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,29 +1,29 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
 import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast, FieldResult, WidthSym } from "../../../new_fields/Doc";
+import { Doc, DocListCast, FieldResult, WidthSym, Opt } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { numberRange } from "../../../Utils";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { numberRange, emptyFunction, returnOne } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from "../../documents/Documents";
-import { KeyCodes } from "../../northstar/utils/KeyCodes";
 import { DragManager } from "../../util/DragManager";
 import { CompiledScript, CompileScript } from "../../util/Scripting";
+import { Transform } from "../../util/Transform";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import Annotation from "./Annotation";
 import PDFMenu from "./PDFMenu";
 import "./PDFViewer.scss";
 import React = require("react");
-import requestPromise = require("request-promise");
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
-import { listSpec } from "../../../new_fields/Schema";
-import { Transform } from "../../util/Transform";
+import { SelectionManager } from "../../util/SelectionManager";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 
 interface IViewerProps {
@@ -33,6 +33,9 @@ interface IViewerProps {
     DataDoc?: Doc;
     fieldExtensionDoc: Doc;
     fieldKey: string;
+    fieldExt: string;
+    renderDepth: number;
+    isSelected: () => boolean;
     loaded: (nw: number, nh: number, np: number) => void;
     scrollTo: (y: number) => void;
     active: () => boolean;
@@ -42,7 +45,7 @@ interface IViewerProps {
     addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
     setPdfViewer: (view: PDFViewer) => void;
     ScreenToLocalTransform: () => Transform;
-
+    ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
 }
 
 /**
@@ -60,18 +63,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @observable private _marqueeWidth: number = 0;
     @observable private _marqueeHeight: number = 0;
 
+    public pdfViewer: any;
+    private _isChildActive = false;
+    private _setPreviewCursor: undefined | ((x: number, y: number) => void);
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _reactionDisposer?: IReactionDisposer;
     private _annotationReactionDisposer?: IReactionDisposer;
     private _filterReactionDisposer?: IReactionDisposer;
     private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
-    public _pdfViewer: any;
     private _selectionText: string = "";
     private _marquee: React.RefObject<HTMLDivElement> = React.createRef();
     private _marqueeing: boolean = false;
-    private _startY: number = 0;
     private _startX: number = 0;
+    private _startY: number = 0;
+    private _downX: number = 0;
+    private _downY: number = 0;
 
     @computed get allAnnotations() {
         return DocListCast(this.props.fieldExtensionDoc.annotations).filter(
@@ -157,22 +164,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     @action
     setupPdfJsViewer = () => {
-        document.addEventListener("pagesinit", () => this._pdfViewer.currentScaleValue = 1);
+        document.addEventListener("pagesinit", () => this.pdfViewer.currentScaleValue = 1);
         document.addEventListener("pagerendered", () => console.log("rendered"));
         var pdfLinkService = new PDFJSViewer.PDFLinkService();
         let pdfFindController = new PDFJSViewer.PDFFindController({
             linkService: pdfLinkService,
         });
-        this._pdfViewer = new PDFJSViewer.PDFViewer({
+        this.pdfViewer = new PDFJSViewer.PDFViewer({
             container: this._mainCont.current,
             viewer: this._viewer.current,
             linkService: pdfLinkService,
             findController: pdfFindController,
             renderer: "svg"
         });
-        pdfLinkService.setViewer(this._pdfViewer);
+        pdfLinkService.setViewer(this.pdfViewer);
         pdfLinkService.setDocument(this.props.pdf, null);
-        this._pdfViewer.setDocument(this.props.pdf);
+        this.pdfViewer.setDocument(this.props.pdf);
     }
 
     @action
@@ -195,7 +202,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             annoDocs.push(annoDoc);
             annoDoc.isButton = true;
             anno.remove();
-            this.props.addDocument && this.props.addDocument(annoDoc, false);
+            // this.props.addDocument && this.props.addDocument(annoDoc, false);
             mainAnnoDoc = annoDoc;
             mainAnnoDocProto.y = annoDoc.y;
             mainAnnoDocProto = Doc.GetProto(annoDoc);
@@ -305,8 +312,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     @action
     search = (searchString: string) => {
-        if (this._pdfViewer._pageViewsReady) {
-            this._pdfViewer.findController.executeCommand('findagain', {
+        if (this.pdfViewer._pageViewsReady) {
+            this.pdfViewer.findController.executeCommand('findagain', {
                 caseSensitive: false,
                 findPrevious: undefined,
                 highlightAll: true,
@@ -316,7 +323,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
         else if (this._mainCont.current) {
             let executeFind = () => {
-                this._pdfViewer.findController.executeCommand('find', {
+                this.pdfViewer.findController.executeCommand('find', {
                     caseSensitive: false,
                     findPrevious: undefined,
                     highlightAll: true,
@@ -333,7 +340,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
     onPointerDown = (e: React.PointerEvent): void => {
         // if alt+left click, drag and annotate
         if (NumCast(this.props.Document.scale, 1) !== 1) return;
-        if (!e.altKey && e.button === 0) {
+        this._marqueeing = false;
+        if (!e.altKey && e.button === 0 && this.active()) {
             PDFMenu.Instance.StartDrag = this.startDrag;
             PDFMenu.Instance.Highlight = this.highlight;
             PDFMenu.Instance.Snippet = this.createSnippet;
@@ -346,6 +354,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 }
             }
             else {
+                this._downX = e.clientX;
+                this._downY = e.clientY;
                 // set marquee x and y positions to the spatially transformed position
                 if (this._mainCont.current) {
                     let boundingRect = this._mainCont.current.getBoundingClientRect();
@@ -356,15 +366,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 this._marquee.current && (this._marquee.current.style.opacity = "0.2");
                 this.receiveAnnotations([], -1);
             }
-            document.removeEventListener("pointermove", this.onSelectStart);
-            document.addEventListener("pointermove", this.onSelectStart);
+            document.removeEventListener("pointermove", this.onSelectMove);
+            document.addEventListener("pointermove", this.onSelectMove);
             document.removeEventListener("pointerup", this.onSelectEnd);
             document.addEventListener("pointerup", this.onSelectEnd);
         }
     }
 
     @action
-    onSelectStart = (e: PointerEvent): void => {
+    onSelectMove = (e: PointerEvent): void => {
         if (this._marqueeing && this._mainCont.current) {
             // transform positions and find the width and height to set the marquee to
             let boundingRect = this._mainCont.current.getBoundingClientRect();
@@ -419,7 +429,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @action
     onSelectEnd = (e: PointerEvent): void => {
         if (this._marqueeing) {
-            this._marqueeing = false;
             if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
                 if (this._marquee.current) { // make a copy of the marquee
                     let copy = document.createElement("div");
@@ -444,23 +453,23 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
             this._marqueeHeight = this._marqueeWidth = 0;
         }
-        // else {
-        //     let sel = window.getSelection();
-        //     if (sel && sel.type === "Range") {
-        //         let selRange = sel.getRangeAt(0);
-        //         this.createTextAnnotation(sel, selRange);
-        //         PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
-        //     }
-        // }
-
-        // if (PDFMenu.Instance.Highlighting) {
-        //     this.highlight(undefined, "goldenrod");
-        // }
-        // else {
-        //     PDFMenu.Instance.StartDrag = this.startDrag;
-        //     PDFMenu.Instance.Highlight = this.highlight;
-        // }
-        document.removeEventListener("pointermove", this.onSelectStart);
+        else {
+            let sel = window.getSelection();
+            if (sel && sel.type === "Range") {
+                let selRange = sel.getRangeAt(0);
+                this.createTextAnnotation(sel, selRange);
+                PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
+            }
+        }
+
+        if (PDFMenu.Instance.Highlighting) {
+            this.highlight(undefined, "goldenrod");
+        }
+        else {
+            PDFMenu.Instance.StartDrag = this.startDrag;
+            PDFMenu.Instance.Highlight = this.highlight;
+        }
+        document.removeEventListener("pointermove", this.onSelectMove);
         document.removeEventListener("pointerup", this.onSelectEnd);
     }
 
@@ -468,7 +477,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     highlight = (targetDoc: Doc | undefined, color: string) => {
         // creates annotation documents for current highlights
         let annotationDoc = this.makeAnnotationDocument(targetDoc, color, false);
-        Doc.AddDocToList(this.props.fieldExtensionDoc, "annotations", annotationDoc);
+        Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc);
         return annotationDoc;
     }
 
@@ -529,7 +538,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     removeDocument(doc: Doc): boolean {
         //TODO This won't create the field if it doesn't already exist
         let targetDataDoc = this.props.fieldExtensionDoc;
-        let targetField = "annotations";
+        let targetField = this.props.fieldExt;
         let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
         let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
         index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
@@ -539,8 +548,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
     scrollXf = () => {
         return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._mainCont.current.scrollTop) : this.props.ScreenToLocalTransform();
     }
+    setPreviewCursor = (func?: (x: number, y: number) => void) => {
+        this._setPreviewCursor = func;
+    }
+    onClick = (e: React.MouseEvent) => {
+        this._setPreviewCursor && this._marqueeing && Math.abs(e.clientX - this._downX) < 3 && Math.abs(e.clientY - this._downY) < 3 &&
+            this._setPreviewCursor(e.clientX, e.clientY);
+    }
+    whenActiveChanged = (isActive: boolean) => {
+        this._isChildActive = isActive;
+        //this.props.whenActiveChanged(isActive); // bcz: is this needed here?
+    }
+    active = () => {
+        return this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
+    }
     render() {
-        return (<div className="pdfViewer-viewer" onPointerDown={this.onPointerDown} onWheel={(e) => e.stopPropagation()} ref={this._mainCont}>
+        return (<div className="pdfViewer-viewer" onPointerDown={this.onPointerDown} onWheel={(e) => e.stopPropagation()} onClick={this.onClick} ref={this._mainCont}>
             <div className="pdfViewer-text" ref={this._viewer} />
             <div className="pdfViewer-dragAnnotationBox" ref={this._marquee}
                 style={{
@@ -554,17 +577,24 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     <Annotation {...this.props} anno={anno} key={`${anno[Id]}-annotation`} />)}
             </div>
             <CollectionFreeFormView {...this.props}
-                fieldExt="annotations"
+                setPreviewCursor={this.setPreviewCursor}
                 PanelHeight={() => this._pageSizes.length && this._pageSizes[0] ? this.props.pdf.numPages * this._pageSizes[0].height : 300}
+                PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : 300}
+                focus={emptyFunction}
+                isSelected={this.props.isSelected}
+                select={emptyFunction}
+                active={this.active}
+                ContentScaling={returnOne}
+                whenActiveChanged={this.whenActiveChanged}
                 removeDocument={this.removeDocument}
                 moveDocument={this.moveDocument}
-                addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.AddDocToList(this.props.fieldExtensionDoc, "annotations", doc); return true; }}
+                addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); return true; }}
                 CollectionView={this.props.ContainingCollectionView}
                 ScreenToLocalTransform={this.scrollXf}
-                ruleProvider={this.props.ruleProvider}
-                chromeCollapsed={true}
-                layoutKey={undefined}
-                backgroundLayout={undefined} >
+                ruleProvider={undefined}
+                renderDepth={this.props.renderDepth + 1}
+                ContainingCollectionDoc={this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document}
+                chromeCollapsed={true}>
             </CollectionFreeFormView>
         </div >);
     }
-- 
cgit v1.2.3-70-g09d2


From 288a3a1368ab7de11007080e54cd1879196342a4 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 26 Sep 2019 14:37:49 -0400
Subject: hopefully last fixes for pdf interactions -- this time for marquee
 dragging with right button.

---
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 36 +++++++++++++---------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  1 -
 src/client/views/nodes/DocumentView.tsx            | 10 +++---
 src/client/views/pdf/PDFViewer.tsx                 | 15 +++++----
 5 files changed, 36 insertions(+), 28 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index ce80526b2..9ffb7fa6d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -30,7 +30,7 @@ export interface CollectionViewProps extends FieldViewProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     chromeCollapsed: boolean;
-    setPreviewCursor?: (func: (x: number, y: number) => void) => void;
+    setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 689a55ec4..44611869e 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;
-    setPreviewCursor?: (func: (x: number, y: number) => void) => void;
+    setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
 
 @observer
@@ -151,15 +151,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     }
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        this._downX = this._lastX = e.pageX;
-        this._downY = this._lastY = e.pageY;
-        this._commandExecuted = false;
-        PreviewCursor.Visible = false;
-        this.cleanupInteractions(true);
+        this._downX = this._lastX = e.clientX;
+        this._downY = this._lastY = e.clientY;
         if (e.button === 2 || (e.button === 0 && e.altKey)) {
-            document.addEventListener("pointermove", this.onPointerMove, true);
-            document.addEventListener("pointerup", this.onPointerUp, true);
-            document.addEventListener("keydown", this.marqueeCommand, true);
+            this.setPreviewCursor(e.clientX, e.clientY, true);
             if (e.altKey) {
                 //e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
                 e.preventDefault();
@@ -182,6 +177,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 e.stopPropagation();
                 e.preventDefault();
             }
+        } else {
+            this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this
         }
         if (e.altKey) {
             e.preventDefault();
@@ -208,17 +205,28 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         }
     }
 
-    setPreviewCursor = (x: number, y: number) => {
-        this._downX = x;
-        this._downY = y;
-        PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
+    setPreviewCursor = (x: number, y: number, drag: boolean) => {
+        if (drag) {
+            this._downX = this._lastX = x;
+            this._downY = this._lastY = y;
+            this._commandExecuted = false;
+            PreviewCursor.Visible = false;
+            this.cleanupInteractions(true);
+            document.addEventListener("pointermove", this.onPointerMove, true);
+            document.addEventListener("pointerup", this.onPointerUp, true);
+            document.addEventListener("keydown", this.marqueeCommand, true);
+        } else {
+            this._downX = x;
+            this._downY = y;
+            PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
+        }
     }
 
     @action
     onClick = (e: React.MouseEvent): void => {
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            this.setPreviewCursor(e.clientX, e.clientY);
+            this.setPreviewCursor(e.clientX, e.clientY, false);
             // let the DocumentView stopPropagation of this event when it selects this document
         } else {  // why do we get a click event when the cursor have moved a big distance?
             // let's cut it off here so no one else has to deal with it.
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index cd183a984..49b6f22db 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -103,7 +103,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     finalPanelHeight = () => this.dataProvider ? this.dataProvider.height : this.panelHeight();
 
     render() {
-        trace();
         return (
             <div className="collectionFreeFormDocumentView-container"
                 style={{
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d1d150027..0d11afbe1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -254,21 +254,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this._hitTemplateDrag = true;
             }
         }
-        if (this.active) e.stopPropagation(); // events stop at the lowest document that is active.  
+        if (this.active && e.button === 0) e.stopPropagation(); // events stop at the lowest document that is active.  if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         document.addEventListener("pointermove", this.onPointerMove);
         document.addEventListener("pointerup", this.onPointerUp);
     }
     onPointerMove = (e: PointerEvent): void => {
-        console.log("Move " + e.clientX + " " + this.props.Document.title);
         if (e.cancelBubble && this.active) {
-            document.removeEventListener("pointermove", this.onPointerMove);
+            document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
         }
-        else if (!e.cancelBubble && (SelectionManager.IsSelected(this) ||
-            this.props.parentActive())) {
+        else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive())) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
-                if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.Document.lockedPosition)) {
+                if (!e.altKey && !this.topMost && e.buttons === 1 && !this.Document.lockedPosition) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
                     this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 427da1d9b..9ef311c86 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -69,7 +69,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     public pdfViewer: any;
     private _isChildActive = false;
-    private _setPreviewCursor: undefined | ((x: number, y: number) => void);
+    private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _reactionDisposer?: IReactionDisposer;
     private _annotationReactionDisposer?: IReactionDisposer;
@@ -355,7 +355,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         // if alt+left click, drag and annotate
+        this._downX = e.clientX;
+        this._downY = e.clientY;
         if (NumCast(this.props.Document.scale, 1) !== 1) return;
+        if (e.button !== 0 && this.active()) {
+            this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true);
+        }
         this._marqueeing = false;
         if (!e.altKey && e.button === 0 && this.active()) {
             PDFMenu.Instance.StartDrag = this.startDrag;
@@ -370,8 +375,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 }
             }
             else {
-                this._downX = e.clientX;
-                this._downY = e.clientY;
                 // set marquee x and y positions to the spatially transformed position
                 if (this._mainCont.current) {
                     let boundingRect = this._mainCont.current.getBoundingClientRect();
@@ -564,15 +567,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
     scrollXf = () => {
         return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._mainCont.current.scrollTop) : this.props.ScreenToLocalTransform();
     }
-    setPreviewCursor = (func?: (x: number, y: number) => void) => {
+    setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => {
         this._setPreviewCursor = func;
     }
     onClick = (e: React.MouseEvent) => {
         this._setPreviewCursor &&
-            this._marqueeing &&
+            e.button === 0 &&
             Math.abs(e.clientX - this._downX) < 3 &&
             Math.abs(e.clientY - this._downY) < 3 &&
-            this._setPreviewCursor(e.clientX, e.clientY);
+            this._setPreviewCursor(e.clientX, e.clientY, false);
     }
     whenActiveChanged = (isActive: boolean) => {
         this._isChildActive = isActive;
-- 
cgit v1.2.3-70-g09d2


From 9d845e78593b6923c8dc3224bcd89715f39aa070 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 27 Sep 2019 14:59:48 -0400
Subject: fixed borderRounding.  fixed pdf marqueeing for efficiency.

---
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  3 +-
 src/client/views/pdf/PDFViewer.scss                |  9 ++
 src/client/views/pdf/PDFViewer.tsx                 | 99 ++++++++++++++--------
 3 files changed, 76 insertions(+), 35 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 49b6f22db..c3d2c9e51 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -77,7 +77,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     borderRounding = () => {
         let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
-        let br = StrCast(((this.layoutDoc.layout as Doc) || this.Document).borderRounding);
+        let ld = this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout as Doc : undefined;
+        let br = StrCast((ld || this.props.Document).borderRounding);
         br = !br && ruleRounding ? ruleRounding : br;
         if (br.endsWith("%")) {
             let nativeDim = Math.min(NumCast(this.layoutDoc.nativeWidth), NumCast(this.layoutDoc.nativeHeight));
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index c5a397691..8027e93a3 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -50,6 +50,15 @@
             opacity: 0.1;
         }
     }
+    .pdfViewer-waiting {
+        width: 70%;
+        height: 70%;
+        margin : 15%;
+        transition: 0.4s opacity ease;
+        opacity: 0.7; 
+        position: absolute;
+        z-index: 10;
+    }
 }
 .pdfViewer-viewer-zoomed {
     overflow-x: scroll;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 016445538..5ad4ffd48 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -392,7 +392,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
             PDFMenu.Instance.Status = "pdf";
             PDFMenu.Instance.fadeOut(true);
             if (e.target && (e.target as any).parentElement.className === "textLayer") {
-                e.stopPropagation();
                 if (!e.ctrlKey) {
                     this.receiveAnnotations([], -1);
                 }
@@ -405,7 +404,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height) + this._mainCont.current.scrollTop;
                 }
                 this._marqueeing = true;
-                this._marquee.current && (this._marquee.current.style.opacity = "0.2");
+                let marquees = this._mainCont.current!.getElementsByClassName("pdfViewer-dragAnnotationBox");
+                if (marquees && marquees.length) { // make a copy of the marquee
+                    let marquee = marquees[0] as HTMLDivElement;
+                    marquee.style.opacity = "0.2";
+                }
                 this.receiveAnnotations([], -1);
             }
             document.removeEventListener("pointermove", this.onSelectMove);
@@ -472,9 +475,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
     onSelectEnd = (e: PointerEvent): void => {
         if (this._marqueeing) {
             if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
-                if (this._marquee.current) { // make a copy of the marquee
+                let marquees = this._mainCont.current!.getElementsByClassName("pdfViewer-dragAnnotationBox");
+                if (marquees && marquees.length) { // make a copy of the marquee
                     let copy = document.createElement("div");
-                    let style = this._marquee.current.style;
+                    let marquee = marquees[0] as HTMLDivElement;
+                    let style = marquee.style;
                     copy.style.left = style.left;
                     copy.style.top = style.top;
                     copy.style.width = style.width;
@@ -483,7 +488,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     copy.style.opacity = style.opacity;
                     copy.className = "pdfPage-annotationBox";
                     this.createAnnotation(copy, this.getPageFromScroll(this._marqueeY));
-                    this._marquee.current.style.opacity = "0";
+                    marquee.style.opacity = "0";
                 }
 
                 if (!e.ctrlKey) {
@@ -609,11 +614,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
     }
 
     getCoverImage = () => {
-        let nativeWidth = NumCast(this.props.Document.nativeWidth);
         if (!this.props.Document[HeightSym]()) {
-            this.props.Document.height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
-            this.props.Document.nativeHeight = nativeWidth * this._coverPath.height / this._coverPath.width
+            setTimeout(() => {
+                this.props.Document.height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
+                this.props.Document.nativeHeight = nativeWidth * this._coverPath.height / this._coverPath.width;
+            }, 0);
         }
+        let nativeWidth = NumCast(this.props.Document.nativeWidth);
         let nativeHeight = NumCast(this.props.Document.nativeHeight);
         return <img key={this._coverPath.path} src={this._coverPath.path} onLoad={action(() => this._showWaiting = false)}
             style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />;
@@ -629,23 +636,37 @@ export class PDFViewer extends React.Component<IViewerProps> {
             this._zoomed = Number(this.pdfViewer.currentScaleValue);
         }
     }
+
+    @computed get annotationLayer() {
+        trace();
+        return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.props.Document.nativeHeight) }} ref={this._annotationLayer}>
+            {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
+                <Annotation {...this.props} anno={anno} key={`${anno[Id]}-annotation`} />)}
+        </div>;
+    }
+    @computed get pdfViewerDiv() {
+        trace();
+        return <div className="pdfViewer-text" ref={this._viewer} style={{ transformOrigin: "left top" }} />;
+    }
+    @computed get standinViews() {
+        trace();
+        return <>
+            {this._showCover ? this.getCoverImage() : (null)}
+            {this._showWaiting ? <img className="pdfViewer-waiting" key="waiting" src={"/assets/loading.gif"} /> : (null)}
+        </>;
+    }
+    marqueeWidth = () => this._marqueeWidth;
+    marqueeHeight = () => this._marqueeHeight;
+    marqueeX = () => this._marqueeX;
+    marqueeY = () => this._marqueeY;
+    marqueeing = () => this._marqueeing;
     render() {
         trace();
-
         return (<div className={"pdfViewer-viewer" + (this._zoomed !== 1 ? "-zoomed" : "")} onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} ref={this._mainCont}>
-            <div className="pdfViewer-text" ref={this._viewer} style={{ transformOrigin: "left top" }} />
-            {!this._marqueeing ? (null) : <div className="pdfViewer-dragAnnotationBox" ref={this._marquee}
-                style={{
-                    left: `${this._marqueeX}px`, top: `${this._marqueeY}px`,
-                    width: `${this._marqueeWidth}px`, height: `${this._marqueeHeight}px`,
-                    border: `${this._marqueeWidth === 0 ? "" : "2px dashed black"}`
-                }}>
-            </div>}
+            {this.pdfViewerDiv}
+            <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
             <div className="pdfViewer-overlay" style={{ transform: `scale(${this._zoomed})` }}>
-                <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.props.Document.nativeHeight) }} ref={this._annotationLayer}>
-                    {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
-                        <Annotation {...this.props} anno={anno} key={`${anno[Id]}-annotation`} />)}
-                </div>
+                {this.annotationLayer}
                 <CollectionFreeFormView {...this.props}
                     setPreviewCursor={this.setPreviewCursor}
                     PanelHeight={() => NumCast(this.props.Document.scrollHeight, NumCast(this.props.Document.nativeHeight))}
@@ -667,21 +688,31 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     chromeCollapsed={true}>
                 </CollectionFreeFormView>
             </div>
-            {this._showCover ? this.getCoverImage() : (null)}
-            {
-                this._showWaiting ? <img src={"/assets/loading.gif"}
-                    style={{
-                        width: "70%",
-                        height: "70%",
-                        margin: "15%",
-                        transition: "0.4s opacity ease",
-                        opacity: 0.7,
-                        position: "absolute",
-                        zIndex: 10
-                    }} /> : (null)
-            }
+            {this.standinViews}
         </div >);
     }
 }
 
+interface PdfViewerMarqueeProps {
+    isMarqueeing: () => boolean;
+    width: () => number;
+    height: () => number;
+    x: () => number;
+    y: () => number;
+}
+
+@observer
+class PdfViewerMarquee extends React.Component<PdfViewerMarqueeProps> {
+    render() {
+        return !this.props.isMarqueeing() ? (null) : <div className="pdfViewer-dragAnnotationBox"
+            style={{
+                left: `${this.props.x()}px`, top: `${this.props.y()}px`,
+                width: `${this.props.width()}px`, height: `${this.props.height()}px`,
+                border: `${this.props.width() === 0 ? "" : "2px dashed black"}`
+            }}>
+        </div>
+    }
+}
+
+
 export enum AnnotationTypes { Region }
-- 
cgit v1.2.3-70-g09d2

