From ba120c1c8bde4d78b339b81798aaee0d7bfb2eb3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 23 Feb 2021 14:42:42 -0500
Subject: restored export/import of collection clones.  did a little cleanup of
 collection freeform view

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 150 +++++++--------------
 1 file changed, 47 insertions(+), 103 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 58288a7b1..18cf825a3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -13,7 +13,7 @@ import { ScriptField } from "../../../../fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
 import { TraceMobx } from "../../../../fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnFalse, setupMoveUpEvents, Utils, emptyFunction } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs, DocUtils } from "../../../documents/Documents";
@@ -57,7 +57,6 @@ export const panZoomSchema = createSchema({
     _currentTimecode: "number",
     _timecodeToShow: "number",
     _currentFrame: "number",
-    arrangeInit: ScriptField,
     _useClusters: "boolean",
     fitToBox: "boolean",
     _xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
@@ -104,19 +103,19 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private _nudgeTime = 0;
     private _thumbIdentifier?: number;
 
-    @observable private _hLines: number[] | undefined;
-    @observable private _vLines: number[] | undefined;
-    @observable private _pullCoords: number[] = [0, 0];
-    @observable private _pullDirection: string = "";
-    @observable private _showAnimTimeline = false;
+    @observable _hLines: number[] | undefined;
+    @observable _vLines: number[] | undefined;
+    @observable _pullCoords: number[] = [0, 0];
+    @observable _pullDirection: string = "";
+    @observable _showAnimTimeline = false;
     @observable ChildDrag: DocumentView | undefined; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
 
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
     @observable _clusterSets: (Doc[])[] = [];
     @observable _timelineRef = React.createRef<Timeline>();
     @observable _marqueeRef = React.createRef<HTMLDivElement>();
-    @observable _focusFilters: Opt<string[]>; // fields that get overriden by focus anchor
-    @observable _focusRangeFilters: Opt<string[]>; // fields that get overriden by focus anchor
+    @observable _focusFilters: Opt<string[]>; // docFilters that are overridden when previewing a link to an anchor which has docFilters set on it
+    @observable _focusRangeFilters: Opt<string[]>; // docRangeFilters that are overridden when previewing a link to an anchor which has docRangeFilters set on it
 
     @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     @computed get fitToContentVals() {
@@ -126,10 +125,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             scale: !this.childDocs.length ? 1 :
                 Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y),
                     this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x))
-        } || undefined;
+        };
     }
     @computed get fitToContent() { return (this.props.fitContentsToDoc?.() || this.Document._fitToBox) && !this.isAnnotationOverlay; }
-    @computed get parentScaling() { return 1; }
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
     @computed get nativeWidth() { return this.fitToContent ? 0 : Doc.NativeWidth(this.Document); }
     @computed get nativeHeight() { return this.fitToContent ? 0 : Doc.NativeHeight(this.Document); }
@@ -138,14 +136,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
     private panX = () => this.freeformData()?.panX ?? NumCast(this.Document._panX);
     private panY = () => this.freeformData()?.panY ?? NumCast(this.Document._panY);
-    private zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)) / this.parentScaling;
+    private zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1));
+    private contentTransform = () => `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`
     @computed get cachedCenteringShiftX(): number {
         const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
-        return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / this.parentScaling / scaling;  // shift so pan position is at center of window for non-overlay collections
+        return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling;  // shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedCenteringShiftY(): number {
         const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
-        return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / this.parentScaling / scaling;// shift so pan position is at center of window for non-overlay collections
+        return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling;// shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedGetLocalTransform(): Transform {
         return Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
@@ -157,8 +156,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return this.getTransformOverlay().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
     }
 
-    private centeringShiftX = () => this.cachedCenteringShiftX;
-    private centeringShiftY = () => this.cachedCenteringShiftY;
     private getTransform = () => this.cachedGetTransform.copy();
     private getLocalTransform = () => this.cachedGetLocalTransform.copy();
     private getContainerTransform = () => this.cachedGetContainerTransform.copy();
@@ -1065,7 +1062,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return this.props.addDocTab(doc, where);
     });
 
-    getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
+    getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc }): PoolData {
         const layoutDoc = Doc.Layout(params.pair.layout);
         const { x, y, opacity } = this.Document._currentFrame === undefined ?
             { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) } :
