From f00096cea4613c13672ba5dbc0bd60c461c92603 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 16 Nov 2023 14:57:29 -0500
Subject: fixed alt-drag to toggle scroll mode.  fixed replaceTab to work
 properly with panelNames.   made slides pointing to trails open the target
 trails in situ.  cleaned up code when starting drag to call freeformview more
 nicel.   fixed brush/highlight of tabs.  made
 collectionFreeformView/FreeformDocs use a more robust and simpler approach to
 caching freeform values.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 128 ++++++---------------
 1 file changed, 38 insertions(+), 90 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 57c943479..cafa8b38f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -15,7 +15,7 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../
 import { ImageField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -36,7 +36,7 @@ import { GestureOverlay } from '../../GestureOverlay';
 import { CtrlKey } from '../../GlobalKeyHandler';
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
 import { LightboxView } from '../../LightboxView';
-import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
+import { CollectionFreeFormDocumentViewWrapper, CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
 import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
@@ -45,7 +45,6 @@ import { CreateImage } from '../../nodes/WebBoxRenderer';
 import { StyleProp } from '../../StyleProvider';
 import { CollectionSubView } from '../CollectionSubView';
 import { TreeViewType } from '../CollectionTreeView';
-import { TabDocView } from '../TabDocView';
 import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid';
 import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents';
@@ -86,9 +85,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     private _hitCluster: number = -1;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _renderCutoffData = observable.map<string, boolean>();
-    private _layoutPoolData = observable.map<string, PoolData>();
-    private _layoutSizeData = observable.map<string, { width?: number; height?: number }>();
-    private _cachedPool: Map<string, PoolData> = new Map();
     private _batch: UndoManager.Batch | undefined = undefined;
     private _brushtimer: any;
     private _brushtimer1: any;
@@ -109,7 +105,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return (this.props.viewField ?? '') + '_freeform_autoReset';
     }
 
-    @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
+    @computed get _layoutElements(): ViewDefResult[] {
+        const computation = this.doInternalLayoutComputation;
+        return this.doLayoutComputation(computation.newPool, computation.computedElementData);
+    }
     @observable _panZoomTransition: 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 _firstRender = false; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown.  could be used for performance improvement
     @observable _showAnimTimeline = false;
@@ -120,7 +119,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined; // highlighted region of freeform canvas used by presentations to indicate a region
     @observable GroupChildDrag: boolean = false; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
 
-    @computed get views() {
+    @computed get contentViews() {
         const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele);
         const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && (ele.inkMask === -1 || ele.inkMask === undefined)).map(ele => ele.ele);
         if (viewsMask.length) renderableEles.push(<div className={`collectionfreeformview-mask${this._layoutElements.some(ele => (ele.inkMask ?? 0) > 0) ? '' : '-empty'}`}>{viewsMask}</div>);
@@ -637,7 +636,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this._downX = this._lastX = e.pageX;
         this._downY = this._lastY = e.pageY;
         this._downTime = Date.now();
-        if (e.button === 0 && !e.altKey && (!(e.ctrlKey && !e.metaKey) || Doc.UserDoc().freeformScrollMode !== freeformScrollMode.Pan) && this.props.isContentActive(true)) {
+        const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode;
+        if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this.props.isContentActive(true)) {
             if (
                 !this.props.Document._isGroup && // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
                 !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) &&
@@ -1052,7 +1052,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
             const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc);
             const measuredDocs = docs
-                .map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ''), size: this.childSizeProviderUnmemoized(doc, '') }))
+                .map(doc => ({ pos: { x: NumCast(doc.x), y: NumCast(doc.y) }, size: { width: NumCast(doc.width), height: NumCast(doc.height) } }))
                 .filter(({ pos, size }) => pos && size)
                 .map(({ pos, size }) => ({ pos: pos!, size: size! }));
             if (measuredDocs.length) {
@@ -1247,7 +1247,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
               this.isContentActive() === false
                   ? 'none'
                   : this.props.pointerEvents?.());
-        console.log(`${this.rootDoc.title} pe = ` + pointerevents);
         return pointerevents;
     }
 
