From eec81f7e0b53395e3e2ea25663a9ea06ec83085d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 2 Nov 2023 19:30:19 -0400
Subject: performance fixes - don't invalidate as much by using reactions in
 place of computd values;  don't make things active when things are dragged
 unless CanEmbed; fix for linkBox to use reaction.

---
 src/client/views/nodes/DocumentView.tsx | 62 ++++++++++++++++++++++-----------
 1 file changed, 42 insertions(+), 20 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3d6b53ccc..30d5a5184 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { Dropdown, DropdownType, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
@@ -136,7 +136,7 @@ export interface DocComponentView {
     componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
     incrementalRendering?: () => void;
     layout_fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox)
-    overridePointerEvents?: () => 'all' | 'none' | undefined; // if the conmponent overrides the pointer events for the document
+    overridePointerEvents?: () => 'all' | 'none' | undefined; // if the conmponent overrides the pointer events for the document (e.g, KeyValueBox always allows pointer events)
     fieldKey?: string;
     annotationKey?: string;
     getTitle?: () => string;
@@ -272,8 +272,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return this._animateScaleTime ?? 100;
     }
     public get displayName() {
-        return 'DocumentView(' + this.props.Document.title + ')';
+        return 'DocumentViewInternal(' + this.props.Document.title + ')';
     } // this makes mobx trace() statements more descriptive
+
     public get ContentDiv() {
         return this._mainCont.current;
     }
@@ -317,8 +318,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get titleHeight() {
         return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0;
     }
+    @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined;
     @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
-        return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents);
+        TraceMobx();
+        return this._pointerEvents;
     }
     @computed get finalLayoutKey() {
         return StrCast(this.Document.layout_fieldKey, 'layout');
@@ -330,6 +333,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return this.props.NativeHeight();
     }
     @computed get disableClickScriptFunc() {
+        TraceMobx();
         const onScriptDisable = this.props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
         // prettier-ignore
         return (
@@ -356,6 +360,25 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
     componentDidMount() {
         this.setupHandlers();
+        this._disposers.contentActive = reaction(
+            () => {
+                //  true  - if the document has been activated directly or indirectly (by having its children selected)
+                //  false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive
+                // undefined - it is not active, but it should be responsive to actions that might activate it or its contents (eg clicking)
+                return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none'
+                    ? false
+                    : Doc.ActiveTool !== InkTool.None || SnappingManager.GetCanEmbed() || this.rootSelected() || this.rootDoc.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()
+                    ? true
+                    : undefined;
+            },
+            active => (this._isContentActive = active),
+            { fireImmediately: true }
+        );
+        this._disposers.pointerevents = reaction(
+            () => this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents),
+            pointerevents => (this._pointerEvents = pointerevents),
+            { fireImmediately: true }
+        );
     }
     preDropFunc = (e: Event, de: DragManager.DropEvent) => {
         const dropAction = this.layoutDoc.dropAction as dropActionType;
@@ -883,23 +906,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
     setHeight = (height: number) => (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
-    @computed get _isContentActive() {
-        //  true  - if the document has been activated directly or indirectly (by having its children selected)
-        //  false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive
-        // undefined - it is not active, but it should be responsive to actions that might active it or its contents (eg clicking)
-        return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none'
-            ? false
-            : Doc.ActiveTool !== InkTool.None || SnappingManager.GetIsDragging() || this.rootSelected() || this.rootDoc.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()
-            ? true
-            : undefined;
-    }
+    @observable _isContentActive: boolean | undefined;
+
     isContentActive = (): boolean | undefined => this._isContentActive;
     childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
 
     /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
     @computed get _contentPointerEvents() {
-        if (this.props.contentPointerEvents) return this.props.contentPointerEvents;
-        return (!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.() && this.isContentActive() !== true) || this.isContentActive() === false ? 'none' : this.pointerEvents;
+        TraceMobx();
+        return this.props.contentPointerEvents ??
+            ((!this.disableClickScriptFunc && //
+                this.onClickHandler &&
+                !this.props.onBrowseClick?.() &&
+                this.isContentActive() !== true) ||
+                this.isContentActive() === false)
+            ? 'none'
+            : this.pointerEvents;
     }
     contentPointerEvents = () => this._contentPointerEvents;
     @computed get contents() {
@@ -1304,8 +1326,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 onContextMenu={this.onContextMenu}
                 onPointerDown={this.onPointerDown}
                 onClick={this.onClick}
-                onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)}
-                onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)}
+                onPointerEnter={e => (!SnappingManager.GetIsDragging() || SnappingManager.GetCanEmbed()) && Doc.BrushDoc(this.rootDoc)}
+                onPointerOver={e => (!SnappingManager.GetIsDragging() || SnappingManager.GetCanEmbed()) && Doc.BrushDoc(this.rootDoc)}
                 onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.rootDoc)}
                 style={{
                     borderRadius: this.borderRounding,
@@ -1426,9 +1448,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     @computed get hideLinkButton() {
         return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkBtn + (this.isSelected() ? ':selected' : ''));
     }
+    hideLinkCount = () => this.props.renderDepth === -1 || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton;
     @computed get linkCountView() {
-        const hideCount = this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton;
-        return hideCount ? null : <DocumentLinksButton View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
+        return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
     }
     @computed get docViewPath(): DocumentView[] {
         return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this];
-- 
cgit v1.2.3-70-g09d2

