From 3381bbb0ef5160707513f4bbbe551ca551b64b0d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 13 Nov 2021 10:38:53 -0500
Subject: change isContentActive to a tri-state to allow turning on/off and
 default - fixes issues with videobox and others so that content can be turned
 off reliably.   added annotation overlay for treeViews for ppt like slides. 
 lots of fixes to tree view to get layout to be more robust.

---
 .../views/collections/CollectionTreeView.tsx       | 252 ++++++++++++---------
 1 file changed, 147 insertions(+), 105 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 3852987b9..6fb18d4c2 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,4 +1,3 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
@@ -8,13 +7,14 @@ import { Document, listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, emptyFunction } from '../../../Utils';
+import { emptyFunction, OmitKeys, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
+import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
@@ -22,11 +22,11 @@ import { EditableView } from "../EditableView";
 import { DocumentView } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
+import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import { TreeView } from "./TreeView";
 import React = require("react");
-import { Transform } from '../../util/Transform';
 const _global = (window /* browser */ || global /* node */) as any;
 
 export type collectionTreeViewProps = {
@@ -41,10 +41,13 @@ export type collectionTreeViewProps = {
 
 @observer
 export class CollectionTreeView extends CollectionSubView<Document, Partial<collectionTreeViewProps>>(Document) {
-    private treedropDisposer?: DragManager.DragDropDisposer;
+    private _treedropDisposer?: DragManager.DragDropDisposer;
     private _mainEle?: HTMLDivElement;
+    private _titleRef?: HTMLDivElement | HTMLInputElement | null;
     private _disposers: { [name: string]: IReactionDisposer } = {};
-    MainEle = () => this._mainEle;
+    private _isDisposing = false; // notes that instance is in process of being disposed
+    private refList: Set<any> = new Set(); // list of tree view items to monitor for height changes
+    private observer: any; // observer for mnonitoring tree view items.
 
     @computed get doc() { return this.props.Document; }
     @computed get dataDoc() { return this.props.DataDoc || this.doc; }
@@ -54,6 +57,10 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
     @computed get fileSysMode() { return this.doc.treeViewType === "fileSystem"; }
     @computed get dashboardMode() { return this.doc === Doc.UserDoc().myDashboards; }
 
+    @observable _explainerHeight = 0; // height of the description of the tree view
+
+    MainEle = () => this._mainEle;
+
     // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent
     @observable _isAnyChildContentActive = false;
     whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive));
@@ -62,11 +69,10 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
             this.props.isSelected(outsideReaction) || this._isAnyChildContentActive ||
             this.props.rootSelected(outsideReaction)) ? true : false)
 
-    isDisposing = false;
     componentWillUnmount() {
-        this.isDisposing = true;
+        this._isDisposing = true;
         super.componentWillUnmount();
-        this.treedropDisposer?.();
+        this._treedropDisposer?.();
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
@@ -76,13 +82,13 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
             { fireImmediately: true });
     }
 
-    refList: Set<any> = new Set();
-    observer: any;
     computeHeight = () => {
-        if (this.isDisposing) return;
-        const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), this.paddingTop() + this.paddingBot());
-        this.layoutDoc._autoHeightMargins = bodyHeight;
-        this.props.setHeight(this.documentTitleHeight() + bodyHeight);
+        if (!this._isDisposing) {
+            const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace("px", ""));
+            const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), this.marginBot());
+            this.layoutDoc._autoHeightMargins = bodyHeight;
+            this.props.setHeight(bodyHeight + titleHeight);
+        }
     }
     unobserveHeight = (ref: any) => {
         this.refList.delete(ref);
@@ -101,8 +107,8 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         }
     }
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
-        this.treedropDisposer?.();
-        if (this._mainEle = ele) this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this));
+        this._treedropDisposer?.();
+        if (this._mainEle = ele) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this));
     }
 
     protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
@@ -165,60 +171,54 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true);
     }
 
-    editableTitle = (childDocs: Doc[]) => {
-        return !this.dataDoc ? (null) :
-            <EditableView
-                contents={this.dataDoc.title}
-                display={"block"}
-                maxHeight={72}
-                height={"auto"}
-                GetValue={() => StrCast(this.dataDoc.title)}
-                SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => {
-                    if (enter && this.props.Document.treeViewType === "outline") this.makeTextCollection(childDocs);
-                    this.dataDoc.title = value;
-                    return true;
-                })} />;
+    get editableTitle() {
+        return <EditableView
+            contents={this.dataDoc.title}
+            display={"block"}
+            maxHeight={72}
+            height={"auto"}
+            GetValue={() => StrCast(this.dataDoc.title)}
+            SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => {
+                if (enter && this.props.Document.treeViewType === "outline") this.makeTextCollection(this.treeChildren);
+                this.dataDoc.title = value;
+                return true;
+            })} />;
     }
 
 