@@ -1258,16 +1257,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const childLayout = entry.pair.layout;
         const childData = entry.pair.data;
         return (
-            <CollectionFreeFormDocumentView
+            <CollectionFreeFormDocumentViewWrapper
+                {...OmitKeys(entry, ['replica', 'pair']).omit}
                 key={childLayout[Id] + (entry.replica || '')}
                 DataDoc={childData}
                 Document={childLayout}
+                dragStarting={this.dragStarting}
+                dragEnding={this.dragEnding}
                 isGroupActive={this.props.isGroupActive}
                 renderDepth={this.props.renderDepth + 1}
-                replica={entry.replica}
                 hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)}
                 suppressSetHeight={this.layoutEngine ? true : false}
-                renderCutoffProvider={this.renderCutoffProvider}
+                RenderCutoffProvider={this.renderCutoffProvider}
                 CollectionFreeFormView={this}
                 LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
                 LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
@@ -1295,8 +1296,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 docViewPath={this.props.docViewPath}
                 styleProvider={this.clusterStyleProvider}
                 dragAction={(this.rootDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
-                dataProvider={this.childDataProvider}
-                sizeProvider={this.childSizeProvider}
                 bringToFront={this.bringToFront}
                 layout_showTitle={this.props.childlayout_showTitle}
                 dontRegisterView={this.props.dontRegisterView}
@@ -1425,20 +1424,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }.bind(this)
     );
 
-    childPositionProviderUnmemoized = (doc: Doc, replica: string) => this._layoutPoolData.get(doc[Id] + (replica || ''));
-    childDataProvider = computedFn(
-        function childDataProvider(this: any, doc: Doc, replica: string) {
-            return this.childPositionProviderUnmemoized(doc, replica);
-        }.bind(this)
-    );
-
-    childSizeProviderUnmemoized = (doc: Doc, replica: string) => this._layoutSizeData.get(doc[Id] + (replica || ''));
-    childSizeProvider = computedFn(
-        function childSizeProvider(this: any, doc: Doc, replica: string) {
-            return this.childSizeProviderUnmemoized(doc, replica);
-        }.bind(this)
-    );
-
     doEngineLayout(
         poolData: Map<string, PoolData>,
         engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[]
@@ -1467,46 +1452,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return { newPool, computedElementData: this.doFreeformLayout(newPool) };
     }
 
-    @observable _numLoaded = 1;
     @action
     doLayoutComputation = (newPool: Map<string, PoolData>, computedElementData: ViewDefResult[]) => {
-        const array = Array.from(newPool.entries());
-        for (const entry of array) {
-            const lastPos = this._cachedPool.get(entry[0]); // last computed pos
-            const newPos = entry[1];
-            if (
-                !lastPos ||
-                newPos.color !== lastPos.color ||
-                newPos.backgroundColor !== lastPos.backgroundColor ||
-                newPos.opacity !== lastPos.opacity ||
-                newPos.x !== lastPos.x ||
-                newPos.y !== lastPos.y ||
-                newPos.z !== lastPos.z ||
-                newPos.rotation !== lastPos.rotation ||
-                newPos.zIndex !== lastPos.zIndex ||
-                newPos.transition !== lastPos.transition ||
-                newPos.pointerEvents !== lastPos.pointerEvents
-            ) {
-                this._layoutPoolData.set(entry[0], newPos);
-            }
-            if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
-                this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
-            }
-        }
-        this._cachedPool.clear();
-        Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
         const elements = computedElementData.slice();
         Array.from(newPool.entries())
             .filter(entry => this.isCurrent(entry[1].pair.layout))
-            .forEach((entry, i) => {
-                const childData: ViewDefBounds = this.childDataProvider(entry[1].pair.layout, entry[1].replica);
-                const childSize = this.childSizeProvider(entry[1].pair.layout, entry[1].replica);
+            .forEach(entry =>
                 elements.push({
                     ele: this.getChildDocView(entry[1]),
-                    bounds: childData.opacity === 0 ? { ...childData, width: 0, height: 0 } : { ...childData, width: childSize.width, height: childSize.height },
+                    bounds: (entry[1].opacity === 0 ? { ...entry[1], width: 0, height: 0 } : { ...entry[1] }) as any,
                     inkMask: BoolCast(entry[1].pair.layout.stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1,
-                });
-            });
+                })
+            );
 
         this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
         return elements;
