From 62eb66ca7d3404f9977acdf73f815f4920fb964d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 16 Sep 2024 18:14:56 -0400
Subject: fixed doc decorations from crashing when dockingView is selected.
 cleaned up tab doc view.

---
 src/client/views/collections/TabDocView.tsx | 175 ++++++++++++----------------
 1 file changed, 72 insertions(+), 103 deletions(-)

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

diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 31b6be927..def1ea731 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -57,6 +57,7 @@ interface TabMiniThumbProps {
     miniLeft: () => number;
 }
 
+export type TabHTMLElement = HTMLDivElement & { InitTab?: (tab: object) => void };
 @observer
 class TabMiniThumb extends React.Component<TabMiniThumbProps> {
     render() {
@@ -193,8 +194,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
             .filter(tv => tv._document)
             .map(tv => tv._document!);
     }
-    _mainCont: HTMLDivElement | null = null;
+    _mainCont: TabHTMLElement | null = null;
     _tabReaction: IReactionDisposer | undefined;
+    _lastSelection = 0; // time when view was last selected - used to re-select views that get invalidated when selected
 
     /**
      * Adds a document to the presentation view
@@ -273,19 +275,24 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
     }
 
+    // Flag indicating that when a tab is activated, it should not select it's document.
+    // this is used by the link properties menu when it wants to display the link target without selecting the target (which would make the link property window go away since it would no longer be selected)
+    public static DontSelectOnActivate = 'dontSelectOnActivate';
+
+    public static IsSelected = (doc?: Doc) => {
+        return DocumentView.getViews(doc).some(dv => dv?.IsSelected);
+    };
+
     static Activate = (tabDoc: Doc) => {
         const tab = Array.from(CollectionDockingView.Instance?.tabMap ?? []).find(findTab => findTab.DashDoc === tabDoc && !findTab.contentItem.config.props.keyValue);
         tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
         return tab !== undefined;
     };
-    // static ActivateTabView(doc: Doc) {
-    //     const tabView = Array.from(TabDocView._allTabs).find(view => view._document === doc);
-    //     if (!tabView?._activated && tabView?._document) {
-    //         TabDocView.Activate(tabView?._document);
-    //         return tabView;
-    //     }
-    //     return undefined;
-    // }
+
+    get stack()           { return this._props.glContainer.parent.parent; } // prettier-ignore
+    get tab()             { return this._props.glContainer.tab;  } // prettier-ignore
+    get view()            { return this._view;  } // prettier-ignore
+
     constructor(props: TabDocViewProps) {
         super(props);
         makeObservable(this);
@@ -299,37 +306,13 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     @observable _hovering = false;
     @observable _isActive: boolean = false;
     @observable _isAnyChildContentActive = false;
-    public static IsSelected = (doc?: Doc) => {
-        if (DocumentView.getViews(doc).some(dv => dv?.IsSelected)) {
-            return true;
-        }
-        return false;
-    };
-    @computed get _isUserActivated() {
-        return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive;
-    }
-    get _isContentActive() {
-        return this._isUserActivated || this._hovering;
-    }
     @observable _document: Doc | undefined = undefined;
     @observable _view: DocumentView | undefined = undefined;
+    @observable _forceInvalidateScreenToLocal = 0; // screentolocal is computed outside of react using a dom resize ovbserver.  this hack allows the resize observer to trigger a react update
 
-    @computed get layoutDoc() {
-        return this._document && Doc.Layout(this._document);
-    }
-
-    get stack() {
-        return this._props.glContainer.parent.parent;
-    }
-    get tab() {
-        return this._props.glContainer.tab;
-    }
-    get view() {
-        return this._view;
-    }
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    _lastTab: any;
-    _lastView: DocumentView | undefined;
+    @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } // prettier-ignore
+    @computed get isUserActivated() {  return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive; } // prettier-ignore
+    @computed get isContentActive() { return this.isUserActivated || this._hovering; } // prettier-ignore
 
     @action
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -413,7 +396,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                             color === variant
                                 ? DashColor(color)
                                       .fade(
-                                          this._isUserActivated
+                                          this.isUserActivated
                                               ? 0
                                               : this._hovering
                                                 ? 0.25
@@ -522,19 +505,14 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         this._props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
     }
 
-    // Flag indicating that when a tab is activated, it should not select it's document.
-    // this is used by the link properties menu when it wants to display the link target without selecting the target (which would make the link property window go away since it would no longer be selected)
-    public static DontSelectOnActivate = 'dontSelectOnActivate';
-
-    @action.bound
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    private onActiveContentItemChanged(contentItem: any) {
+    onActiveContentItemChanged = (contentItem: any) => {
         if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) {
             this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab;
             if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => DocumentView.SelectView(this._view, false));
             !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
         }
-    }
+    };
 
     // adds a tab to the layout based on the locaiton parameter which can be:
     //  close[:{left,right,top,bottom}]  - e.g., "close" will close the tab, "close:left" will close the left tab,
@@ -551,7 +529,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
         const panelName = whereFields.length > 1 ? whereFields.lastElement() : '';
         if (docs[0]?.dockingConfig && !keyValue) return DashboardView.openDashboard(docs[0]);
-        // prettier-ignore
         switch (whereFields[0]) {
             case undefined:
             case OpenWhere.lightbox:   return LightboxView.Instance.AddDocTab(docs[0], location);
@@ -559,7 +536,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
             case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(docs[0], whereMods, this.stack, panelName, undefined, keyValue);
             case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(docs[0], whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue);
             case OpenWhere.add:default:return CollectionDockingView.AddSplit(docs[0], whereMods, this.stack, undefined, keyValue);
-        }
+        } // prettier-ignore
     };
     remDocTab = (doc: Doc | Doc[]) => {
         if (doc === this._document) {
@@ -571,8 +548,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     };
 
     getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
-
-    @action
     focusFunc = () => {
         if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
             this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
@@ -580,7 +555,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         return undefined;
     };
     active = () => this._isActive;
-    @observable _forceInvalidateScreenToLocal = 0;
     ScreenToLocalTransform = () => {
         this._forceInvalidateScreenToLocal;
         const { translateX, translateY } = ClientUtils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
@@ -594,47 +568,44 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     whenChildContentActiveChanges = (isActive: boolean) => {
         this._isAnyChildContentActive = isActive;
     };
-    isContentActive = () => this._isContentActive;
+    isContentActiveFunc = () => this.isContentActive;
     waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined);
-    @computed get docView() {
-        return !this._activated || !this._document ? null : (
-            <>
-                <DocumentView
-                    key={this._document[Id]}
-                    ref={action((r: DocumentView) => {
-                        this._lastView && DocumentView.removeView(this._lastView);
-                        this._view = r;
-                        this._lastView = this._view;
-                    })}
-                    renderDepth={0}
-                    LayoutTemplateString={this._props.keyValue ? KeyValueBox.LayoutString() : undefined}
-                    hideTitle={this._props.keyValue}
-                    Document={this._document}
-                    TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
-                    waitForDoubleClickToClick={this.waitForDoubleClick}
-                    isContentActive={this.isContentActive}
-                    isDocumentActive={returnFalse}
-                    PanelWidth={this.PanelWidth}
-                    PanelHeight={this.PanelHeight}
-                    styleProvider={DefaultStyleProvider}
-                    childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyFilter}
-                    childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter}
-                    searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
-                    addDocument={undefined}
-                    removeDocument={this.remDocTab}
-                    addDocTab={this.addDocTab}
-                    suppressSetHeight={!!this._document._layout_fitWidth}
-                    ScreenToLocalTransform={this.ScreenToLocalTransform}
-                    dontCenter="y"
-                    whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
-                    focus={this.focusFunc}
-                    containerViewPath={returnEmptyDocViewList}
-                    pinToPres={TabDocView.PinDoc}
-                />
-                {this.disableMinimap() ? null : <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />}
-            </>
-        );
-    }
+    renderDocView = (doc: Doc) => (
+        <DocumentView
+            key={doc[Id]}
+            ref={action((r: DocumentView) => {
+                const now = Date.now();
+                this._lastSelection = this._view?.IsSelected ? now : this._lastSelection;
+                if (this._view) DocumentView.removeView(this._view);
+                this._view = r;
+                if (this._view && now - this._lastSelection < 1000) this._view.select(false);
+            })}
+            renderDepth={0}
+            LayoutTemplateString={this._props.keyValue ? KeyValueBox.LayoutString() : undefined}
+            hideTitle={this._props.keyValue}
+            Document={doc}
+            TemplateDataDocument={!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined}
+            waitForDoubleClickToClick={this.waitForDoubleClick}
+            isContentActive={this.isContentActiveFunc}
+            isDocumentActive={returnFalse}
+            PanelWidth={this.PanelWidth}
+            PanelHeight={this.PanelHeight}
+            styleProvider={DefaultStyleProvider}
+            childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyFilter}
+            childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter}
+            searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
+            addDocument={undefined}
+            removeDocument={this.remDocTab}
+            addDocTab={this.addDocTab}
+            suppressSetHeight={!!doc._layout_fitWidth}
+            ScreenToLocalTransform={this.ScreenToLocalTransform}
+            dontCenter="y"
+            whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
+            focus={this.focusFunc}
+            containerViewPath={returnEmptyDocViewList}
+            pinToPres={TabDocView.PinDoc}
+        />
+    );
 
     render() {
         return (
@@ -647,23 +618,21 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                 onPointerLeave={action(() => { this._hovering = false; })} // prettier-ignore
                 onDragOver={action(() =>     { this._hovering = true; })} // prettier-ignore
                 onDragLeave={action(() =>    { this._hovering = false; })} // prettier-ignore
-                ref={ref => {
+                ref={(ref: TabHTMLElement) => {
+                    // "add" an InitTab function to this div to call from tabCreated in CollectionDockingView when div is reused
                     this._mainCont = ref;
                     if (this._mainCont) {
-                        if (this._lastTab) {
-                            this._view && DocumentView.removeView(this._view);
-                        }
-                        this._lastTab = this.tab;
-                        (this._mainCont as { InitTab?: (tab: object) => void }).InitTab = (tab: object) => this.init(tab, this._document);
-                        DocServer.GetRefField(this._props.documentId).then(
-                            action(doc => {
-                                doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document);
-                            })
-                        );
-                        ref && new ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(ref);
+                        this._mainCont.InitTab = (tab: object) => this.init(tab, this._document);
+                        DocServer.GetRefField(this._props.documentId).then(action(doc => {
+                            doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document);
+                        })); // prettier-ignore
+                        new ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(this._mainCont);
                     }
                 }}>
-                {this.docView}
+                {!this._activated || !this._document ? null : this.renderDocView(this._document)}
+                {this.disableMinimap() || !this._document ? null : (
+                    <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />
+                )}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2