-    documentTitle = (childDocs: Doc[]) => {
-        return <div style={{ display: "inline-block", width: "100%", height: this.documentTitleHeight() }} key={this.doc[Id]}
-            onKeyDown={e => {
-                e.stopPropagation();
-                e.key === "Enter" && this.makeTextCollection(childDocs);
-            }}>
-            <DocumentView
-                Document={this.doc}
-                DataDoc={undefined}
-                LayoutTemplateString={FormattedTextBox.LayoutString("text")}
-                renderDepth={this.props.renderDepth + 1}
-                isContentActive={this.isContentActive}
-                isDocumentActive={this.isContentActive}
-                rootSelected={returnTrue}
-                docViewPath={this.props.docViewPath}
-                styleProvider={this.props.styleProvider}
-                layerProvider={this.props.layerProvider}
-                PanelWidth={this.documentTitleWidth}
-                PanelHeight={this.documentTitleHeight}
-                NativeWidth={this.documentTitleWidth}
-                NativeHeight={this.documentTitleHeight}
-                focus={this.props.focus}
-                treeViewDoc={this.props.Document}
-                ScreenToLocalTransform={this.titleTransform}
-                docFilters={returnEmptyFilter}
-                docRangeFilters={returnEmptyFilter}
-                searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionDoc={this.doc}
-                ContainingCollectionView={this.props.CollectionView}
-                addDocument={this.props.addDocument}
-                moveDocument={returnFalse}
-                removeDocument={returnFalse}
-                whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                addDocTab={this.props.addDocTab}
-                pinToPres={this.props.pinToPres}
-                bringToFront={returnFalse}
-            />
-        </div>;
+    get documentTitle() {
+        return <DocumentView
+            Document={this.doc}
+            DataDoc={undefined}
+            LayoutTemplateString={FormattedTextBox.LayoutString("text")}
+            renderDepth={this.props.renderDepth + 1}
+            isContentActive={this.isContentActive}
+            isDocumentActive={this.isContentActive}
+            rootSelected={returnTrue}
+            forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not autoHeight
+            docViewPath={this.props.docViewPath}
+            styleProvider={this.props.styleProvider}
+            layerProvider={this.props.layerProvider}
+            PanelWidth={this.documentTitleWidth}
+            PanelHeight={this.documentTitleHeight}
+            NativeWidth={this.documentTitleWidth}
+            NativeHeight={this.documentTitleHeight}
+            focus={this.props.focus}
+            treeViewDoc={this.props.Document}
+            ScreenToLocalTransform={this.titleTransform}
+            docFilters={returnEmptyFilter}
+            docRangeFilters={returnEmptyFilter}
+            searchFilterDocs={returnEmptyDoclist}
+            ContainingCollectionDoc={this.doc}
+            ContainingCollectionView={this.props.CollectionView}
+            addDocument={this.props.addDocument}
+            moveDocument={returnFalse}
+            removeDocument={returnFalse}
+            whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+            addDocTab={this.props.addDocTab}
+            pinToPres={this.props.pinToPres}
+            bringToFront={returnFalse}
+        />;
     }
     childContextMenuItems = () => {
         const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
@@ -263,21 +263,31 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         );
     }
     @computed get titleBar() {
-        const hideTitle = this.props.treeViewHideTitle || this.doc.treeViewHideTitle;
-        return hideTitle ? (null) : (this.outlineMode ? this.documentTitle : this.editableTitle)(this.treeChildren);
+        return this.dataDoc === null ? (null) :
+            <div className="collectionTreeView-titleBar" key={this.doc[Id]}
+                style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingRight: this.marginX(), paddingTop: this.marginTop() } : {}}
+                ref={r => this._titleRef = r}
+                onKeyDown={e => {
+                    if (this.outlineMode) {
+                        e.stopPropagation();
+                        e.key === "Enter" && this.makeTextCollection(this.treeChildren);
+                    }
+                }}>
+                {this.outlineMode ? this.documentTitle : this.editableTitle}
+            </div>;
+    }
+
+    @computed get noviceExplainer() {
+        return !Doc.UserDoc().noviceMode || !this.rootDoc.explainer ? (null) :
+            <div className="documentExplanation"> {this.rootDoc.explainer} </div>;
     }
 
     return35 = () => 35;
     @computed get buttonMenu() {
-        const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
+        const menuDoc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
         // To create a multibutton menu add a CollectionLinearView
-        if (menuDoc) {
-
-            const width: number = NumCast(menuDoc._width, 30);
-            const height: number = NumCast(menuDoc._height, 30);
-            console.log(menuDoc.title, width, height);
-            return (<div className="buttonMenu-docBtn"
-                style={{ width: width, height: height }}>
+        return !menuDoc ? null :
+            (<div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}>
                 <DocumentView
                     Document={menuDoc}
                     DataDoc={menuDoc}
@@ -306,11 +316,8 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
                     ContainingCollectionDoc={undefined}
                 />
             </div>);
-        }
     }
 
-    @observable _explainerHeight: number = 0;
-
     @computed get nativeWidth() { return Doc.NativeWidth(this.Document, undefined, true); }
     @computed get nativeHeight() { return Doc.NativeHeight(this.Document, undefined, true); }
 