@@ -1580,12 +1537,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     { fireImmediately: true }
                 );
 
-                this._disposers.layoutComputation = reaction(
-                    () => this.doInternalLayoutComputation,
-                    ({ newPool, computedElementData }) => (this._layoutElements = this.doLayoutComputation(newPool, computedElementData)),
-                    { fireImmediately: true, name: 'layoutComputationReaction' }
-                );
-
                 this._disposers.active = reaction(
                     () => this.isContentActive(), // if autoreset is on, then whenever the view is selected, it will be restored to it default pan/zoom positions
                     active => !SnappingManager.GetIsDragging() && this.rootDoc[this.autoResetFieldKey] && active && this.resetView()
@@ -1752,7 +1703,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             return;
         }
         !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
-        appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
+        appearanceItems.push({ description: `Pin View`, event: () => this.props.pinToPres(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
         !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
         this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
 
@@ -1799,13 +1750,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         SnappingManager.clearSnapLines();
     };
     @action
-    dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean, visited = new Set<Doc>()) => {
+    dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean = true, visited = new Set<Doc>()) => {
         if (visited.has(this.rootDoc)) return;
         visited.add(this.rootDoc);
         showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document._isGroup));
-        if (this.rootDoc._isGroup && this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
-            this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.dragStarting(snapToDraggedDoc, false, visited);
-        }
         const activeDocs = this.getActiveDocuments();
         const size = this.screenToLocalXf.transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
         const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
@@ -1813,13 +1761,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
 
         const snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
-        activeDocs.forEach(
-            doc =>
-                doc._isGroup &&
-                SnappingManager.GetIsResizing() !== doc &&
-                !DragManager.docsBeingDragged.includes(doc) &&
-                (DocumentManager.Instance.getDocumentView(doc)?.ComponentView as CollectionFreeFormView)?.dragStarting(snapToDraggedDoc, false, visited)
-        );
+        activeDocs
+            .filter(doc => doc._isGroup && SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc))
+            .forEach(doc => DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited));
 
         const horizLines: number[] = [];
         const vertLines: number[] = [];
@@ -1850,10 +1794,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
     });
 
-    get children() {
-        this.incrementalRender();
-        const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : this.props.children ? [this.props.children] : [];
-        return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />];
+    // if a freeform view has any children, then the children will likely consist of a single child
+    // which will be a DocumentView.  In this sitation, this freeform views acts as an annotation overlay for
+    // the underlying DocumentView and will pan and scoll with the underlying Documen tView.
+    @computed get underlayViews() {
+        return this.props.children ? [this.props.children] : [];
     }
 
     @computed get placeholder() {
@@ -1889,6 +1834,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         );
     }
     @computed get pannableContents() {
+        this.incrementalRender();
         return (
             <CollectionFreeFormPannableContents
                 rootDoc={this.rootDoc}
@@ -1897,7 +1843,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 transform={this.PanZoomCenterXf}
                 transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))}
                 viewDefDivClick={this.props.viewDefDivClick}>
-                {this.children}
+                {this.underlayViews}
+                {this.contentViews}
+                <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
             </CollectionFreeFormPannableContents>
         );
     }
@@ -2048,11 +1996,11 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
             if (!focused) {
                 const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
                 let containers = dv.props.docViewPath();
-                let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+                let parFfview = dv.CollectionFreeFormView;
                 for (var cont of containers) {
-                    parFfview = parFfview ?? cont.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+                    parFfview = parFfview ?? cont.CollectionFreeFormView;
                 }
-                while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+                while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.DocumentView?.().CollectionFreeFormView;
                 const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
                 ffview?.zoomSmoothlyAboutPt(ffview.screenToLocalXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
                 Doc.linkFollowHighlight(dv?.props.Document, false);
@@ -2084,8 +2032,8 @@ ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
     );
 });
 ScriptingGlobals.add(function bringToFront() {
-    SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc));
+    SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc));
 });
 ScriptingGlobals.add(function sendToBack(doc: Doc) {
-    SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc, true));
+    SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc, true));
 });
-- 
cgit v1.2.3-70-g09d2