@@ -1133,24 +1130,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     doFreeformLayout(poolData: Map<string, PoolData>) {
-        const layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
-        const initResult = this.Document.arrangeInit?.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
-        const state = initResult?.success ? initResult.result.scriptState : undefined;
-        const elements = initResult?.success ? this.viewDefsToJSX(initResult.result.views) : [];
-
-        this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
-            const pos = this.getCalculatedPositions({ pair, index: i, collection: this.Document, docs: layoutDocs, state });
-            poolData.set(pair.layout[Id], pos);
-        });
-        return elements;
+        this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) =>
+            poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document })));
+        return [] as ViewDefResult[];
     }
 
     @computed get doInternalLayoutComputation() {
         TraceMobx();
-
         const newPool = new Map<string, PoolData>();
-        const engine = this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
-        switch (engine) {
+        switch (this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine)) {
             case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
             case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
             case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
@@ -1437,19 +1425,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         e.stopPropagation();
     }
 
-    private childViews = () => {
-        const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
-        return [
-            ...children,
-            ...this.views,
-        ];
-    }
-
     children = () => {
-        const eles: JSX.Element[] = [];
-        eles.push(...this.childViews());
-        eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
-        return eles;
+        const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
+        return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />];
     }
 
     @computed get placeholder() {
@@ -1486,8 +1464,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             ref={(el) => {
                 const ctx = el?.getContext('2d');
                 if (ctx) {
-                    const Cx = this.centeringShiftX() % renderGridSpace;
-                    const Cy = this.centeringShiftY() % renderGridSpace;
+                    const Cx = this.cachedCenteringShiftX % renderGridSpace;
+                    const Cy = this.cachedCenteringShiftY % renderGridSpace;
                     ctx.lineWidth = Math.min(1, Math.max(0.5, this.zoomScaling()));
                     ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
                     ctx.clearRect(0, 0, w, h);
@@ -1536,14 +1514,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 {this.layoutDoc["_backgroundGrid-show"] && (!SnappingManager.GetIsDragging() || !Doc.UserDoc().showSnapLines) ? this.backgroundGrid : (null)}
                 <CollectionFreeFormViewPannableContents
                     isAnnotationOverlay={this.isAnnotationOverlay}
-                    centeringShiftX={this.centeringShiftX}
-                    centeringShiftY={this.centeringShiftY}
+                    transform={this.contentTransform}
+                    zoomScaling={this.zoomScaling}
                     presPaths={BoolCast(this.Document.presPathView)}
                     progressivize={BoolCast(this.Document.editProgressivize)}
                     presPinView={BoolCast(this.Document.presPinView)}
                     transition={Cast(this.layoutDoc._viewTransition, "string", null)}
-                    viewDefDivClick={this.props.viewDefDivClick}
-                    zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+                    viewDefDivClick={this.props.viewDefDivClick}>
                     {this.children}
                 </CollectionFreeFormViewPannableContents>
             </div>
@@ -1582,7 +1559,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }}>
             {this.Document._freeformLOD && !this.props.active() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0 ?
                 this.placeholder : this.marqueeView}
-            {!this.props.noOverlay ? <CollectionFreeFormOverlayView elements={this.elementFunc} /> : (null)}
+            {this.props.noOverlay ? (null) : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
 
             <div className={"pullpane-indicator"}
                 style={{
@@ -1628,10 +1605,7 @@ class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOv
 }
 
 interface CollectionFreeFormViewPannableContentsProps {
-    centeringShiftX: () => number;
-    centeringShiftY: () => number;
-    panX: () => number;
-    panY: () => number;
+    transform: () => string;
     zoomScaling: () => number;
     viewDefDivClick?: ScriptField;
     children: () => JSX.Element[];
@@ -1644,67 +1618,45 @@ interface CollectionFreeFormViewPannableContentsProps {
 
 @observer
 class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
-    @observable private _drag: string = '';
+    @observable _drag: string = '';
 
     //Adds event listener so knows pointer is down and moving
     onPointerDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
         e.preventDefault();
-        const corner = e.target as any;
-        if (corner) this._drag = corner.id;
-        const rect = document.getElementById(this._drag);
-        if (rect) {
-            setupMoveUpEvents(e.target, e, this.onPointerMove, (e) => { }, (e) => { });
-        }
-    }
-
-    //Removes all event listeners
-    onPointerUp = (e: PointerEvent): void => {
-        e.stopPropagation();
-        e.preventDefault();
-        this._drag = "";
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
+        this._drag = (e.target as any)?.id ?? "";
+        document.getElementById(this._drag) && setupMoveUpEvents(e.target, e, this.onPointerMove, emptyFunction, emptyFunction);
     }
 
     //Adjusts the value in NodeStore
     @action
     onPointerMove = (e: PointerEvent) => {
         const doc = document.getElementById('resizable');
-        const rect = doc!.getBoundingClientRect();
-        const toNumber = (original: number, delta: number): number => {
-            return original + (delta * this.props.zoomScaling());
-        };
+        const toNumber = (original: number, delta: number) => original + (delta * this.props.zoomScaling());
         if (doc) {
-            const height = doc.offsetHeight;
-            const width = doc.offsetWidth;
-            const top = doc.offsetTop;
-            const left = doc.offsetLeft;
             switch (this._drag) {
-                case "": break;
                 case "resizer-br":
-                    doc.style.width = toNumber(width, e.movementX) + 'px';
-                    doc.style.height = toNumber(height, e.movementY) + 'px';
+                    doc.style.width = toNumber(doc.offsetWidth, e.movementX) + 'px';
+                    doc.style.height = toNumber(doc.offsetHeight, e.movementY) + 'px';
                     break;
                 case "resizer-bl":
-                    doc.style.width = toNumber(width, -e.movementX) + 'px';
-                    doc.style.height = toNumber(height, e.movementY) + 'px';
-                    doc.style.left = toNumber(left, e.movementX) + 'px';
+                    doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px';
+                    doc.style.height = toNumber(doc.offsetHeight, e.movementY) + 'px';
+                    doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px';
                     break;
                 case "resizer-tr":
-                    doc.style.width = toNumber(width, -e.movementX) + 'px';
-                    doc.style.height = toNumber(height, -e.movementY) + 'px';
-                    doc.style.top = toNumber(top, e.movementY) + 'px';
+                    doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px';
+                    doc.style.height = toNumber(doc.offsetHeight, -e.movementY) + 'px';
+                    doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px';
                 case "resizer-tl":
-                    doc.style.width = toNumber(width, -e.movementX) + 'px';
-                    doc.style.height = toNumber(height, -e.movementY) + 'px';
-                    doc.style.top = toNumber(top, e.movementY) + 'px';
-                    doc.style.left = toNumber(left, e.movementX) + 'px';
+                    doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px';
+                    doc.style.height = toNumber(doc.offsetHeight, -e.movementY) + 'px';
+                    doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px';
+                    doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px';
                 case "resizable":
-                    doc.style.top = toNumber(top, e.movementY) + 'px';
-                    doc.style.left = toNumber(left, e.movementX) + 'px';
+                    doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px';
+                    doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px';
             }
-            // this.updateAll(height, width, top, left);
             return false;
         }
         return true;
@@ -1763,16 +1715,8 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         </>;
     }
 
-
     render() {
-        // trace();
-        const freeformclass = "collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : "-none");
-        const cenx = this.props.centeringShiftX();
-        const ceny = this.props.centeringShiftY();
-        const panx = -this.props.panX();
-        const pany = -this.props.panY();
-        const zoom = this.props.zoomScaling();
-        return <div className={freeformclass}
+        return <div className={"collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : "-none")}
             onScroll={e => {
                 const target = e.target as any;
                 if (getComputedStyle(target)?.overflow === "visible") {  // if collection is visible, then scrolling will mess things up since there are no scroll bars
@@ -1780,7 +1724,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
                 }
             }}
             style={{
-                transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)`,
+                transform: this.props.transform(),
                 transition: this.props.transition,
                 width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable.  This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
                 //willChange: "transform"
-- 
cgit v1.2.3-70-g09d2


From 684faaab165681c34fe824e1b6ac887d25b73f36 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 23 Feb 2021 17:18:14 -0500
Subject: reorg and cleanup a bit of colelctionfreeformview.  minor other
 cleanup.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 340 ++++++++++-----------
 src/client/views/nodes/DocumentView.tsx            |   6 +-
 src/fields/Doc.ts                                  |   9 +-
 3 files changed, 165 insertions(+), 190 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 18cf825a3..9041d9b21 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -13,7 +13,7 @@ import { ScriptField } from "../../../../fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
 import { TraceMobx } from "../../../../fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnFalse, setupMoveUpEvents, Utils, emptyFunction } from "../../../../Utils";
+import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs, DocUtils } from "../../../documents/Documents";
@@ -58,24 +58,20 @@ export const panZoomSchema = createSchema({
     _timecodeToShow: "number",
     _currentFrame: "number",
     _useClusters: "boolean",
-    fitToBox: "boolean",
+    _viewTransition: "string",
     _xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     _yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
-    _viewTransition: "string",
-    scrollHeight: "number",
-    fitX: "number",
-    fitY: "number",
-    fitW: "number",
-    fitH: "number"
+    _fitToBox: "boolean",
+    scrollHeight: "number"    // this will be set when the collection is an annotation overlay for a PDF/Webpage
 });
 
 type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof collectionSchema, typeof documentSchema, typeof pageSchema]>;
 const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentSchema, pageSchema);
 export type collectionFreeformViewProps = {
+    parentActive: (outsideReaction: boolean) => boolean;
     forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
     viewDefDivClick?: ScriptField;
     childPointerEvents?: boolean;
-    parentActive: (outsideReaction: boolean) => boolean;
     scaleField?: string;
     noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
     engineProps?: any;
@@ -94,29 +90,32 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private _wordPalette: Map<string, string> = new Map<string, string>();
     private _clusterDistance: number = 75;
     private _hitCluster: number = -1;
-    private _layoutComputeReaction: IReactionDisposer | undefined;
-    private _boundsReaction: IReactionDisposer | undefined;
+    private _disposers: { [name: string]: IReactionDisposer } = {};
     private _layoutPoolData = new ObservableMap<string, PoolData>();
     private _layoutSizeData = new ObservableMap<string, { width?: number, height?: number }>();
     private _cachedPool: Map<string, PoolData> = new Map();
     private _lastTap = 0;
     private _nudgeTime = 0;
-    private _thumbIdentifier?: number;
 
+    private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
+    private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
+    private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
+
+    @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
     @observable _hLines: number[] | undefined;
     @observable _vLines: number[] | undefined;
     @observable _pullCoords: number[] = [0, 0];
     @observable _pullDirection: string = "";
     @observable _showAnimTimeline = false;
-    @observable ChildDrag: DocumentView | undefined; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
-
-    @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
     @observable _clusterSets: (Doc[])[] = [];
     @observable _timelineRef = React.createRef<Timeline>();
     @observable _marqueeRef = React.createRef<HTMLDivElement>();
     @observable _focusFilters: Opt<string[]>; // docFilters that are overridden when previewing a link to an anchor which has docFilters set on it
     @observable _focusRangeFilters: Opt<string[]>; // docRangeFilters that are overridden when previewing a link to an anchor which has docRangeFilters set on it
+    @observable ChildDrag: DocumentView | undefined; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
 
+    @computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
+    @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); }
     @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     @computed get fitToContentVals() {
         return {
@@ -131,13 +130,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
     @computed get nativeWidth() { return this.fitToContent ? 0 : Doc.NativeWidth(this.Document); }
     @computed get nativeHeight() { return this.fitToContent ? 0 : Doc.NativeHeight(this.Document); }
-    private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
-    private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
-    private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
-    private panX = () => this.freeformData()?.panX ?? NumCast(this.Document._panX);
-    private panY = () => this.freeformData()?.panY ?? NumCast(this.Document._panY);
-    private zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1));
-    private contentTransform = () => `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`
     @computed get cachedCenteringShiftX(): number {
         const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
         return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling;  // shift so pan position is at center of window for non-overlay collections
@@ -156,16 +148,31 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return this.getTransformOverlay().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
     }
 
-    private getTransform = () => this.cachedGetTransform.copy();
-    private getLocalTransform = () => this.cachedGetLocalTransform.copy();
-    private getContainerTransform = () => this.cachedGetContainerTransform.copy();
-    private getTransformOverlay = () => this.getContainerTransform().translate(1, 1);
-    private addLiveTextBox = (newBox: Doc) => {
+    onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
+    onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
+    parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.props.parentActive?.(outsideReaction) || this.backgroundActive || this.layoutDoc._viewType === CollectionViewType.Pile ? true : false;
+    elementFunc = () => this._layoutElements;
+    freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined;
+    freeformDocFilters = () => this._focusFilters || this.docFilters();
+    freeformRangeDocFilters = () => this._focusRangeFilters || this.docRangeFilters();
+    panX = () => this.freeformData()?.panX ?? NumCast(this.Document._panX);
+    panY = () => this.freeformData()?.panY ?? NumCast(this.Document._panY);
+    zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1));
+    contentTransform = () => `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`
+    getTransform = () => this.cachedGetTransform.copy();
+    getLocalTransform = () => this.cachedGetLocalTransform.copy();
+    getContainerTransform = () => this.cachedGetContainerTransform.copy();
+    getTransformOverlay = () => this.getContainerTransform().translate(1, 1);
+    getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
+    addLiveTextBox = (newBox: Doc) => {
         FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
         this.addDocument(newBox);
     }
-
-    addDocument = action((newBox: Doc | Doc[]) => {
+    selectDocuments = (docs: Doc[]) => {
+        SelectionManager.DeselectAll();
+        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true));
+    }
+    addDocument = (newBox: Doc | Doc[]) => {
         let retVal = false;
         if (newBox instanceof Doc) {
             retVal = this.props.addDocument?.(newBox) || false;
@@ -191,25 +198,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }
         }
         return retVal;
-    });
-
-    private selectDocuments = (docs: Doc[]) => {
-        SelectionManager.DeselectAll();
-        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true));
-    }
-    public isCurrent(doc: Doc) {
-        const dispTime = NumCast(doc._timecodeToShow, -1);
-        const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5);
-        const curTime = NumCast(this.Document._currentTimecode, -1);
-        return dispTime === -1 || ((curTime - dispTime) >= -1e-4 && curTime <= endTime);
-    }
-
-    public getActiveDocuments = () => {
-        return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
-    }
-
-    onExternalDrop = (e: React.DragEvent) => {
-        return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY));
     }
 
     updateGroupBounds = () => {
@@ -231,6 +219,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.layoutDoc.y = pbounds.y;
     }
 
+    isCurrent(doc: Doc) {
+        const dispTime = NumCast(doc._timecodeToShow, -1);
+        const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5);
+        const curTime = NumCast(this.Document._currentTimecode, -1);
+        return dispTime === -1 || ((curTime - dispTime) >= -1e-4 && curTime <= endTime);
+    }
+
     @action
     internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
         if (!this.ChildDrag && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false;
@@ -271,7 +266,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     @undoBatch
-    @action
     internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) {
         const dragDoc = annoDragData.dropDocument!;
         const dropPos = [NumCast(dragDoc.x), NumCast(dragDoc.y)];
@@ -282,7 +276,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     @undoBatch
-    @action
     internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
         if (linkDragData.linkSourceDocument === this.props.Document || this.props.Document.annotationOn) return false;
         if (!linkDragData.linkSourceDocument.context || StrCast(Cast(linkDragData.linkSourceDocument.context, Doc, null)?.type) === DocumentType.COL) {
@@ -299,7 +292,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
     }
 
-    @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         const [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
         if (this.isAnnotationOverlay !== true && de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp);
@@ -308,6 +300,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return false;
     }
 
+    onExternalDrop = (e: React.DragEvent) => {
+        return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY));
+    }
+
     pickCluster(probe: number[]) {
         return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => {
             const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1);
@@ -435,6 +431,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return styleProp;
     }
 
+    trySelectCluster = (addToSel: boolean) => {
+        if (this._hitCluster !== -1) {
+            !addToSel && SelectionManager.DeselectAll();
+            const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === this._hitCluster);
+            this.selectDocuments(eles);
+            return true;
+        }
+        return false;
+    }
+
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
@@ -774,10 +780,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     cleanUpInteractions = () => {
         switch (this._pullDirection) {
-            case "left":
-            case "right":
-            case "top":
-            case "bottom":
+            case "left": case "right": case "top": case "bottom":
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), this._pullDirection);
         }
 
@@ -868,7 +871,21 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
     }
 
-    bringToFront = action((doc: Doc, sendToBack?: boolean) => {
+    @action
+    nudge = (x: number, y: number) => {
+        if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ||
+            this.props.ContainingCollectionDoc._panX !== undefined) { // bcz: this isn't ideal, but want to try it out...
+            this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
+                NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "transform 500ms", true);
+            this._nudgeTime = Date.now();
+            setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document._viewTransition = undefined), 500);
+            return true;
+        }
+        return false;
+    }
+
+    @action
+    bringToFront = (doc: Doc, sendToBack?: boolean) => {
         if (sendToBack || StrListCast(doc.layers).includes(StyleLayers.Background)) {
             doc.zIndex = 0;
         } else if (doc.isInkMask) {
@@ -883,7 +900,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }
             doc.zIndex = zlast + 1;
         }
-    });
+    }
 
     scaleAtPt(docpt: number[], scale: number) {
         if (this.Document._isGroup) return;
@@ -923,7 +940,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey], transition: this.Document._viewTransition };
             const newState = HistoryUtil.getState();
             const cantTransform = this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
-            const { panX, panY, scale } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
+            const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(layoutdoc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
             if (!cantTransform) {   // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
                 newState.initializers![this.Document[Id]] = { panX: panX, panY: panY };
                 HistoryUtil.pushState(newState);
@@ -960,14 +977,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                         NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1);
 
             this.props.focus(cantTransform ? doc : this.rootDoc, {
-                ...options, docTransform: xf,
+                ...options,
+                docTransform: xf,
                 afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res =>
                     setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime))))
             });
         }
     }
 
-    setPanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
+    calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
         const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1);
         const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1);
         const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
@@ -1000,43 +1018,51 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
     }
 
-    onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
-    onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
-    parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.props.parentActive?.(outsideReaction) || this.backgroundActive || this.layoutDoc._viewType === CollectionViewType.Pile ? true : false;
-    getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
-        return {
-            addDocument: this.props.addDocument,
-            removeDocument: this.props.removeDocument,
-            moveDocument: this.props.moveDocument,
-            pinToPres: this.props.pinToPres,
-            whenActiveChanged: this.props.whenActiveChanged,
-            parentActive: this.parentActive,
-            docViewPath: this.props.docViewPath,
-            DataDoc: childData,
-            Document: childLayout,
-            ContainingCollectionView: this.props.CollectionView,
-            ContainingCollectionDoc: this.props.Document,
-            LayoutTemplate: childLayout.z ? undefined : this.props.childLayoutTemplate,
-            LayoutTemplateString: childLayout.z ? undefined : this.props.childLayoutString,
-            rootSelected: childData ? this.rootSelected : returnFalse,
-            onClick: this.onChildClickHandler,
-            onDoubleClick: this.onChildDoubleClickHandler,
-            ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
-            PanelWidth: childLayout[WidthSym],
-            PanelHeight: childLayout[HeightSym],
-            docFilters: this.freeformDocFilters,
-            docRangeFilters: this.freeformRangeDocFilters,
-            searchFilterDocs: this.searchFilterDocs,
-            focus: this.focusDocument,
-            styleProvider: this.getClusterColor,
-            layerProvider: this.props.layerProvider,
-            freezeDimensions: this.props.childFreezeDimensions,
-            dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
-            bringToFront: this.bringToFront,
-            addDocTab: this.addDocTab,
-            renderDepth: this.props.renderDepth + 1,
-            dontRegisterView: this.props.dontRegisterView,
-        };
+    getChildDocView(entry: PoolData) {
+        const childLayout = entry.pair.layout;
+        const childData = entry.pair.data;
+        const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+        return <CollectionFreeFormDocumentView key={childLayout[Id] + (entry.replica || "")}
+            DataDoc={childData}
+            Document={childLayout}
+            renderDepth={this.props.renderDepth + 1}
+            replica={entry.replica}
+            ContainingCollectionView={this.props.CollectionView}
+            ContainingCollectionDoc={this.props.Document}
+            CollectionFreeFormView={this}
+            LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
+            LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
+            rootSelected={childData ? this.rootSelected : returnFalse}
+            onClick={this.onChildClickHandler}
+            onDoubleClick={this.onChildDoubleClickHandler}
+            ScreenToLocalTransform={childLayout.z ? this.getTransformOverlay : this.getTransform}
+            PanelWidth={childLayout[WidthSym]}
+            PanelHeight={childLayout[HeightSym]}
+            docFilters={this.freeformDocFilters}
+            docRangeFilters={this.freeformRangeDocFilters}
+            searchFilterDocs={this.searchFilterDocs}
+            focus={this.focusDocument}
+            addDocTab={this.addDocTab}
+            addDocument={this.props.addDocument}
+            removeDocument={this.props.removeDocument}
+            moveDocument={this.props.moveDocument}
+            pinToPres={this.props.pinToPres}
+            whenActiveChanged={this.props.whenActiveChanged}
+            parentActive={this.parentActive}
+            docViewPath={this.props.docViewPath}
+            styleProvider={this.getClusterColor}
+            layerProvider={this.props.layerProvider}
+            dataProvider={this.childDataProvider}
+            sizeProvider={this.childSizeProvider}
+            freezeDimensions={this.props.childFreezeDimensions}
+            dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
+            bringToFront={this.bringToFront}
+            dontRegisterView={this.props.dontRegisterView}
+            pointerEvents={this.backgroundActive || this.props.childPointerEvents ? "all" :
+                (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : undefined}
+            jitterRotation={NumCast(this.props.Document._jitterRotation) || ((Doc.UserDoc().renderStyle === "comic" ? 10 : 0))}
+        //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
+        />;
     }
 
     addDocTab = action((doc: Doc, where: string) => {
@@ -1075,15 +1101,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         };
     }
 
-    viewDefsToJSX = (views: ViewDefBounds[]) => {
-        return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
-    }
-
     onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
         (this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload });
         e.stopPropagation();
     }
-    private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
+
+    viewDefsToJSX = (views: ViewDefBounds[]) => {
+        return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
+    }
+
+    viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
         const { x, y, z } = viewDef;
         const color = StrCast(viewDef.color);
         const width = Cast(viewDef.width, "number");
@@ -1165,24 +1192,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this._cachedPool.clear();
         Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
         const elements = computedElementData.slice();
-        const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
         Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry =>
             elements.push({
-                ele: <CollectionFreeFormDocumentView
-                    key={entry[1].pair.layout[Id] + (entry[1].replica || "")}
-                    {...this.getChildDocumentViewProps(entry[1].pair.layout, entry[1].pair.data)}
-                    replica={entry[1].replica}
-                    CollectionFreeFormView={this}
-                    dataProvider={this.childDataProvider}
-                    sizeProvider={this.childSizeProvider}
-                    layerProvider={this.props.layerProvider}
-                    pointerEvents={this.backgroundActive || this.props.childPointerEvents ?
-                        "all" :
-                        (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : undefined}
-                    jitterRotation={NumCast(this.props.Document._jitterRotation) || ((Doc.UserDoc().renderStyle === "comic" ? 10 : 0))}
-                    //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
-                    freezeDimensions={BoolCast(this.props.childFreezeDimensions)}
-                />,
+                ele: this.getChildDocView(entry[1]),
                 bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
             }));
 
@@ -1195,8 +1207,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return elements;
     }
 
-    freeformDocFilters = () => this._focusFilters || this.docFilters();
-    freeformRangeDocFilters = () => this._focusRangeFilters || this.docRangeFilters();
     @action
     setViewSpec = (anchor: Doc, preview: boolean) => {
         if (preview) {
@@ -1226,16 +1236,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
         return anchor;
     }
-    freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined;
+
     @action
     componentDidMount() {
         super.componentDidMount?.();
         this.props.setContentView?.(this);
-        this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
+        this._disposers.layoutComputation = reaction(() => this.doLayoutComputation,
             (elements) => this._layoutElements = elements || [],
             { fireImmediately: true, name: "doLayout" });
         if (!this.props.isAnnotationOverlay) {
-            this._boundsReaction = reaction(() => this.contentBounds,
+            this._disposers.contentBounds = reaction(() => this.contentBounds,
                 bounds => (!this.fitToContent && this._layoutElements?.length) && setTimeout(() => {
                     const rbounds = Cast(this.Document._renderContentBounds, listSpec("number"), [0, 0, 0, 0]);
                     if (rbounds[0] !== bounds.x || rbounds[1] !== bounds.y || rbounds[2] !== bounds.r || rbounds[3] !== bounds.b) {
@@ -1248,14 +1258,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     componentWillUnmount() {
-        this._layoutComputeReaction?.();
-        this._boundsReaction?.();
+        Object.values(this._disposers).forEach(disposer => disposer?.());
         this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
     }
 
-    @computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
-    elementFunc = () => this._layoutElements;
-
     @action
     onCursorMove = (e: React.PointerEvent) => {
         //  super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
@@ -1281,7 +1287,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         e.stopPropagation();
     }
 
-    promoteCollection = undoBatch(action(() => {
+    @undoBatch
+    promoteCollection = () => {
         const childDocs = this.childDocs.slice();
         childDocs.forEach(doc => {
             const scr = this.getTransform().inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
@@ -1290,10 +1297,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         });
         this.props.addDocTab(childDocs as any as Doc, "inParent");
         this.props.ContainingCollectionView?.removeDocument(this.props.Document);
-    }));
+    }
 
     @undoBatch
-    layoutDocsInGrid = action(() => {
+    layoutDocsInGrid = () => {
         const docs = this.childLayoutPairs;
         const startX = this.Document._panX || 0;
         let x = startX;
@@ -1311,19 +1318,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 y += height + 20;
             }
         });
-    });
+    }
 
     @undoBatch
-    @action
-    toggleNativeDimensions = () => {
-        Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight);
-    }
+    toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight);
 
     @undoBatch
     @action
-    toggleLockTransform = (): void => {
-        this.layoutDoc._lockedTransform = this.layoutDoc._lockedTransform ? undefined : true;
-    }
+    toggleLockTransform = () => this.layoutDoc._lockedTransform = this.layoutDoc._lockedTransform ? undefined : true;
 
     onContextMenu = (e: React.MouseEvent) => {
         if (this.props.isAnnotationOverlay || this.props.Document.annotationOn || !ContextMenu.Instance) return;
@@ -1430,24 +1432,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />];
     }
 
-    @computed get placeholder() {
-        return <div className="collectionfreeformview-placeholder" style={{ background: this.Document.backgroundColor }}>
-            <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span>
-        </div>;
-    }
-
-    nudge = action((x: number, y: number) => {
-        if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ||
-            this.props.ContainingCollectionDoc._panX !== undefined) { // bcz: this isn't ideal, but want to try it out...
-            this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
-                NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "transform 500ms", true);
-            this._nudgeTime = Date.now();
-            setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document._viewTransition = undefined), 500);
-            return true;
-        }
-        return false;
-    });
-
     chooseGridSpace = (gridSpace: number): number => {
         const divisions = this.props.PanelWidth() / this.zoomScaling() / gridSpace + 3;
         return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10);
@@ -1486,14 +1470,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }} />;
     }
 
-    trySelectCluster = (addToSel: boolean) => {
-        if (this._hitCluster !== -1) {
-            !addToSel && SelectionManager.DeselectAll();
-            const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === this._hitCluster);
-            this.selectDocuments(eles);
-            return true;
-        }
-        return false;
+    @computed get placeholder() {
+        return <div className="collectionfreeformview-placeholder" style={{ background: this.Document.backgroundColor }}>
+            <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span>
+        </div>;
     }
 
     @computed get marqueeView() {
@@ -1536,7 +1516,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale ? wscale : hscale;
     }
-    @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); }
 
     render() {
         TraceMobx();
@@ -1555,7 +1534,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
                 transformOrigin: this.contentScaling ? "left top" : "",
                 width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
-                height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
+                height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
             }}>
             {this.Document._freeformLOD && !this.props.active() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0 ?
                 this.placeholder : this.marqueeView}
@@ -1667,17 +1646,17 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         const activeItem = PresBox.Instance.activeItem;
         // const targetDoc = PresBox.Instance.targetDoc;
         if (activeItem && activeItem.presPinView && activeItem.id) {
-            const vfLeft: number = NumCast(activeItem.presPinViewX);
-            const vfTop: number = NumCast(activeItem.presPinViewY);
-            const vfWidth: number = 100;
-            const vfHeight: number = 100;
+            const left = NumCast(activeItem.presPinViewX);
+            const top = NumCast(activeItem.presPinViewY);
+            const width = 100;
+            const height = 100;
             return !this.props.presPinView ? (null) :
-                <div key="resizable" className="resizable" onPointerDown={this.onPointerDown} style={{ width: vfWidth, height: vfHeight, top: vfTop, left: vfLeft, position: 'absolute' }}>
+                <div key="resizable" className="resizable" onPointerDown={this.onPointerDown} style={{ width, height, top, left, position: 'absolute' }}>
                     <div className='resizers' key={'resizer' + activeItem.id}>
-                        <div className='resizer top-left' onPointerDown={this.onPointerDown}></div>
-                        <div className='resizer top-right' onPointerDown={this.onPointerDown}></div>
-                        <div className='resizer bottom-left' onPointerDown={this.onPointerDown}></div>
-                        <div className='resizer bottom-right' onPointerDown={this.onPointerDown}></div>
+                        <div className='resizer top-left' onPointerDown={this.onPointerDown} />
+                        <div className='resizer top-right' onPointerDown={this.onPointerDown} />
+                        <div className='resizer bottom-left' onPointerDown={this.onPointerDown} />
+                        <div className='resizer bottom-right' onPointerDown={this.onPointerDown} />
                     </div>
                 </div>;
         }
@@ -1697,16 +1676,13 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
             <div key="presorder">{PresBox.Instance.order}</div>
             <svg key="svg" className={presPaths}>
                 <defs>
-                    <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5"
-                        orient="auto" overflow="visible">
+                    <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
                         <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="white" fillOpacity="0.8" />
                     </marker>
-                    <marker id="markerSquareFilled" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5"
-                        orient="auto" overflow="visible">
+                    <marker id="markerSquareFilled" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
                         <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="#69a6db" />
                     </marker>
-                    <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4"
-                        orient="auto" overflow="visible">
+                    <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4" orient="auto" overflow="visible">
                         <path d="M2,2 L2,6 L6,4 L2,2 Z" stroke="#69a6db" strokeLinejoin="round" strokeWidth="1" fill="white" fillOpacity="0.8" />
                     </marker>
                 </defs>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b79c1d474..e31101459 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -378,8 +378,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     startDragging(x: number, y: number, dropAction: dropActionType) {
         if (this._mainCont.current) {
-            const ffview = this.props.CollectionFreeFormDocumentView;
-            ffview && runInAction(() => (ffview().props.CollectionFreeFormView.ChildDrag = this.props.DocumentView()));
             const dragData = new DragManager.DocumentDragData([this.props.Document]);
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0);
             dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top);
@@ -387,8 +385,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             dragData.treeViewDoc = this.props.treeViewDoc;
             dragData.removeDocument = this.props.removeDocument;
             dragData.moveDocument = this.props.moveDocument;
+            const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+            ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
             DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.layoutDoc.onDragStart },
-                () => setTimeout(action(() => ffview && (ffview().props.CollectionFreeFormView.ChildDrag = undefined)))); // this needs to happen after the drop event is processed.
+                () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))); // this needs to happen after the drop event is processed.
         }
     }
 
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 4ae436083..ce5b08440 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -25,6 +25,7 @@ import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
 import { AudioField, ImageField, PdfField, VideoField, WebField } from "./URLField";
 import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util";
 import JSZip = require("jszip");
+import { prefix } from "@fortawesome/free-regular-svg-icons";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -230,10 +231,11 @@ export class Doc extends RefField {
         const sameAuthor = this.author === Doc.CurrentUserEmail;
         if (set) {
             for (const key in set) {
-                if (!key.startsWith("fields.")) {
+                const fprefix = "fields.";
+                if (!key.startsWith(fprefix)) {
                     continue;
                 }
-                const fKey = key.substring(7);
+                const fKey = key.substring(fprefix.length);
                 const fn = async () => {
                     const value = await SerializationHelper.Deserialize(set[key]);
                     const prev = GetEffectiveAcl(this);
@@ -246,9 +248,6 @@ export class Doc extends RefField {
                     if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) {
                         DocServer.GetRefField(this[Id], true);
                     }
-                    // if (prev !== AclPrivate && GetEffectiveAcl(this) === AclPrivate) {
-                    //     this[FieldsSym](true);
-                    // }
                 };
                 if (sameAuthor || fKey.startsWith("acl") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
                     delete this[CachedUpdates][fKey];
-- 
cgit v1.2.3-70-g09d2


From 5a2589c766ace01908a6b151f7170a90f425610c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 23 Feb 2021 20:42:49 -0500
Subject: fixed pdfs so that if you activate and immediately start selecting
 text it works.  cleaned up some more in collectionFreeFormView by improving
 layoutDocsInGrid

---
 src/client/views/MarqueeAnnotator.tsx              | 60 +++++++++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 73 +++++++---------------
 .../collections/collectionFreeForm/MarqueeView.tsx | 15 ++---
 src/client/views/pdf/PDFViewer.tsx                 |  4 +-
 4 files changed, 61 insertions(+), 91 deletions(-)

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

diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 03afbe7bf..6e8f9a2df 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -58,6 +58,36 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         this._height = this._width = 0;
         document.addEventListener("pointermove", this.onSelectMove);
         document.addEventListener("pointerup", this.onSelectEnd);
+
+        AnchorMenu.Instance.Highlight = this.highlight;
+        /**
+         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.  
+         * It also initiates a Drag/Drop interaction to place the text annotation.
+         */
+        AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => {
+            e.preventDefault();
+            e.stopPropagation();
+            const targetCreator = () => {
+                const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100);
+                FormattedTextBox.SelectOnLoad = target[Id];
+                return target;
+            };
+            const anchorCreator = () => {
+                const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color
+                annoDoc.isLinkButton = true; // prevents link button from showing up --- maybe not a good thing?
+                this.props.addDocument(annoDoc);
+                return annoDoc;
+            };
+            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.rootDoc, anchorCreator, targetCreator), e.pageX, e.pageY, {
+                dragComplete: e => {
+                    if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) {
+                        e.linkDocument = DocUtils.MakeLink({ doc: e.annoDragData.annotationDocument }, { doc: e.annoDragData.dropDocument }, "Annotation");
+                        e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.rootDoc;
+                    }
+                    e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument
+                }
+            });
+        });
     }
     componentWillUnmount() {
         document.removeEventListener("pointermove", this.onSelectMove);
@@ -154,36 +184,6 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
             AnchorMenu.Instance.Marquee = { left: this._left, top: this._top, width: this._width, height: this._height };
         }
 
-        AnchorMenu.Instance.Highlight = this.highlight;
-        /**
-         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.  
-         * It also initiates a Drag/Drop interaction to place the text annotation.
-         */
-        AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => {
-            e.preventDefault();
-            e.stopPropagation();
-            const targetCreator = () => {
-                const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100);
-                FormattedTextBox.SelectOnLoad = target[Id];
-                return target;
-            };
-            const anchorCreator = () => {
-                const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color
-                annoDoc.isLinkButton = true; // prevents link button from showing up --- maybe not a good thing?
-                this.props.addDocument(annoDoc);
-                return annoDoc;
-            };
-            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.rootDoc, anchorCreator, targetCreator), e.pageX, e.pageY, {
-                dragComplete: e => {
-                    if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) {
-                        e.linkDocument = DocUtils.MakeLink({ doc: e.annoDragData.annotationDocument }, { doc: e.annoDragData.dropDocument }, "Annotation");
-                        e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.rootDoc;
-                    }
-                    e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument
-                }
-            });
-        });
-
         if (this._width > 10 || this._height > 10) {  // configure and show the annotation/link menu if a the drag region is big enough
             const marquees = this.props.mainCont.getElementsByClassName("marqueeAnnotator-dragBox");
             if (marquees?.length) { // copy the temporary marquee to allow for multiple selections (not currently available though).
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9041d9b21..d9cd4fd4e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -175,9 +175,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     addDocument = (newBox: Doc | Doc[]) => {
         let retVal = false;
         if (newBox instanceof Doc) {
-            retVal = this.props.addDocument?.(newBox) || false;
-            retVal && this.bringToFront(newBox);
-            retVal && this.updateCluster(newBox);
+            if (retVal = this.props.addDocument?.(newBox) || false) {
+                this.bringToFront(newBox);
+                this.updateCluster(newBox);
+            }
         } else {
             retVal = this.props.addDocument?.(newBox) || false;
             // bcz: deal with clusters
@@ -267,11 +268,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @undoBatch
     internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) {
-        const dragDoc = annoDragData.dropDocument!;
-        const dropPos = [NumCast(dragDoc.x), NumCast(dragDoc.y)];
-        dragDoc.x = xp - annoDragData.offset[0] + (NumCast(dragDoc.x) - dropPos[0]);
-        dragDoc.y = yp - annoDragData.offset[1] + (NumCast(dragDoc.y) - dropPos[1]);
-        this.bringToFront(dragDoc);
+        annoDragData.dropDocument!.x = xp - annoDragData.offset[0];
+        annoDragData.dropDocument!.y = yp - annoDragData.offset[1];
+        this.bringToFront(annoDragData.dropDocument!);
         return true;
     }
 
@@ -636,26 +635,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @action
     onPointerMove = (e: PointerEvent): void => {
+        if (this.props.Document._isGroup) return; // groups don't pan when dragged -- instead let the event go through to allow the group itself to drag
+        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return;
         if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
-            if (this.props.active(true)) {
-                e.stopPropagation();
-            }
-            return;
-        }
-        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
-            return;
-        }
-        if (!e.cancelBubble) {
-            if (this.props.Document._isGroup) return; // groups don't pan when dragged -- instead let the event go through to allow the group itself to drag
+            if (this.props.active(true)) e.stopPropagation();
+        } else if (!e.cancelBubble) {
             if (Doc.GetSelectedTool() === InkTool.None) {
                 if (this.tryDragCluster(e, this._hitCluster)) {
-                    e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
-                    e.preventDefault();
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
-                    return;
                 }
-                (!MarqueeView.DragMarquee || e.altKey) && this.pan(e);
+                else this.pan(e);
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
             e.preventDefault();
@@ -821,18 +811,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
             e.stopPropagation();
         }
-        else if (this.props.active(true)) {
-            if (!e.ctrlKey && MarqueeView.DragMarquee) {
-                this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true);
-                e.stopPropagation();
-                e.preventDefault();
-            }
-            else if (!this.Document._isGroup) {
-                e.stopPropagation();
-                e.preventDefault();
-                this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
-            }
-
+        else if (this.props.active(true) && !this.Document._isGroup) {
+            e.stopPropagation();
+            e.preventDefault();
+            this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
         }
         this.props.Document.targetScale = NumCast(this.props.Document[this.scaleFieldKey]);
     }
@@ -1301,22 +1283,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @undoBatch
     layoutDocsInGrid = () => {
-        const docs = this.childLayoutPairs;
-        const 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.layout._width)));
-        const height = Math.max(...docs.map(doc => NumCast(doc.layout._height)));
-        docs.forEach(pair => {
-            pair.layout.x = x;
-            pair.layout.y = y;
-            x += width + 20;
-            if (++i === 6) {
-                i = 0;
-                x = startX;
-                y += height + 20;
-            }
+        const docs = this.childLayoutPairs.map(pair => pair.layout);
+        const width = Math.max(...docs.map(doc => NumCast(doc._width))) + 20;
+        const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20;
+        const dim = Math.ceil(Math.sqrt(docs.length));
+        docs.forEach((doc, i) => {
+            doc.x = (this.Document._panX || 0) + (i % dim) * width - width * dim / 2;
+            doc.y = (this.Document._panY || 0) + Math.floor(i / dim) * height - height * dim / 2;
         });
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 4008a20b3..e61cf83bb 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -46,7 +46,6 @@ interface MarqueeViewProps {
 export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps>
 {
     private _commandExecuted = false;
-    @observable public static DragMarquee = false;
     @observable _lastX: number = 0;
     @observable _lastY: number = 0;
     @observable _downX: number = 0;
@@ -220,8 +219,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         this._downY = this._lastY = e.clientY;
         if (!(e.nativeEvent as any).marqueeHit) {
             (e.nativeEvent as any).marqueeHit = true;
-            // allow marquee if right click OR alt+left click OR space bar + left click
-            if (e.button === 2 || (e.button === 0 && (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))))) {
+            // allow marquee if right click OR alt+left click
+            if (e.button === 2 || (e.button === 0 && e.altKey)) {
                 // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) {
                 this.setPreviewCursor(e.clientX, e.clientY, true);
                 // (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
@@ -251,9 +250,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         } else {
             this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this
         }
-        if (e.altKey || MarqueeView.DragMarquee) {
-            e.preventDefault();
-        }
+        e.altKey && e.preventDefault();
     }
 
     @action
@@ -290,9 +287,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         }
         this.cleanupInteractions(true, this._commandExecuted);
 
-        if (e.altKey || MarqueeView.DragMarquee) {
-            e.preventDefault();
-        }
+        e.altKey && e.preventDefault();
     }
 
     clearSelection() {
@@ -659,7 +654,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             style={{
                 overflow: (!this.props.ContainingCollectionView && this.props.isAnnotationOverlay) ? "visible" :
                     StrCast(this.props.Document._overflow),
-                cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand"
+                cursor: "hand"
             }}
             onDragOver={e => e.preventDefault()}
             onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 7687690b2..ca6dc87ae 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -385,10 +385,12 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
             this._setPreviewCursor?.(e.clientX, e.clientY, true);
         }
         if (!e.altKey && e.button === 0 && this.active(true)) {
+            this._marqueeing = [e.clientX, e.clientY];
             if (e.target && ((e.target as any).className.includes("endOfContent") || ((e.target as any).parentElement.className !== "textLayer"))) {
-                this._marqueeing = [e.clientX, e.clientY];  // if texLayer is hit, then we select text instead of using a marquee
                 document.addEventListener("pointermove", this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called
             } else {
+                // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
+                setTimeout(action(() => this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
                 // clear out old marquees and initialize menu for new selection
                 AnchorMenu.Instance.Status = "marquee";
                 this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
-- 
cgit v1.2.3-70-g09d2


From 953e9dacf886aa5dff7331ca1f7ba7ba7f5373dd Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 23 Feb 2021 21:46:57 -0500
Subject: cleaned up viewTransitions in CollectionFreeFormView to not write to
 document for transient effects.

---
 src/client/views/Touchable.tsx                     | 39 ++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 88 ++++++++++------------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 3 files changed, 47 insertions(+), 82 deletions(-)

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

diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index bb9e108cb..789756a78 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -11,7 +11,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
     private holdMoveDisposer?: InteractionUtils.MultiTouchEventDisposer;
     private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
-
     protected abstract _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     protected _touchDrag: boolean = false;
     protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
@@ -86,19 +85,13 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
         this._touchDrag = true;
         switch (myTouches.length) {
-            case 1:
-                this.handle1PointerMove(te, me);
-                break;
-            case 2:
-                this.handle2PointersMove(te, me);
-                break;
+            case 1: this.handle1PointerMove(te, me); break;
+            case 2: this.handle2PointersMove(te, me); break;
         }
 
         for (const pt of me.touches) {
-            if (pt) {
-                if (this.prevPoints.has(pt.identifier)) {
-                    this.prevPoints.set(pt.identifier, pt);
-                }
+            if (pt && this.prevPoints.has(pt.identifier)) {
+                this.prevPoints.set(pt.identifier, pt);
             }
         }
     }
@@ -166,7 +159,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         this.removeHoldEndListeners();
         this.addHoldMoveListeners();
         this.addHoldEndListeners();
-
     }
 
     addMoveListeners = () => {
@@ -174,21 +166,12 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         document.addEventListener("dashOnTouchMove", handler);
         this.moveDisposer = () => document.removeEventListener("dashOnTouchMove", handler);
     }
-
-    removeMoveListeners = () => {
-        this.moveDisposer && this.moveDisposer();
-    }
-
     addEndListeners = () => {
         const handler = (e: Event) => this.onTouchEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
         document.addEventListener("dashOnTouchEnd", handler);
         this.endDisposer = () => document.removeEventListener("dashOnTouchEnd", handler);
     }
 
-    removeEndListeners = () => {
-        this.endDisposer && this.endDisposer();
-    }
-
     addHoldMoveListeners = () => {
         const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
         document.addEventListener("dashOnTouchHoldMove", handler);
@@ -201,21 +184,16 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         this.holdEndDisposer = () => document.removeEventListener("dashOnTouchHoldEnd", handler);
     }
 
-    removeHoldMoveListeners = () => {
-        this.holdMoveDisposer && this.holdMoveDisposer();
-    }
-
-    removeHoldEndListeners = () => {
-        this.holdEndDisposer && this.holdEndDisposer();
-    }
-
+    removeMoveListeners = () => this.moveDisposer?.();
+    removeEndListeners = () => this.endDisposer?.();
+    removeHoldMoveListeners = () => this.holdMoveDisposer?.();
+    removeHoldEndListeners = () => this.holdEndDisposer?.();
 
     handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
         // e.stopPropagation();
         // me.touchEvent.stopPropagation();
     }
 
-
     handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
         e.stopPropagation();
         me.touchEvent.stopPropagation();
@@ -226,7 +204,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         me.touchEvent.preventDefault();
     }
 
-
     handleHandDown = (e: React.TouchEvent) => {
         // e.stopPropagation();
         // e.preventDefault();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d9cd4fd4e..9fa5b8670 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -81,6 +81,7 @@ export type collectionFreeformViewProps = {
 export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, Partial<collectionFreeformViewProps>>(PanZoomDocument) {
     public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
 
+    private _lastNudge: any;
     private _lastX: number = 0;
     private _lastY: number = 0;
     private _downX: number = 0;
@@ -95,13 +96,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private _layoutSizeData = new ObservableMap<string, { width?: number, height?: number }>();
     private _cachedPool: Map<string, PoolData> = new Map();
     private _lastTap = 0;
-    private _nudgeTime = 0;
 
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
     private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
 
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
+    @observable _viewTransition: number = 0;  // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan.  set to 0 to use document's transition time or default of 0
     @observable _hLines: number[] | undefined;
     @observable _vLines: number[] | undefined;
     @observable _pullCoords: number[] = [0, 0];
@@ -324,11 +325,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             if (ptsParent) {
                 const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster);
                 const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!);
-                const de = new DragManager.DocumentDragData(eles);
-                de.moveDocument = this.props.moveDocument;
                 const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
+                const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? "alias" : undefined);
+                de.moveDocument = this.props.moveDocument;
                 de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
-                de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
                 DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, { hideSource: !de.dropAction });
                 return true;
             }
@@ -423,8 +423,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 styleProp = colors[cluster % colors.length];
                 const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
                 // 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 => !StrListCast(s.layers).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor));
-                set && set.filter(s => StrListCast(s.layers).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor));
+                set?.filter(s => !StrListCast(s.layers).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor));
+                set?.filter(s => StrListCast(s.layers).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor));
             }
         } //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray";
         return styleProp;
@@ -612,14 +612,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     onClick = (e: React.MouseEvent) => {
         if ((Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
-            if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a double click 
-                if (e.shiftKey) {
-                    if (this.layoutDoc.targetScale) {
-                        this.scaleAtPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
-                    }
-                    e.stopPropagation();
-                    e.preventDefault();
-                }
+            if (e.shiftKey && Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a shift + double click 
+                this.layoutDoc.targetScale && this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
+                e.stopPropagation();
+                e.preventDefault();
             }
             this._lastTap = Date.now();
         }
@@ -628,7 +624,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @action
     pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true);
+        this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, 0, true);
         this._lastX = e.clientX;
         this._lastY = e.clientY;
     }
@@ -655,8 +651,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
         if (!e.cancelBubble) {
             const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-            const pt = myTouches[0];
-            if (pt) {
+            if (myTouches[0]) {
                 if (Doc.GetSelectedTool() === InkTool.None) {
                     if (this.tryDragCluster(e, this._hitCluster)) {
                         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
@@ -665,7 +660,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                         document.removeEventListener("pointerup", this.onPointerUp);
                         return;
                     }
-                    this.pan(pt);
+                    this.pan(myTouches[0]);
                 }
             }
             // e.stopPropagation();
@@ -796,8 +791,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         if (deltaScale * invTransform.Scale < 1 && this.isAnnotationOverlay) {
             deltaScale = 1 / invTransform.Scale;
         }
-        const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
 
+        const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
         if (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling()) {
             const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 20);
             this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
@@ -820,7 +815,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     @action
-    setPan(panX: number, panY: number, panType: string = "None", clamp: boolean = false) {
+    setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) {
         if (!this.isAnnotationOverlay && clamp) {
             // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
             const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
@@ -844,7 +839,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }
         }
         if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || CurrentUserUtils.OverlayDocs.includes(this.Document)) {
-            this.Document._viewTransition = panType;
+            this._viewTransition = panTime;
             const scale = this.getLocalTransform().inverse().Scale;
             const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
             const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY));
@@ -854,13 +849,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     @action
-    nudge = (x: number, y: number) => {
+    nudge = (x: number, y: number, nudgeTime: number = 500) => {
         if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ||
             this.props.ContainingCollectionDoc._panX !== undefined) { // bcz: this isn't ideal, but want to try it out...
             this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
-                NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "transform 500ms", true);
-            this._nudgeTime = Date.now();
-            setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document._viewTransition = undefined), 500);
+                NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), nudgeTime, true);
+            this._lastNudge && clearTimeout(this._lastNudge);
+            this._lastNudge = setTimeout(action(() => this._viewTransition = 0), nudgeTime);
             return true;
         }
         return false;
@@ -884,10 +879,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
     }
 
-    scaleAtPt(docpt: number[], scale: number) {
+    @action
+    zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
         if (this.Document._isGroup) return;
+        setTimeout(action(() => this._viewTransition = 0), this._viewTransition = transitionTime); // set transition to be smooth, then reset 
         const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
-        this.Document._viewTransition = "transform 500ms";
         this.layoutDoc[this.scaleFieldKey] = scale;
         const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
         const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
@@ -919,7 +915,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         } else {
             const xfToCollection = options?.docTransform ?? Transform.Identity();
             const layoutdoc = Doc.Layout(doc);
-            const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey], transition: this.Document._viewTransition };
+            const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey] };
             const newState = HistoryUtil.getState();
             const cantTransform = this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
             const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(layoutdoc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
@@ -932,7 +928,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             const focusSpeed = didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0;
             // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
             if (didMove) {
-                this.setPan(panX, panY, `transform ${focusSpeed}ms`, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+                this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
                 scale && (this.Document[this.scaleFieldKey] = scale);
             }
             Doc.BrushDoc(doc);
@@ -947,7 +943,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     this.Document._panX = restoreState.panX;
                     this.Document._panY = restoreState.panY;
                     this.Document[this.scaleFieldKey] = restoreState.scale;
-                    this.Document._viewTransition = restoreState.transition;
+                    runInAction(() => this._viewTransition = 0);
                 }
                 return resetView;
             };
@@ -979,25 +975,24 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
         if (scale) {
             const maxZoom = 2; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
-
             return {
                 panX: (bounds.left + bounds.right) / 2,
                 panY: (bounds.top + bounds.bot) / 2,
                 scale: Math.min(maxZoom, scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0]), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1])))
             };
-        } else if ((screen.right - screen.left) < (bounds.right - bounds.left) ||
+        }
+        if ((screen.right - screen.left) < (bounds.right - bounds.left) ||
             (screen.bot - screen.top) < (bounds.bot - bounds.top)) {
             return {
                 panX: (bounds.left + bounds.right) / 2,
                 panY: (bounds.top + bounds.bot) / 2,
                 scale: Math.min(this.props.PanelHeight() / (bounds.bot - bounds.top), this.props.PanelWidth() / (bounds.right - bounds.left)) / 1.1
             };
-        } else {
-            return {
-                panX: cx + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
-                panY: cy + Math.min(0, bounds.top - ph / 10 - screen.top) + Math.max(0, bounds.bot + ph / 10 - screen.bot),
-            };
         }
+        return {
+            panX: cx + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
+            panY: cy + Math.min(0, bounds.top - ph / 10 - screen.top) + Math.max(0, bounds.bot + ph / 10 - screen.bot),
+        };
     }
 
     getChildDocView(entry: PoolData) {
@@ -1049,19 +1044,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     addDocTab = action((doc: Doc, where: string) => {
         if (where === "inParent") {
-            if (doc instanceof Doc) {
+            ((doc instanceof Doc) ? [doc] : doc).forEach(doc => {
                 const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
                 doc.x = pt[0];
                 doc.y = pt[1];
-                return this.props.addDocument?.(doc) || false;
-            } else {
-                (doc as any as Doc[]).forEach(doc => {
-                    const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
-                    doc.x = pt[0];
-                    doc.y = pt[1];
-                });
-                return this.props.addDocument?.(doc) || false;
-            }
+            });
+            return this.props.addDocument?.(doc) || false;
         }
         if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
             this.dataDoc[this.props.fieldKey] = doc instanceof Doc ? doc : new List<Doc>(doc as any as Doc[]);
@@ -1072,10 +1060,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc }): PoolData {
         const layoutDoc = Doc.Layout(params.pair.layout);
+        const { z, color, zIndex } = params.pair.layout;
         const { x, y, opacity } = this.Document._currentFrame === undefined ?
             { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) } :
-            CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document._currentFrame || 0);
-        const { z, color, zIndex } = params.pair.layout;
+            CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document._currentFrame);
         return {
             x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
             transition: StrCast(layoutDoc.dataTransition), opacity: this.Document.editing ? 1 : Cast(opacity, "number", null),
@@ -1472,7 +1460,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     presPaths={BoolCast(this.Document.presPathView)}
                     progressivize={BoolCast(this.Document.editProgressivize)}
                     presPinView={BoolCast(this.Document.presPinView)}
-                    transition={Cast(this.layoutDoc._viewTransition, "string", null)}
+                    transition={this._viewTransition ? `transform ${this._viewTransition}ms` : Cast(this.layoutDoc._viewTransition, "string", null)}
                     viewDefDivClick={this.props.viewDefDivClick}>
                     {this.children}
                 </CollectionFreeFormViewPannableContents>
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e61cf83bb..9e442a8c8 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -38,7 +38,7 @@ interface MarqueeViewProps {
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
     trySelectCluster: (addToSel: boolean) => boolean;
-    nudge?: (x: number, y: number) => boolean;
+    nudge?: (x: number, y: number, nudgeTime?: number) => boolean;
     ungroup?: () => void;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
-- 
cgit v1.2.3-70-g09d2


From 2cddc1cd6012a1c71c919124f640ce1d04593ec8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 23 Feb 2021 22:36:52 -0500
Subject: removed _renderContentBounds and updated miniMap view.

---
 src/client/views/collections/TabDocView.tsx        | 186 ++++++++++++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  10 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 3 files changed, 105 insertions(+), 93 deletions(-)

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

diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index c1a1a6f04..c7eb9ef48 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -9,7 +9,6 @@ import * as ReactDOM from 'react-dom';
 import { DataSym, Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { Id } from '../../../fields/FieldSymbols';
 import { FieldId } from "../../../fields/RefField";
-import { listSpec } from '../../../fields/Schema';
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
@@ -55,13 +54,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
 
     @computed get layoutDoc() { return this._document && Doc.Layout(this._document); }
     @computed get tabColor() { return StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor))); }
-    @computed get renderBounds() {
-        const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
-        const xbounds = bounds[2] - bounds[0];
-        const ybounds = bounds[3] - bounds[1];
-        const dim = Math.max(xbounds, ybounds);
-        return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
-    }
+
 
     get stack() { return (this.props as any).glContainer.parent.parent; }
     get tab() { return (this.props as any).glContainer.tab; }
@@ -290,76 +283,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
         }
     }
 
-    childLayoutTemplate = () => Cast(this._document?.childLayoutTemplate, Doc, null);
-    returnMiniSize = () => NumCast(this._document?._miniMapSize, 150);
-    miniDown = (e: React.PointerEvent) => {
-        const doc = this._document;
-        const miniSize = this.returnMiniSize();
-        doc && setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
-            doc._panX = clamp(NumCast(doc._panX) + delta[0] / miniSize * this.renderBounds.dim, this.renderBounds.l, this.renderBounds.l + this.renderBounds.dim);
-            doc._panY = clamp(NumCast(doc._panY) + delta[1] / miniSize * this.renderBounds.dim, this.renderBounds.t, this.renderBounds.t + this.renderBounds.dim);
-            return false;
-        }), emptyFunction, emptyFunction);
-    }
     getCurrentFrame = () => {
         return NumCast(Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null)._currentFrame);
     }
-    renderMiniMap() {
-        const miniWidth = this.PanelWidth() / NumCast(this._document?._viewScale, 1) / this.renderBounds.dim * 100;
-        const miniHeight = this.PanelHeight() / NumCast(this._document?._viewScale, 1) / this.renderBounds.dim * 100;
-        const miniLeft = 50 + (NumCast(this._document?._panX) - this.renderBounds.cx) / this.renderBounds.dim * 100 - miniWidth / 2;
-        const miniTop = 50 + (NumCast(this._document?._panY) - this.renderBounds.cy) / this.renderBounds.dim * 100 - miniHeight / 2;
-        const miniSize = this.returnMiniSize();
-        return <>
-            <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.tabColor }}>
-                <CollectionFreeFormView
-                    Document={this._document!}
-                    CollectionView={undefined}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
-                    parentActive={returnFalse}
-                    docViewPath={returnEmptyDoclist}
-                    childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
-                    noOverlay={true} // don't render overlay Docs since they won't scale
-                    active={returnTrue}
-                    select={emptyFunction}
-                    dropAction={undefined}
-                    isSelected={returnFalse}
-                    dontRegisterView={true}
-                    fieldKey={Doc.LayoutFieldKey(this._document!)}
-                    bringToFront={emptyFunction}
-                    rootSelected={returnTrue}
-                    addDocument={returnFalse}
-                    moveDocument={returnFalse}
-                    removeDocument={returnFalse}
-                    PanelWidth={this.returnMiniSize}
-                    PanelHeight={this.returnMiniSize}
-                    ScreenToLocalTransform={Transform.Identity}
-                    renderDepth={0}
-                    whenActiveChanged={emptyFunction}
-                    focus={DocUtils.DefaultFocus}
-                    styleProvider={TabDocView.miniStyleProvider}
-                    layerProvider={undefined}
-                    addDocTab={this.addDocTab}
-                    pinToPres={TabDocView.PinDoc}
-                    docFilters={CollectionDockingView.Instance.docFilters}
-                    docRangeFilters={CollectionDockingView.Instance.docRangeFilters}
-                    searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
-                    fitContentsToDoc={returnTrue}
-                />
-                <div className="miniOverlay" onPointerDown={this.miniDown} >
-                    <div className="miniThumb" style={{ width: `${miniWidth}% `, height: `${miniHeight}% `, left: `${miniLeft}% `, top: `${miniTop}% `, }} />
-                </div>
-            </div>
-
-            <Tooltip title={<div className="dash-tooltip">{"toggle minimap"}</div>}>
-                <div className="miniMap-hidden" onPointerDown={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })}
-                    style={{ background: DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor) }} >
-                    <FontAwesomeIcon icon={"globe-asia"} size="lg" />
-                </div>
-            </Tooltip>
-        </>;
-    }
     @action
     focusFunc = (doc: Doc, options?: DocFocusOptions) => {
         const vals = (!options?.originalTarget || options?.originalTarget === this._document) && this.view?.ComponentView?.freeformData?.(true);
@@ -402,6 +328,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             }
         }
     }
+    miniMapColor = () => this.tabColor;
+    miniPanelWidth = () => this.PanelWidth();
+    miniPanelHeight = () => this.PanelHeight();
+    tabView = () => this._view;
     @computed get layerProvider() { return this._document && DefaultLayerProvider(this._document); }
     @computed get docView() {
         TraceMobx();
@@ -431,14 +361,18 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 docViewPath={returnEmptyDoclist}
                 bringToFront={emptyFunction}
                 pinToPres={TabDocView.PinDoc} />
-                {this._document._viewType !== CollectionViewType.Freeform ? (null) :
-                    <>{this._document.hideMinimap ? (null) : this.renderMiniMap()}
-                        <Tooltip key="ttip" title={<div className="dash-tooltip">{"toggle minimap"}</div>}>
-                            <div className="miniMap-hidden" onPointerDown={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} >
-                                <FontAwesomeIcon icon={"globe-asia"} size="lg" />
-                            </div>
-                        </Tooltip>
-                    </>}
+                <TabMinimapView key="minimap"
+                    addDocTab={this.addDocTab}
+                    PanelHeight={this.miniPanelHeight}
+                    PanelWidth={this.miniPanelWidth}
+                    background={this.miniMapColor}
+                    document={this._document}
+                    tabView={this.tabView} />
+                <Tooltip key="ttip" title={<div className="dash-tooltip">{"toggle minimap"}</div>}>
+                    <div className="miniMap-hidden" onPointerDown={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} >
+                        <FontAwesomeIcon icon={"globe-asia"} size="lg" />
+                    </div>
+                </Tooltip>
             </>;
     }
 
@@ -454,4 +388,90 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             </div >
         );
     }
+}
+
+interface TabMinimapViewProps {
+    document: Doc;
+    tabView: () => DocumentView | undefined;
+    addDocTab: (doc: Doc, where: string) => boolean;
+    PanelWidth: () => number;
+    PanelHeight: () => number;
+    background: () => string;
+}
+@observer
+export class TabMinimapView extends React.Component<TabMinimapViewProps> {
+    @computed get renderBounds() {
+        const bounds = this.props.tabView()?.ComponentView?.freeformData?.()?.bounds ?? { x: 0, y: 0, r: this.returnMiniSize(), b: this.returnMiniSize() };
+        const xbounds = bounds.r - bounds.x;
+        const ybounds = bounds.b - bounds.y;
+        const dim = Math.max(xbounds, ybounds);
+        return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
+    }
+    childLayoutTemplate = () => Cast(this.props.document.childLayoutTemplate, Doc, null);
+    returnMiniSize = () => NumCast(this.props.document._miniMapSize, 150);
+    miniDown = (e: React.PointerEvent) => {
+        const doc = this.props.document;
+        const miniSize = this.returnMiniSize();
+        doc && setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
+            doc._panX = clamp(NumCast(doc._panX) + delta[0] / miniSize * this.renderBounds.dim, this.renderBounds.l, this.renderBounds.l + this.renderBounds.dim);
+            doc._panY = clamp(NumCast(doc._panY) + delta[1] / miniSize * this.renderBounds.dim, this.renderBounds.t, this.renderBounds.t + this.renderBounds.dim);
+            return false;
+        }), emptyFunction, emptyFunction);
+    }
+    render() {
+        const miniWidth = this.props.PanelWidth() / NumCast(this.props.document._viewScale, 1) / this.renderBounds.dim * 100;
+        const miniHeight = this.props.PanelHeight() / NumCast(this.props.document._viewScale, 1) / this.renderBounds.dim * 100;
+        const miniLeft = 50 + (NumCast(this.props.document._panX) - this.renderBounds.cx) / this.renderBounds.dim * 100 - miniWidth / 2;
+        const miniTop = 50 + (NumCast(this.props.document._panY) - this.renderBounds.cy) / this.renderBounds.dim * 100 - miniHeight / 2;
+        const miniSize = this.returnMiniSize();
+        return this.props.document._viewType !== CollectionViewType.Freeform || this.props.document.hideMinimap ? (null) : <>
+            <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
+                <CollectionFreeFormView
+                    Document={this.props.document}
+                    CollectionView={undefined}
+                    ContainingCollectionView={undefined}
+                    ContainingCollectionDoc={undefined}
+                    parentActive={returnFalse}
+                    docViewPath={returnEmptyDoclist}
+                    childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
+                    noOverlay={true} // don't render overlay Docs since they won't scale
+                    active={returnTrue}
+                    select={emptyFunction}
+                    dropAction={undefined}
+                    isSelected={returnFalse}
+                    dontRegisterView={true}
+                    fieldKey={Doc.LayoutFieldKey(this.props.document)}
+                    bringToFront={emptyFunction}
+                    rootSelected={returnTrue}
+                    addDocument={returnFalse}
+                    moveDocument={returnFalse}
+                    removeDocument={returnFalse}
+                    PanelWidth={this.returnMiniSize}
+                    PanelHeight={this.returnMiniSize}
+                    ScreenToLocalTransform={Transform.Identity}
+                    renderDepth={0}
+                    whenActiveChanged={emptyFunction}
+                    focus={DocUtils.DefaultFocus}
+                    styleProvider={TabDocView.miniStyleProvider}
+                    layerProvider={undefined}
+                    addDocTab={this.props.addDocTab}
+                    pinToPres={TabDocView.PinDoc}
+                    docFilters={CollectionDockingView.Instance.docFilters}
+                    docRangeFilters={CollectionDockingView.Instance.docRangeFilters}
+                    searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
+                    fitContentsToDoc={returnTrue}
+                />
+                <div className="miniOverlay" onPointerDown={this.miniDown} >
+                    <div className="miniThumb" style={{ width: `${miniWidth}% `, height: `${miniHeight}% `, left: `${miniLeft}% `, top: `${miniTop}% `, }} />
+                </div>
+            </div>
+
+            <Tooltip title={<div className="dash-tooltip">{"toggle minimap"}</div>}>
+                <div className="miniMap-hidden" onPointerDown={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this.props.document.hideMinimap = !this.props.document.hideMinimap; })}
+                    style={{ background: DefaultStyleProvider(this.props.document, undefined, StyleProp.BackgroundColor) }} >
+                    <FontAwesomeIcon icon={"globe-asia"} size="lg" />
+                </div>
+            </Tooltip>
+        </>;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9fa5b8670..ba89579cd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -120,6 +120,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     @computed get fitToContentVals() {
         return {
+            bounds: this.contentBounds,
             panX: (this.contentBounds.x + this.contentBounds.r) / 2,
             panY: (this.contentBounds.y + this.contentBounds.b) / 2,
             scale: !this.childDocs.length ? 1 :
@@ -1214,15 +1215,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this._disposers.layoutComputation = reaction(() => this.doLayoutComputation,
             (elements) => this._layoutElements = elements || [],
             { fireImmediately: true, name: "doLayout" });
-        if (!this.props.isAnnotationOverlay) {
-            this._disposers.contentBounds = reaction(() => this.contentBounds,
-                bounds => (!this.fitToContent && this._layoutElements?.length) && setTimeout(() => {
-                    const rbounds = Cast(this.Document._renderContentBounds, listSpec("number"), [0, 0, 0, 0]);
-                    if (rbounds[0] !== bounds.x || rbounds[1] !== bounds.y || rbounds[2] !== bounds.r || rbounds[3] !== bounds.b) {
-                        this.Document._renderContentBounds = new List<number>([bounds.x, bounds.y, bounds.r, bounds.b]);
-                    }
-                }));
-        }
 
         this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e31101459..5dfb3e99b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -83,7 +83,7 @@ export interface DocComponentView {
     forward?: () => boolean;
     url?: () => string;
     submitURL?: (url: string) => boolean;
-    freeformData?: (force?: boolean) => Opt<{ panX: number, panY: number, scale: number }>;
+    freeformData?: (force?: boolean) => Opt<{ panX: number, panY: number, scale: number, bounds: { x: number, y: number, r: number, b: number } }>;
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
-- 
cgit v1.2.3-70-g09d2


From 477fa6b85e24046edbebc44b49aba8c286558cd0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 Feb 2021 10:45:37 -0500
Subject: fixed carouself view which wasn't displaying vertically oriented
 documents.  cleaned up collectionTime script to not capture variables
 (causing doc's to be create)

---
 src/client/views/collections/CollectionCarousel3DView.scss     |  1 -
 src/client/views/collections/CollectionCarousel3DView.tsx      | 10 +++++-----
 src/client/views/collections/CollectionTimeView.tsx            |  5 ++---
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx      |  4 +---
 5 files changed, 9 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCarousel3DView.scss b/src/client/views/collections/CollectionCarousel3DView.scss
index 652293ed6..5c8b491eb 100644
--- a/src/client/views/collections/CollectionCarousel3DView.scss
+++ b/src/client/views/collections/CollectionCarousel3DView.scss
@@ -9,7 +9,6 @@
     position: absolute;
     top: 15%;
     height: 60%;
-    width: 100%;
     align-items: center;
     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
 
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 9b1e3b80d..c5a05da00 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -40,7 +40,7 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
     @computed get content() {
         const currentIndex = NumCast(this.layoutDoc._itemIndex);
         const displayDoc = (childPair: { layout: Doc, data: Doc }) => {
-            const script = ScriptField.MakeScript("child._showCaption = 'caption'", { child: Doc.name }, { child: childPair.layout });
+            const script = ScriptField.MakeScript("this._showCaption = 'caption'", { this: Doc.name });
             const onChildClick = script && (() => script);
             return <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                 onDoubleClick={this.onChildDoubleClick}
@@ -63,8 +63,8 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
                 <div key={childPair.layout[Id]}
                     className={`collectionCarousel3DView-item${index === currentIndex ? "-active" : ""} ${index}`}
                     style={index === currentIndex ?
-                        { opacity: '1', transform: 'scale(1.3)' } :
-                        { opacity: '0.5', transform: 'scale(0.6)', userSelect: 'none' }}>
+                        { opacity: '1', transform: 'scale(1.3)', width: this.panelWidth() } :
+                        { opacity: '0.5', transform: 'scale(0.6)', userSelect: 'none', width: this.panelWidth() }}>
                     {displayDoc(childPair)}
                 </div>);
         }));
@@ -166,10 +166,10 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
 
     render() {
         const index = NumCast(this.layoutDoc._itemIndex);
-        const translateX = 33 * (1 - index);
+        const translateX = this.panelWidth() * (1 - index);
 
         return <div className="collectionCarousel3DView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget}>
-            <div className="carousel-wrapper" style={{ transform: `translateX(calc(${translateX}%` }}>
+            <div className="carousel-wrapper" style={{ transform: `translateX(${translateX}px)` }}>
                 {this.content}
             </div>
             {this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)}
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 857d5e1d7..1f6322997 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -62,11 +62,10 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 
     async componentDidMount() {
         this.props.setContentView?.(this);
-        const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), "");
+        //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), "");
         ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
-        const childText = "openInLightbox(self, shiftKey); ";
         runInAction(() => {
-            this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! });
+            this._childClickedScript = ScriptField.MakeScript("openInLightbox(self, shiftKey)", { this: Doc.name, shiftKey: "boolean" });//, { detailView: detailView! });
             this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
         });
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ba89579cd..96ef7af01 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1199,7 +1199,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         });
         const proto = Doc.GetProto(anchor);
         proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType;
-        proto[ViewSpecPrefix + "_docFilters"] = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List<string>([]);
+        proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List<string>([]);
         if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) {
             Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor);
         } else {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index da7ed9ac7..ce54bf57f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -109,9 +109,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     private _ignoreScroll = false;
 
     @computed get _recording() { return this.dataDoc?.audioState === "recording"; }
-    set _recording(value) {
-        this.dataDoc.audioState = value ? "recording" : undefined;
-    }
+    set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; }
 
     public static FocusedBox: FormattedTextBox | undefined;
     public static SelectOnLoad = "";
-- 
cgit v1.2.3-70-g09d2


From 3410e107435410a5635a70f12ee05e2d874ff01c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 Feb 2021 22:04:31 -0500
Subject: cleaned up DocumentView's contentView api to be more general. fixed
 screenToLocal for freeformView's whenb _fitToBox is set.  moved webBox menu
 items out of CollectionMenu and into WebBox using the contentView API.

---
 src/client/views/collections/CollectionMenu.tsx    | 57 +---------------------
 .../collectionFreeForm/CollectionFreeFormView.scss |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 18 +++----
 src/client/views/nodes/DocumentView.tsx            | 17 +++----
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 src/client/views/nodes/VideoBox.tsx                |  2 +-
 src/client/views/nodes/WebBox.tsx                  | 47 ++++++++++++++++++
 7 files changed, 68 insertions(+), 77 deletions(-)

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

diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 423c94005..591b4161e 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -560,7 +560,6 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
     @computed get isText() {
         return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false;
     }
-    @computed get webBoxUrl() { return this.selectedDocumentView?.ComponentView?.url?.(); }
 
     @undoBatch
     @action
@@ -733,58 +732,6 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
 
             </div>;
     }
-
-    onWebUrlDrop = (e: React.DragEvent) => {
-        const { dataTransfer } = e;
-        const html = dataTransfer.getData("text/html");
-        const uri = dataTransfer.getData("text/uri-list");
-        const url = uri || html || this.webBoxUrl || "";
-        const newurl = url.startsWith(window.location.origin) ?
-            url.replace(window.location.origin, this.webBoxUrl?.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url;
-        this.submitWebUrl(newurl);
-        e.stopPropagation();
-    }
-    onWebUrlValueKeyDown = (e: React.KeyboardEvent) => {
-        e.key === "Enter" && this.submitWebUrl(this._keyInput.current!.value);
-        e.stopPropagation();
-    }
-    submitWebUrl = (url: string) => this.selectedDocumentView?.ComponentView?.submitURL?.(url);
-    webUrlForward = () => this.selectedDocumentView?.ComponentView?.forward?.();
-    webUrlBack = () => this.selectedDocumentView?.ComponentView?.back?.();
-
-    private _keyInput = React.createRef<HTMLInputElement>();
-
-    @computed get urlEditor() {
-        return (
-            <div className="collectionMenu-webUrlButtons" onDrop={this.onWebUrlDrop} onDragOver={e => e.preventDefault()} >
-                <input className="collectionMenu-urlInput" key={this.webBoxUrl}
-                    placeholder="ENTER URL"
-                    defaultValue={this.webBoxUrl}
-                    onDrop={this.onWebUrlDrop}
-                    onDragOver={e => e.preventDefault()}
-                    onKeyDown={this.onWebUrlValueKeyDown}
-                    onClick={(e) => {
-                        this._keyInput.current!.select();
-                        e.stopPropagation();
-                    }}
-                    ref={this._keyInput}
-                />
-                <div style={{ display: "flex", flexDirection: "row", justifyContent: "space-between", maxWidth: "250px", }}>
-                    <button className="submitUrl" onClick={() => this.submitWebUrl(this._keyInput.current!.value)} onDragOver={e => e.stopPropagation()} onDrop={this.onWebUrlDrop}>
-                        GO
-                    </button>
-                    <button className="submitUrl" onClick={this.webUrlBack}>
-                        <FontAwesomeIcon icon="caret-left" size="lg"></FontAwesomeIcon>
-                    </button>
-                    <button className="submitUrl" onClick={this.webUrlForward}>
-                        <FontAwesomeIcon icon="caret-right" size="lg"></FontAwesomeIcon>
-                    </button>
-                </div>
-            </div>
-        );
-    }
-
-
     @observable viewType = this.selectedDoc?._viewType;
 
     render() {
@@ -811,9 +758,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
                     </>
                     : null}
 
-                {!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText || this.props.isDoc ? (null) :
-                    this.urlEditor
-                }
+                {!this.selectedDocumentView?.ComponentView?.menuControls ? (null) : this.selectedDocumentView?.ComponentView?.menuControls?.()}
                 {!this.isText ?
                     <>
                         {this.drawButtons}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index a05c25c9b..eb0538c41 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -191,7 +191,7 @@
 .collectionfreeformview-container {
     // touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions.
     touch-action: none;
-
+    transform-origin: top left;
 
     .collectionfreeformview-placeholder {
         background: gray;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 96ef7af01..92c09ff3f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -69,7 +69,7 @@ type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof collectionSch
 const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentSchema, pageSchema);
 export type collectionFreeformViewProps = {
     parentActive: (outsideReaction: boolean) => boolean;
-    forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
+    annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox)
     viewDefDivClick?: ScriptField;
     childPointerEvents?: boolean;
     scaleField?: string;
@@ -157,10 +157,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined;
     freeformDocFilters = () => this._focusFilters || this.docFilters();
     freeformRangeDocFilters = () => this._focusRangeFilters || this.docRangeFilters();
+    reverseNativeScaling = () => this.fitToContent ? true : false;
     panX = () => this.freeformData()?.panX ?? NumCast(this.Document._panX);
     panY = () => this.freeformData()?.panY ?? NumCast(this.Document._panY);
     zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1));
-    contentTransform = () => `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`
+    contentTransform = () => `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
     getTransform = () => this.cachedGetTransform.copy();
     getLocalTransform = () => this.cachedGetLocalTransform.copy();
     getContainerTransform = () => this.cachedGetContainerTransform.copy();
@@ -1274,11 +1275,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     @undoBatch
-    toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight);
+    toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight)
 
     @undoBatch
     @action
-    toggleLockTransform = () => this.layoutDoc._lockedTransform = this.layoutDoc._lockedTransform ? undefined : true;
+    toggleLockTransform = () => this.layoutDoc._lockedTransform = this.layoutDoc._lockedTransform ? undefined : true
 
     onContextMenu = (e: React.MouseEvent) => {
         if (this.props.isAnnotationOverlay || this.props.Document.annotationOn || !ContextMenu.Instance) return;
@@ -1462,7 +1463,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     @computed get contentScaling() {
-        if (this.props.isAnnotationOverlay && !this.props.forceScaling) return 0;
+        if (this.props.isAnnotationOverlay && !this.props.annotationLayerHostsContent) return 0;
         const nw = this.nativeWidth;
         const nh = this.nativeHeight;
         const hscale = nh ? this.props.PanelHeight() / nh : 1;
@@ -1484,10 +1485,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             onContextMenu={this.onContextMenu}
             style={{
                 pointerEvents: this.backgroundEvents ? "all" : undefined,
-                transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
-                transformOrigin: this.contentScaling ? "left top" : "",
-                width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
-                height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
+                transform: `scale(${this.contentScaling || 1})`,
+                width: `${100 / (this.contentScaling || 1)}%`,
+                height: this.isAnnotationOverlay && this.Document.scrollHeight ? this.Document.scrollHeight : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
             }}>
             {this.Document._freeformLOD && !this.props.active() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0 ?
                 this.placeholder : this.marqueeView}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5dfb3e99b..ec2c77a82 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -76,14 +76,13 @@ export type DocAfterFocusFunc = (notFocused: boolean) => Promise<ViewAdjustment>
 export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void;
 export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
 export interface DocComponentView {
-    getAnchor?: () => Doc;
+    getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
     scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
-    setViewSpec?: (anchor: Doc, preview: boolean) => void;
-    back?: () => boolean;
-    forward?: () => boolean;
-    url?: () => string;
-    submitURL?: (url: string) => boolean;
-    freeformData?: (force?: boolean) => Opt<{ panX: number, panY: number, scale: number, bounds: { x: number, y: number, r: number, b: number } }>;
+    setViewSpec?: (anchor: Doc, preview: boolean) => void;  // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
+    reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height.  However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
+    menuControls?: any; // controls to display in the top menu bar when the document is selected.
+    // this is kind of hacky since it's not really a generic interface. need to think about how to do this better (it's used to fit a tab's contents to view when shown in a lightbox and to setup the minimap)
+    freeformData?: (force?: boolean) => Opt<{ panX: number, panY: number, scale: number, bounds: { x: number, y: number, r: number, b: number } }>;  // the content bounds, pan position and zoom scale of a content view (typically for FreeformViews)
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
@@ -990,8 +989,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     @computed get docViewPath() { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; }
     @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); }
-    @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); }
-    @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions) || 0); }
+    @computed get nativeWidth() { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); }
+    @computed get nativeHeight() { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions) || 0); }
     @computed get nativeScaling() {
         if (this.nativeWidth && (this.layoutDoc?._fitWidth || this.props.PanelHeight() / this.nativeHeight > this.props.PanelWidth() / this.nativeWidth)) {
             return this.props.PanelWidth() / this.nativeWidth;  // width-limited or fitWidth
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 728e5e02f..4c3031ae2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -381,7 +381,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
                 removeDocument={this.removeDocument}
                 moveDocument={this.moveDocument}
                 addDocument={this.addDocument}
-                forceScaling={true}
+                annotationLayerHostsContent={true}
                 focus={this.props.focus}
                 isSelected={this.props.isSelected}
                 select={emptyFunction}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 435563255..ac67949f9 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -552,7 +552,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                     <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
                         fieldKey={this.annotationKey}
                         isAnnotationOverlay={true}
-                        forceScaling={true}
+                        annotationLayerHostsContent={true}
                         select={emptyFunction}
                         active={this.annotationsActive}
                         scaling={returnOne}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 663a8b8e4..2cd6f5f33 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -46,6 +46,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
     private _iframeDragRef = React.createRef<HTMLDivElement>();
+    private _keyInput = React.createRef<HTMLInputElement>();
     private _ignoreScroll = false;
     private _initialScroll: Opt<number>;
     @observable private _marqueeing: number[] | undefined;
@@ -262,6 +263,52 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         return true;
     }
 
+    menuControls = () => this.urlEditor;
+    onWebUrlDrop = (e: React.DragEvent) => {
+        const { dataTransfer } = e;
+        const html = dataTransfer.getData("text/html");
+        const uri = dataTransfer.getData("text/uri-list");
+        const url = uri || html || this._url || "";
+        const newurl = url.startsWith(window.location.origin) ?
+            url.replace(window.location.origin, this._url?.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url;
+        this.submitURL(newurl);
+        e.stopPropagation();
+    }
+    onWebUrlValueKeyDown = (e: React.KeyboardEvent) => {
+        e.key === "Enter" && this.submitURL(this._keyInput.current!.value);
+        e.stopPropagation();
+    }
+
+    @computed get urlEditor() {
+        return (
+            <div className="collectionMenu-webUrlButtons" onDrop={this.onWebUrlDrop} onDragOver={e => e.preventDefault()} >
+                <input className="collectionMenu-urlInput" key={this._url}
+                    placeholder="ENTER URL"
+                    defaultValue={this._url}
+                    onDrop={this.onWebUrlDrop}
+                    onDragOver={e => e.preventDefault()}
+                    onKeyDown={this.onWebUrlValueKeyDown}
+                    onClick={(e) => {
+                        this._keyInput.current!.select();
+                        e.stopPropagation();
+                    }}
+                    ref={this._keyInput}
+                />
+                <div style={{ display: "flex", flexDirection: "row", justifyContent: "space-between", maxWidth: "250px", }}>
+                    <button className="submitUrl" onClick={() => this.submitURL(this._keyInput.current!.value)} onDragOver={e => e.stopPropagation()} onDrop={this.onWebUrlDrop}>
+                        GO
+                    </button>
+                    <button className="submitUrl" onClick={this.back}>
+                        <FontAwesomeIcon icon="caret-left" size="lg"></FontAwesomeIcon>
+                    </button>
+                    <button className="submitUrl" onClick={this.forward}>
+                        <FontAwesomeIcon icon="caret-right" size="lg"></FontAwesomeIcon>
+                    </button>
+                </div>
+            </div>
+        );
+    }
+
     editToggleBtn() {
         return <Tooltip title={<div className="dash-tooltip" >{`${this.props.Document.isAnnotating ? "Exit" : "Enter"} annotation mode`}</div>}>
             <div className="webBox-annotationToggle"
-- 
cgit v1.2.3-70-g09d2


From 260744fb08aca76f5c1810e0af815df3f851a853 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 25 Feb 2021 01:25:36 -0500
Subject: cleaned up DoccomponentView to just have general api fields.  fixed
 lightbox to animate back properly & shinkwrap.

---
 src/client/views/LightboxView.tsx                  | 34 +++++++++-------------
 src/client/views/collections/TabDocView.tsx        |  8 ++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 14 +++++----
 src/client/views/nodes/DocumentView.tsx            |  5 ++--
 4 files changed, 28 insertions(+), 33 deletions(-)

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

diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index e967a5b07..1b21bd073 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -13,7 +13,7 @@ import { SelectionManager } from '../util/SelectionManager';
 import { Transform } from '../util/Transform';
 import { TabDocView } from './collections/TabDocView';
 import "./LightboxView.scss";
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, ViewAdjustment } from './nodes/DocumentView';
 import { DefaultStyleProvider } from './StyleProvider';
 
 interface LightboxViewProps {
@@ -103,8 +103,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             [...DocListCast(doc[Doc.LayoutFieldKey(doc)]),
             ...DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"]),
             ...(LightboxView._future ?? [])
-            ]
-                .sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)));
+            ].sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)));
     }
     docFilters = () => LightboxView._docFilters || [];
     addDocTab = LightboxView.AddDocTab;