@@ -321,47 +328,82 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale ? wscale : hscale;
     }
-    paddingX = () => NumCast(this.doc._xPadding, 15);
-    paddingTop = () => NumCast(this.doc._yPadding, 20);
-    paddingBot = () => NumCast(this.doc._yPadding, 20);
+    marginX = () => NumCast(this.doc._xMargin);
+    marginTop = () => NumCast(this.doc._yMargin);
+    marginBot = () => NumCast(this.doc._yMargin);
     documentTitleWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.panelWidth());
     documentTitleHeight = () => (this.layoutDoc?.[HeightSym]() || 0) - NumCast(this.layoutDoc.autoHeightMargins);
     titleTransform = () => this.props.ScreenToLocalTransform().translate(-NumCast(this.doc._xPadding, 10), -NumCast(this.doc._yPadding, 20));
     truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
     onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
-    panelWidth = () => (this.props.PanelWidth() - 2 * this.paddingX()) * (this.props.scaling?.() || 1);
-    render() {
-        TraceMobx();
+    panelWidth = () => (this.props.PanelWidth() - 2 * this.marginX()) * (this.props.scaling?.() || 1);
+
+    addAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false;
+    remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false;
+    moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) =>
+        this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false
+
+    contentFunc = () => {
         const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
         const pointerEvents = () => !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined;
-        const buttonMenu = this.rootDoc.buttonMenu;
-        const noviceExplainer = this.rootDoc.explainer;
-
-        return !(this.doc instanceof Doc) || !this.treeChildren ? (null) :
-            <>
-                {this.titleBar}
+        const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? (null) : this.titleBar;
+        return [
+            <div className="collectionTreeView-contents" key="tree" style={{
+                ...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
+                overflow: "auto",
+                height: this.layoutDoc._autoHeight ? "max-content" : "100%"
+            }} >
+                {titleBar}
                 <div className="collectionTreeView-container"
-                    style={this.outlineMode ? { transform: `scale(${this.contentScaling})`, width: `calc(${100 / this.contentScaling}%)` } : {}}
+                    style={{
+                        transform: this.outlineMode ? `scale(${this.contentScaling})` : "",
+                        paddingLeft: `${this.marginX()}px`,
+                        height: "max-content",
+                        width: this.outlineMode ? `calc(${100 / this.contentScaling}%)` : ""
+                    }}
                     onContextMenu={this.onContextMenu}>
-                    {buttonMenu || noviceExplainer ? <div className="documentButtonMenu" ref={action((r: HTMLDivElement) => r && (this._explainerHeight = r.getBoundingClientRect().height))}>
-                        {buttonMenu ? this.buttonMenu : null}
-                        {Doc.UserDoc().noviceMode && noviceExplainer ?
-                            <div className="documentExplanation">
-                                {noviceExplainer}
-                            </div>
-                            : null
-                        }
-                    </div> : null}
+                    {!this.buttonMenu && !this.noviceExplainer ? (null) :
+                        <div className="documentButtonMenu" ref={action((r: HTMLDivElement) => r && (this._explainerHeight = r.getBoundingClientRect().height))}>
+                            {this.buttonMenu}
+                            {this.noviceExplainer}
+                        </div>
+                    }
                     <div className="collectionTreeView-dropTarget"
-                        style={{ background: background(), height: `calc(100% - ${this._explainerHeight}px)`, paddingLeft: `${this.paddingX()}px`, paddingRight: `${this.paddingX()}px`, paddingBottom: `${this.paddingBot()}px`, paddingTop: `${this.paddingTop()}px`, pointerEvents: pointerEvents() }}
+                        style={{
+                            background: background(),
+                            height: `calc(100% - ${this._explainerHeight}px)`,
+                            pointerEvents: pointerEvents()
+                        }}
                         onWheel={e => e.stopPropagation()}
                         onDrop={this.onTreeDrop}
-                        ref={this.createTreeDropTarget}>
+                        ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}>
                         <ul className={`no-indent${this.outlineMode ? "-outline" : ""}`} >
                             {this.treeViewElements}
                         </ul>
                     </div >
                 </div>
-            </>;
+            </div>
+        ];
+    }
+    render() {
+        TraceMobx();
+
+        return !(this.doc instanceof Doc) || !this.treeChildren ? (null) :
+            this.doc.treeViewHasOverlay ?
+                <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+                    isAnnotationOverlay={true}
+                    isAnnotationOverlayScrollable={true}
+                    childDocumentsActive={this.props.isDocumentActive}
+                    fieldKey={this.props.fieldKey + "-annotations"}
+                    dropAction={"move"}
+                    select={emptyFunction}
+                    addDocument={this.addAnnotationDocument}
+                    removeDocument={this.remAnnotationDocument}
+                    moveDocument={this.moveAnnotationDocument}
+                    bringToFront={emptyFunction}
+                    renderDepth={this.props.renderDepth + 1} >
+                    {this.contentFunc}
+                </CollectionFreeFormView> :
+                this.contentFunc();
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2