@@ -113,8 +112,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         const target = LightboxView._docTarget = LightboxView._future?.pop();
         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
         if (docView && target) {
-            docView.focus(target, { willZoom: true, scale: 0.9 });
-            if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target: LightboxView._docTarget });
+            docView.focus(target, { originalTarget: target, willZoom: true, scale: 0.9 });
+            if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target });
         } else {
             if (!target && LightboxView.path.length) {
                 const saved = LightboxView._savedState;
@@ -149,16 +148,14 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             return;
         }
         const { doc, target } = LightboxView._history?.lastElement();
-        const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
-        if (docView && target) {
-            LightboxView._doc = doc;
-            LightboxView._docTarget = target || doc;
-            if (LightboxView._future?.lastElement() !== previous.target || previous.doc) LightboxView._future?.push(previous.target || previous.doc);
-            docView.focus(target, { willZoom: true, scale: 0.9 });
-        } else {
-            LightboxView._doc = doc;
-            LightboxView._docTarget = target || doc;
-        }
+        const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
+        const focusSpeed = 1000;
+        doc._viewTransition = `transform ${focusSpeed}ms`;
+        LightboxView._doc = doc;
+        LightboxView._docTarget = target || doc;
+        docView?.focus(doc, { willZoom: true, scale: 0.9 });
+        setTimeout(() => doc._viewTransition = undefined, focusSpeed);
+        if (LightboxView._future?.lastElement() !== previous.target || previous.doc) LightboxView._future?.push(previous.target || previous.doc);
         LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links).map(link => {
             const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!);
             return opp?.TourMap ? opp : undefined;
@@ -208,11 +205,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     <DocumentView ref={action((r: DocumentView | null) => {
                         LightboxView._docView = r !== null ? r : undefined;
                         setTimeout(action(() => {
-                            const vals = r?.ComponentView?.freeformData?.();
-                            if (vals && r) {
-                                r.layoutDoc._panX = vals.panX;
-                                r.layoutDoc._panY = vals.panY;
-                                r.layoutDoc._viewScale = vals.scale;
+                            if (r && LightboxView._docTarget === r.props.Document) {
+                                r.ComponentView?.shrinkWrap?.();
                             }
                             r && (LightboxView._docTarget = undefined);
                         }));
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 80ddf2f48..3f8794665 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -273,12 +273,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     }
     @action
     focusFunc = (doc: Doc, options?: DocFocusOptions) => {
-        const vals = (!options?.originalTarget || options?.originalTarget === this._document) && this.view?.ComponentView?.freeformData?.(true);
-        if (vals && this._document) {
+        const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap;
+        if (shrinkwrap && this._document) {
             const focusSpeed = 1000;
-            this._document._panX = vals.panX;
-            this._document._panY = vals.panY;
-            this._document._viewScale = vals.scale;
+            shrinkwrap();
             this._document._viewTransition = `transform ${focusSpeed}ms`;
             setTimeout(action(() => {
                 this._document!._viewTransition = undefined;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 92c09ff3f..dfca2ba07 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -120,9 +120,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     @computed get fitToContentVals() {
         return {
-            bounds: this.contentBounds,
-            panX: (this.contentBounds.x + this.contentBounds.r) / 2,
-            panY: (this.contentBounds.y + this.contentBounds.b) / 2,
+            bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 },
             scale: !this.childDocs.length ? 1 :
                 Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y),
                     this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x))
@@ -154,12 +152,18 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.props.parentActive?.(outsideReaction) || this.backgroundActive || this.layoutDoc._viewType === CollectionViewType.Pile ? true : false;
     elementFunc = () => this._layoutElements;
+    shrinkWrap = () => {
+        const vals = this.fitToContentVals;
+        this.layoutDoc._panX = vals.bounds.cx;
+        this.layoutDoc._panY = vals.bounds.cy;
+        this.layoutDoc._viewScale = vals.scale;
+    }
     freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined;
     freeformDocFilters = () => this._focusFilters || this.docFilters();
     freeformRangeDocFilters = () => this._focusRangeFilters || this.docRangeFilters();
     reverseNativeScaling = () => this.fitToContent ? true : false;
-    panX = () => this.freeformData()?.panX ?? NumCast(this.Document._panX);
-    panY = () => this.freeformData()?.panY ?? NumCast(this.Document._panY);
+    panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX);
+    panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY);
     zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1));
     contentTransform = () => `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
     getTransform = () => this.cachedGetTransform.copy();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ec2c77a82..01d799255 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -80,9 +80,8 @@ export interface DocComponentView {
     scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
     setViewSpec?: (anchor: Doc, preview: boolean) => void;  // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
     reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height.  However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
-    menuControls?: any; // controls to display in the top menu bar when the document is selected.
-    // this is kind of hacky since it's not really a generic interface. need to think about how to do this better (it's used to fit a tab's contents to view when shown in a lightbox and to setup the minimap)
-    freeformData?: (force?: boolean) => Opt<{ panX: number, panY: number, scale: number, bounds: { x: number, y: number, r: number, b: number } }>;  // the content bounds, pan position and zoom scale of a content view (typically for FreeformViews)
+    shrinkWrap?: () => boolean;  // requests a document to display all of its contents with no white space.  currently only implemented (needed?) for freeform views
+    menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
-- 
cgit v1.2.3-70-g09d2


From c4c3764397eb1ab12c93ea3e07483b436e87d736 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 25 Feb 2021 11:50:54 -0500
Subject: overhaul of state preservation in lightboxview when going
 back/forward/reopening a doc/etc

---
 src/client/views/LightboxView.tsx                  | 101 +++++++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  13 +--
 src/client/views/nodes/DocumentView.tsx            |   3 +-
 3 files changed, 71 insertions(+), 46 deletions(-)

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

diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 1b21bd073..babc518ff 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import "normalize.css";
 import * as React from 'react';
@@ -28,32 +28,33 @@ export class LightboxView extends React.Component<LightboxViewProps> {
     @computed public static get LightboxDoc() { return this._doc; }
     @observable private static _doc: Opt<Doc>;
     @observable private static _docTarget: Opt<Doc>;
-    @observable private static _tourMap: Opt<Doc[]> = [];   // list of all tours available from the current target
     @observable private static _docFilters: string[] = []; // filters
-    private static _savedState: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }>;
+    @observable private static _tourMap: Opt<Doc[]> = [];   // list of all tours available from the current target
+    private static _savedState: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number> }>;
     private static _history: Opt<{ doc: Doc, target?: Doc }[]> = [];
     private static _future: Opt<Doc[]> = [];
     private static _docView: Opt<DocumentView>;
-    static path: { doc: Opt<Doc>, target: Opt<Doc>, history: Opt<{ doc: Doc, target?: Doc }[]>, future: Opt<Doc[]>, saved: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }> }[] = [];
+    static path: { doc: Opt<Doc>, target: Opt<Doc>, history: Opt<{ doc: Doc, target?: Doc }[]>, future: Opt<Doc[]>, saved: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number> }> }[] = [];
     @action public static SetLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[]) {
+        if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) {
+            this.LightboxDoc._panX = this._savedState.panX;
+            this.LightboxDoc._panY = this._savedState.panY;
+            this.LightboxDoc._viewScale = this._savedState.scale;
+            this.LightboxDoc._viewTransition = undefined;
+        }
         if (!doc) {
             this._docFilters && (this._docFilters.length = 0);
-            if (this.LightboxDoc) {
-                this.LightboxDoc._panX = this._savedState?.panX;
-                this.LightboxDoc._panY = this._savedState?.panY;
-                this.LightboxDoc._viewScale = this._savedState?.scale;
-                this.LightboxDoc._viewTransition = this._savedState?.transition;
-            }
             this._future = this._history = [];
         } else {
             TabDocView.PinDoc(doc, { hidePresBox: true });
             this._history ? this._history.push({ doc, target }) : this._history = [{ doc, target }];
-            this._savedState = {
-                panX: Cast(doc._panX, "number", null),
-                panY: Cast(doc._panY, "number", null),
-                scale: Cast(doc._viewScale, "number", null),
-                transition: Cast(doc._viewTransition, "string", null)
-            };
+            if (doc !== LightboxView.LightboxDoc) {
+                this._savedState = {
+                    panX: Cast(doc._panX, "number", null),
+                    panY: Cast(doc._panY, "number", null),
+                    scale: Cast(doc._viewScale, "number", null),
+                };
+            }
         }
         if (future) {
             this._future = future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length);
@@ -92,6 +93,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
     }
 
     // adds a cookie to the lightbox view - the cookie becomes part of a filter which will display any documents whose cookie metadata field matches this cookie
+    @action
     public static SetCookie(cookie: string) {
         if (this.LightboxDoc && cookie) {
             this._docFilters = (f => this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f])(`cookies:${cookie}:provide`);
@@ -117,11 +119,11 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         } else {
             if (!target && LightboxView.path.length) {
                 const saved = LightboxView._savedState;
-                if (LightboxView.LightboxDoc) {
-                    LightboxView.LightboxDoc._panX = saved?.panX;
-                    LightboxView.LightboxDoc._panY = saved?.panY;
-                    LightboxView.LightboxDoc._viewScale = saved?.scale;
-                    LightboxView.LightboxDoc._viewTransition = saved?.transition;
+                if (LightboxView.LightboxDoc && saved) {
+                    LightboxView.LightboxDoc._panX = saved.panX;
+                    LightboxView.LightboxDoc._panY = saved.panY;
+                    LightboxView.LightboxDoc._viewScale = saved.scale;
+                    LightboxView.LightboxDoc._viewTransition = undefined;
                 }
                 const pop = LightboxView.path.pop();
                 if (pop) {
@@ -149,12 +151,17 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         }
         const { doc, target } = LightboxView._history?.lastElement();
         const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
-        const focusSpeed = 1000;
-        doc._viewTransition = `transform ${focusSpeed}ms`;
-        LightboxView._doc = doc;
-        LightboxView._docTarget = target || doc;
-        docView?.focus(doc, { willZoom: true, scale: 0.9 });
-        setTimeout(() => doc._viewTransition = undefined, focusSpeed);
+        if (docView) {
+            LightboxView._docTarget = undefined;
+            const focusSpeed = 1000;
+            doc._viewTransition = `transform ${focusSpeed}ms`;
+            if (!target) docView.ComponentView?.shrinkWrap?.();
+            else docView.focus(target, { willZoom: true, scale: 0.9 });
+            setTimeout(() => doc._viewTransition = undefined, focusSpeed);
+        }
+        else {
+            LightboxView.SetLightboxDoc(doc, target);
+        }
         if (LightboxView._future?.lastElement() !== previous.target || previous.doc) LightboxView._future?.push(previous.target || previous.doc);
         LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links).map(link => {
             const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!);
@@ -185,6 +192,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         setTimeout(LightboxView.Next);
     }
 
+    future = () => LightboxView._future;
+    tourMap = () => LightboxView._tourMap;
     fitToBox = () => LightboxView._docTarget === LightboxView.LightboxDoc;
     render() {
         let downx = 0, downy = 0;
@@ -204,11 +213,13 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                 }}>
                     <DocumentView ref={action((r: DocumentView | null) => {
                         LightboxView._docView = r !== null ? r : undefined;
-                        setTimeout(action(() => {
-                            if (r && LightboxView._docTarget === r.props.Document) {
-                                r.ComponentView?.shrinkWrap?.();
-                            }
-                            r && (LightboxView._docTarget = undefined);
+                        r && setTimeout(action(() => {
+                            const target = LightboxView._docTarget;
+                            const doc = LightboxView._doc;
+                            const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+                            if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.();
+                            else target && targetView?.focus(target, { willZoom: true, scale: 0.9, instant: true });
+                            LightboxView._docTarget = undefined;
                         }));
                     })}
                         Document={LightboxView.LightboxDoc}
@@ -246,13 +257,25 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         e.stopPropagation();
                         LightboxView.Next();
                     })}
-                {this.navBtn("50%", 0, 0, "chevron-down",
-                    () => LightboxView.LightboxDoc && LightboxView._future?.length ? "" : "none", e => {
-                        e.stopPropagation();
-                        this.stepInto();
-                    },
-                    StrCast(LightboxView._tourMap?.lastElement()?.TourMap)
-                )}
+                <LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} tourMap={this.tourMap} />
             </div>;
     }
+}
+interface LightboxTourBtnProps {
+    navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => JSX.Element;
+    tourMap: () => Opt<Doc[]>;
+    future: () => Opt<Doc[]>;
+    stepInto: () => void;
+}
+@observer
+export class LightboxTourBtn extends React.Component<LightboxTourBtnProps> {
+    render() {
+        return this.props.navBtn("50%", 0, 0, "chevron-down",
+            () => LightboxView.LightboxDoc && this.props.future()?.length ? "" : "none", e => {
+                e.stopPropagation();
+                this.props.stepInto();
+            },
+            StrCast(this.props.tourMap()?.lastElement()?.TourMap)
+        )
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dfca2ba07..4040362d8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -914,7 +914,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 HistoryUtil.pushState(state);
             }
         }
-        const LightboxState = LightboxView.GetSavedState(this.props.Document);
         SelectionManager.DeselectAll();
         if (this.props.Document.scrollHeight) {
             this.props.focus(doc, options);
@@ -931,7 +930,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }
             // focus on the document in the collection
             const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== undefined);
-            const focusSpeed = didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0;
+            const focusSpeed = options?.instant ? 0 : didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0;
             // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
             if (didMove) {
                 this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
@@ -945,10 +944,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 doc.hidden && Doc.UnHighlightDoc(doc);
                 const resetView = options?.afterFocus ? await options?.afterFocus(moved) : ViewAdjustment.doNothing;
                 if (resetView) {
-                    const restoreState = LightboxView.LightboxDoc !== this.props.Document && LightboxState ? LightboxState : savedState;
-                    this.Document._panX = restoreState.panX;
-                    this.Document._panY = restoreState.panY;
-                    this.Document[this.scaleFieldKey] = restoreState.scale;
+                    const restoreState = !LightboxView.LightboxDoc || LightboxView.LightboxDoc === this.props.Document && savedState;
+                    if (typeof restoreState !== "boolean") {
+                        this.Document._panX = restoreState.panX;
+                        this.Document._panY = restoreState.panY;
+                        this.Document[this.scaleFieldKey] = restoreState.scale;
+                    }
                     runInAction(() => this._viewTransition = 0);
                 }
                 return resetView;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 01d799255..e58aaba65 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -71,6 +71,7 @@ export interface DocFocusOptions {
     scale?: number;       // percent of containing frame to zoom into document
     afterFocus?: DocAfterFocusFunc;  // function to call after focusing on a document
     docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom.  when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
+    instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
 }
 export type DocAfterFocusFunc = (notFocused: boolean) => Promise<ViewAdjustment>;
 export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void;
@@ -80,7 +81,7 @@ export interface DocComponentView {
     scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
     setViewSpec?: (anchor: Doc, preview: boolean) => void;  // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
     reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height.  However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
-    shrinkWrap?: () => boolean;  // requests a document to display all of its contents with no white space.  currently only implemented (needed?) for freeform views
+    shrinkWrap?: () => void;  // requests a document to display all of its contents with no white space.  currently only implemented (needed?) for freeform views
     menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
 }
 export interface DocumentViewSharedProps {
-- 
cgit v1.2.3-70-g09d2

