From dda3d092ba9f071f567d279aed369efe185ffbde Mon Sep 17 00:00:00 2001
From: mehekj <mehek.jethani@gmail.com>
Date: Thu, 7 Oct 2021 16:08:30 -0400
Subject: fix presentation play bounds to fit trim bounds

---
 src/client/views/collections/TabDocView.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 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 eb95bb913..9f24d60d8 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -220,8 +220,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             if (!pinProps?.audioRange && duration !== undefined) {
                 pinDoc.mediaStart = "manual";
                 pinDoc.mediaStop = "manual";
-                pinDoc.presStartTime = 0;
-                pinDoc.presEndTime = duration;
+                pinDoc.presStartTime = doc.clipStart;
+                pinDoc.presEndTime = doc.clipEnd;
             }
             //save position
             if (pinProps?.setPosition || pinDoc.isInkMask) {
-- 
cgit v1.2.3-70-g09d2


From 1f94d6b33f81b1b2e6140f58f4de749eb4e74478 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 8 Nov 2021 11:07:05 -0500
Subject: fixed vertical height of timeline for video.  fixed initial display
 of timeline by not  assigning clipEnd in DidMount

---
 .../collections/CollectionStackedTimeline.scss     |  2 +-
 .../collections/CollectionStackedTimeline.tsx      | 33 ++++++++++++----------
 src/client/views/collections/TabDocView.tsx        |  4 +--
 src/client/views/nodes/AudioBox.scss               |  5 ++--
 src/client/views/nodes/AudioBox.tsx                |  7 +++--
 src/client/views/nodes/VideoBox.tsx                |  9 +++---
 6 files changed, 33 insertions(+), 27 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index fce105a44..36aa36978 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -1,7 +1,7 @@
 @import "../global/globalCssVariables.scss";
 
 .timeline-container {
-    height: calc(100% - 50px);
+    height: 100%;
     overflow-x: auto;
     overflow-y: hidden;
     border: none;
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index ced8a68e8..f5c3676e8 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -127,8 +127,11 @@ export class CollectionStackedTimeline extends CollectionSubView<
     }
 
     componentDidMount() {
-        this.layoutDoc.clipStart = 0;
-        this.layoutDoc.clipEnd = this.props.rawDuration;
+        // bcz: setting these shouldn't be necessary since they are the default values of this.clipStart and this.clipEnd.
+        //      also, setting anything on the Document in DidMount or the constructor is not good form since it means that
+        //      someone who has viewing but not edit permissions would not be able to do the assignment.
+        // this.layoutDoc.clipStart = 0;
+        // this.layoutDoc.clipEnd = this.props.rawDuration;
         document.addEventListener("keydown", this.keyEvents, true);
     }
 
@@ -915,19 +918,19 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                 {inner.view}
                 {!inner.anchor.view ||
                     !SelectionManager.IsSelected(inner.anchor.view) ? null : (
-                    <>
-                        <div
-                            key="left"
-                            className="collectionStackedTimeline-left-resizer"
-                            onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, true)}
-                        />
-                        <div
-                            key="right"
-                            className="collectionStackedTimeline-resizer"
-                            onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, false)}
-                        />
-                    </>
-                )}
+                        <>
+                            <div
+                                key="left"
+                                className="collectionStackedTimeline-left-resizer"
+                                onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, true)}
+                            />
+                            <div
+                                key="right"
+                                className="collectionStackedTimeline-resizer"
+                                onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, false)}
+                            />
+                        </>
+                    )}
             </>
         );
     }
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 9f24d60d8..6e1d9b067 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -220,8 +220,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             if (!pinProps?.audioRange && duration !== undefined) {
                 pinDoc.mediaStart = "manual";
                 pinDoc.mediaStop = "manual";
-                pinDoc.presStartTime = doc.clipStart;
-                pinDoc.presEndTime = doc.clipEnd;
+                pinDoc.presStartTime = NumCast(doc.clipStart);
+                pinDoc.presEndTime = NumCast(doc.clipEnd, duration);
             }
             //save position
             if (pinProps?.setPosition || pinDoc.isInkMask) {
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index 458d607a5..681a6b022 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -180,13 +180,14 @@
 
     .audiobox-playback {
         width: 100%;
-        height: calc(100% - 50px);
+        height: 100%;
         background: $white;
 
         .audiobox-timeline {
-            height: 100%;
+            height: calc(100% - 50px);
             width: 100%;
             background: $white;
+            position: absolute;
         }
 
         .audiobox-timeline > div {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index b51908e20..62958a80b 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -64,8 +64,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @observable _volume: number = 1;
     @observable _muted: boolean = false;
     @observable _paused: boolean = false;
+    @observable rawDuration: number = 0; // computed from the length of the audio element when loaded
     @computed get recordingStart() { return DateCast(this.dataDoc[this.fieldKey + "-recordingStart"])?.date.getTime(); }
-    @computed get rawDuration() { return NumCast(this.dataDoc[`${this.fieldKey}-duration`]); }
+    // @computed get rawDuration() { return NumCast(this.dataDoc[`${this.fieldKey}-duration`]); } // bcz: shouldn't be needed since it's computed from audio element
     @computed get links() { return DocListCast(this.dataDoc.links); }
     @computed get pauseTime() { return this._pauseEnd - this._pauseStart; } // total time paused to update the correct time
     @computed get mediaState() { return this.layoutDoc.mediaState as media_state; }
@@ -490,7 +491,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     />
                 </div>
                 <div className="timecode-duration">
-                    {this.timeline && formatTime(Math.round(NumCast(this.timeline?.clipDuration)))}
+                    {this.timeline && formatTime(Math.round(this.timeline.clipDuration))}
                 </div>
             </div>
 
@@ -537,7 +538,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             className={`audiobox-control${this.props.isContentActive() ? "-interactive" : ""}`}
             onLoadedData={action(e =>
                 (this._ele?.duration && this._ele?.duration !== Infinity) &&
-                (this.dataDoc[this.fieldKey + "-duration"] = this._ele?.duration)
+                (this.dataDoc[this.fieldKey + "-duration"] = this.rawDuration = this._ele.duration)
             )}
         >
             <source src={this.path} type="audio/mpeg" />
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index c22dbab5c..ec6519abd 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -79,7 +79,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     @computed get links() { return DocListCast(this.dataDoc.links); }
     @computed get heightPercent() { return NumCast(this.layoutDoc._timelineHeightPercent, 100); }
-    @computed get rawDuration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); }
+    // @computed get rawDuration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); }
+    @observable rawDuration: number = 0;
 
     @computed get youtubeVideoId() {
         const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
@@ -234,15 +235,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow"/* videoStart */, "_timecodeToHide" /* videoEnd */, timecode ? timecode : undefined, undefined, marquee) || this.rootDoc;
     }
 
-    videoLoad = () => {
+    videoLoad = action(() => {
         const aspect = this.player!.videoWidth / this.player!.videoHeight;
         Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth);
         Doc.SetNativeHeight(this.dataDoc, this.player!.videoHeight);
         this.layoutDoc._height = (this.layoutDoc._width || 0) / aspect;
         if (Number.isFinite(this.player!.duration)) {
-            this.dataDoc[this.fieldKey + "-duration"] = this.player!.duration;
+            this.rawDuration = this.player!.duration;
         }
-    }
+    })
 
     @action
     updateTimecode = () => {
-- 
cgit v1.2.3-70-g09d2


From d5d472521229f006f4a95d5c019276ec0281ac57 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 31 Mar 2022 14:56:03 -0400
Subject: cleaned up addDocTab for properties views to allow toggling of
 targets

---
 src/client/views/DocumentDecorations.tsx            | 16 +++++++++-------
 src/client/views/MainView.tsx                       | 20 ++++++++++++++++----
 src/client/views/PropertiesDocBacklinksSelector.tsx |  4 +---
 src/client/views/PropertiesDocContextSelector.tsx   | 11 +++++++----
 src/client/views/PropertiesView.tsx                 |  7 ++++---
 src/client/views/collections/TabDocView.tsx         |  6 ++----
 6 files changed, 39 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 328b8ab88..8cd646935 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -83,16 +83,18 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         } else if (this._titleControlString.startsWith("#")) {
             const titleFieldKey = this._titleControlString.substring(1);
             UndoManager.RunInBatch(() => titleFieldKey && SelectionManager.Views().forEach(d => {
-                titleFieldKey === "title" && (d.dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-"));
+                if (titleFieldKey === "title") {
+                    d.dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-");
+                    if (StrCast(d.rootDoc.title).startsWith("@") && !this._accumulatedTitle.startsWith("@")) {
+                        Doc.RemoveDocFromList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc);
+                    }
+                    if (!StrCast(d.rootDoc.title).startsWith("@") && this._accumulatedTitle.startsWith("@")) {
+                        Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc);
+                    }
+                }
                 //@ts-ignore
                 const titleField = (+this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle);
-                if (titleFieldKey === "title" && StrCast(d.rootDoc.title).startsWith("@") && !this._accumulatedTitle.startsWith("@")) {
-                    Doc.RemoveDocFromList(Doc.UserDoc(), "myPublishedDocs", SelectionManager.Docs().lastElement());
-                }
                 Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
-                if (titleFieldKey === "title" && this._accumulatedTitle.startsWith("@")) {
-                    Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", SelectionManager.Docs().lastElement());
-                }
 
                 if (d.rootDoc.syncLayoutFieldWithTitle) {
                     const title = titleField.toString();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7cb40f701..5e68832fc 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -329,9 +329,21 @@ export class MainView extends React.Component {
 
     sidebarScreenToLocal = () => new Transform(0, -this.topOfMainDoc, 1);
     mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
-    addDocTabFunc = (doc: Doc, where: string): boolean => {
-        return where === "close" ? CollectionDockingView.CloseSplit(doc) :
-            doc.dockingConfig ? CurrentUserUtils.openDashboard(Doc.UserDoc(), doc) : CollectionDockingView.AddSplit(doc, "right");
+    addDocTabFunc = (doc: Doc, location: string): boolean => {
+        const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":");
+        const locationParams = locationFields.length > 1 ? locationFields[1] : "";
+        if (doc.dockingConfig) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
+        switch (locationFields[0]) {
+            case "dashboard": return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
+            case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
+            case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
+            case "lightbox": return LightboxView.AddDocTab(doc, location);
+            case "toggle": return CollectionDockingView.ToggleSplit(doc, locationParams);
+            case "inPlace":
+            case "add":
+            default:
+                return CollectionDockingView.AddSplit(doc, locationParams);
+        }
     }
 
 
@@ -440,7 +452,7 @@ export class MainView extends React.Component {
                         <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
                     </div>
                     <div className="properties-container" style={{ width: this.propertiesWidth() }}>
-                        {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
+                        {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={this.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
                     </div>
                 </div>
             </div>
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index 5ffa93961..ea0d90e04 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -5,8 +5,6 @@ import { Doc, DocListCast } from "../../fields/Doc";
 import { Id } from "../../fields/FieldSymbols";
 import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { LinkManager } from "../util/LinkManager";
-import { CollectionDockingView } from "./collections/CollectionDockingView";
-import { CollectionViewType } from "./collections/CollectionView";
 import './PropertiesDocContextSelector.scss';
 
 type PropertiesDocBacklinksSelectorProps = {
@@ -34,7 +32,7 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
 
     getOnClick = (col: Doc) => {
         col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
-        this.props.addDocTab(col, "add:right");
+        this.props.addDocTab(col, "toggle:right");
     }
 
     render() {
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 45a8b6bf6..4d803f992 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -6,10 +6,11 @@ import { Id } from "../../fields/FieldSymbols";
 import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { CollectionDockingView } from "./collections/CollectionDockingView";
 import { CollectionViewType } from "./collections/CollectionView";
+import { DocumentView } from "./nodes/DocumentView";
 import './PropertiesDocContextSelector.scss';
 
 type PropertiesDocContextSelectorProps = {
-    Document: Doc,
+    DocView?: DocumentView,
     Stack?: any,
     hideTitle?: boolean,
     addDocTab(doc: Doc, location: string): void
@@ -18,9 +19,11 @@ type PropertiesDocContextSelectorProps = {
 @observer
 export class PropertiesDocContextSelector extends React.Component<PropertiesDocContextSelectorProps> {
     @computed get _docs() {
-        const target = this.props.Document;
+        if (!this.props.DocView) return [];
+        const target = this.props.DocView.props.Document;
+        const targetContext = this.props.DocView.props.ContainingCollectionDoc;
         const aliases = DocListCast(target.aliases);
-        const containerProtos = aliases.filter(alias => alias.context).reduce((set, alias) => set.add(Cast(alias.context, Doc, null)), new Set<Doc>());
+        const containerProtos = aliases.filter(alias => alias.context && alias.context instanceof Doc && Cast(alias.context, Doc, null) !== targetContext).reduce((set, alias) => set.add(Cast(alias.context, Doc, null)), new Set<Doc>());
         const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.aliases));
         const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
         const doclayoutSets = Array.from(containers.keys()).map(dp => DocListCast(dp.aliases));
@@ -34,7 +37,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
             col._panX = NumCast(target.x) + NumCast(target._width) / 2;
             col._panY = NumCast(target.y) + NumCast(target._height) / 2;
         }
-        this.props.addDocTab(col, "add:right");
+        this.props.addDocTab(col, "toggle:right");
     }
 
     render() {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 8e1e9ea15..91cf83619 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -44,6 +44,7 @@ interface PropertiesViewProps {
     width: number;
     height: number;
     styleProvider?: StyleProviderFunc;
+    addDocTab: (doc: Doc, where: string) => boolean;
 }
 
 @observer
@@ -279,11 +280,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     @computed get contexts() {
-        return !this.selectedDoc ? (null) : <PropertiesDocContextSelector Document={this.selectedDoc} hideTitle={true} addDocTab={(doc, where) => CollectionDockingView.AddSplit(doc, "right")} />;
+        return !this.selectedDoc ? (null) : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle={true} addDocTab={this.props.addDocTab} />;
     }
 
     @computed get links() {
-        return !this.selectedDoc ? (null) : <PropertiesDocBacklinksSelector Document={this.selectedDoc} hideTitle={true} addDocTab={(doc, where) => CollectionDockingView.AddSplit(doc, "right")} />;
+        return !this.selectedDoc ? (null) : <PropertiesDocBacklinksSelector Document={this.selectedDoc} hideTitle={true} addDocTab={this.props.addDocTab} />;
     }
 
     @computed get layoutPreview() {
@@ -1077,7 +1078,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             <div className="propertiesView-contexts-title"
                 onPointerDown={action(() => this.openContexts = !this.openContexts)}
                 style={{ backgroundColor: this.openContexts ? "black" : "" }}>
-                Contexts
+                Other Contexts
                 <div className="propertiesView-contexts-title-icon">
                     <FontAwesomeIcon icon={this.openContexts ? "caret-down" : "caret-right"} size="lg" color="white" />
                 </div>
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index d52746d11..8b5022593 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -298,10 +298,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
             case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
             case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
-            case "lightbox": {
-                // TabDocView.PinDoc(doc, { hidePresBox: true });
-                return LightboxView.AddDocTab(doc, location);
-            }
+            case "lightbox": return LightboxView.AddDocTab(doc, location);
+            case "toggle": return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack);
             case "inPlace":
             case "add":
             default:
-- 
cgit v1.2.3-70-g09d2


From 51d7ce5f71465f2f578a08a998b2df353242ff4d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 5 Apr 2022 13:54:26 -0400
Subject: fixes to allow dragging golden layout windows to be aborted with
 'esc'.  some code cleanup as well to avoid rebuilding golden layout when tabs
 are closed.  Fixes for undo and goldenlayout

---
 src/client/goldenLayout.js                         |  32 ++++--
 src/client/util/DragManager.ts                     |  25 ++---
 src/client/views/DocumentDecorations.tsx           |   7 +-
 src/client/views/GlobalKeyHandler.ts               |   1 +
 .../views/collections/CollectionDockingView.tsx    | 109 +++++++++++++--------
 src/client/views/collections/TabDocView.tsx        |   3 +-
 6 files changed, 108 insertions(+), 69 deletions(-)

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

diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index 896237e1d..295875ef6 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -366,6 +366,7 @@
         this._nOriginalY = 0;
 
         this._bDragging = false;
+        this._bAborting = false;
 
         this._fMove = lm.utils.fnBind(this.onMouseMove, this);
         this._fUp = lm.utils.fnBind(this.onMouseUp, this);
@@ -427,6 +428,24 @@
             }
         },
 
+        AbortDrag: function () {
+            if (this._timeout != null) {
+                clearTimeout(this._timeout);
+                this._eBody.removeClass('lm_dragging');
+                this._eElement.removeClass('lm_dragging');
+                this._oDocument.find('iframe').css('pointer-events', '');
+                this._oDocument.unbind('mousemove touchmove', this._fMove);
+                this._oDocument.unbind('mouseup touchend', this._fUp);
+
+                if (this._bDragging === true) {
+                    this._bDragging = false;
+                    this._bAborting = true;
+                    this.emit('dragStop', { pageX: 0, pageY: 0 }, this._nOriginalX + this._nX);
+                    this._bAborting = false;
+                }
+            }
+        },
+
         onMouseUp: function (oEvent) {
             if (this._timeout != null) {
                 clearTimeout(this._timeout);
@@ -2178,18 +2197,18 @@
          */
         _onDrop: function () {
             this._layoutManager.dropTargetIndicator.hide();
-
+            let abortedDrop = this._dragListener._bAborting;
             /*
              * Valid drop area found
              */
-            if (this._area !== null) {
+            if (!abortedDrop && this._area !== null) {
                 this._area.contentItem._$onDrop(this._contentItem, this._area);
 
                 /**
                  * No valid drop area available at present, but one has been found before.
                  * Use it
                  */
-            } else if (this._lastValidArea !== null) {
+            } else if (!abortedDrop && this._lastValidArea !== null) {
                 this._lastValidArea.contentItem._$onDrop(this._contentItem, this._lastValidArea);
 
                 /**
@@ -2197,7 +2216,7 @@
                  * content item to its original position if a original parent is provided.
                  * (Which is not the case if the drag had been initiated by createDragSource)
                  */
-            } else if (this._originalParent) {
+            } else if (!abortedDrop && this._originalParent) {
                 this._originalParent.addChild(this._contentItem);
 
                 /**
@@ -2212,7 +2231,7 @@
             restoreScrollTops(this._contentItem.element)
             this.element.remove();
 
-            this._layoutManager.emit('itemDropped', this._contentItem);
+            !abortedDrop && this._layoutManager.emit('itemDropped', this._contentItem);
         },
 
         /**
@@ -2963,7 +2982,7 @@
             if (this.contentItem.parent.isMaximised === true) {
                 this.contentItem.parent.toggleMaximise();
             }
-            new lm.controls.DragProxy(
+            let proxy = new lm.controls.DragProxy(
                 x,
                 y,
                 this._dragListener,
@@ -2971,6 +2990,7 @@
                 this.contentItem,
                 this.header.parent
             );
+            this._layoutManager.emit('dragStart', proxy);
         },
 
         /**
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0d6a77f71..411fc6d11 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,3 +1,4 @@
+import { extend } from "lodash";
 import { action } from "mobx";
 import { DateField } from "../../fields/DateField";
 import { Doc, Field, Opt } from "../../fields/Doc";
@@ -56,12 +57,7 @@ export function SetupDrag(
             if (e.shiftKey) {
                 e.persist();
                 const dragDoc = await docFunc();
-                dragDoc && DragManager.StartWindowDrag?.({
-                    pageX: e.pageX,
-                    pageY: e.pageY,
-                    preventDefault: emptyFunction,
-                    button: 0
-                }, [dragDoc]);
+                dragDoc && DragManager.StartWindowDrag?.(e, [dragDoc]);
             } else {
                 document.addEventListener("pointermove", onRowMove);
                 document.addEventListener("pointerup", onRowUp);
@@ -74,7 +70,8 @@ export function SetupDrag(
 export namespace DragManager {
     let dragDiv: HTMLDivElement;
     let dragLabel: HTMLDivElement;
-    export let StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined;
+    export let StartWindowDrag: Opt<((e: { pageX: number, pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => void)>;
+    export let CompleteWindowDrag: Opt<(aborted: boolean) => void>;
 
     export function Root() {
         const root = document.getElementById("root");
@@ -460,17 +457,13 @@ export namespace DragManager {
             }
             if (((e.target as any)?.className === "lm_tabs" || e?.shiftKey) && dragData.draggedDocuments.length === 1) {
                 dragData.dropAction = dragData.userDropAction || "same";
-                if (dragData.dropAction === "move" || dragData.dropAction === "same") {
-                    dragData.removeDocument?.(dragData.draggedDocuments[0]);
-                }
                 AbortDrag();
                 await finishDrag?.(new DragCompleteEvent(true, dragData));
-                DragManager.StartWindowDrag?.({
-                    pageX: e.pageX,
-                    pageY: e.pageY,
-                    preventDefault: emptyFunction,
-                    button: 0
-                }, dragData.droppedDocuments);
+                DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, (aborted) => {
+                    if (!aborted && (dragData.dropAction === "move" || dragData.dropAction === "same")) {
+                        dragData.removeDocument?.(dragData.draggedDocuments[0]);
+                    }
+                });
             }
 
             const target = document.elementFromPoint(e.x, e.y);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8cd646935..d7ead713a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -151,12 +151,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
     }
     onMaximizeDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, () => {
-            DragManager.StartWindowDrag?.({
-                pageX: e.pageX,
-                pageY: e.pageY,
-                preventDefault: emptyFunction,
-                button: 0
-            }, [SelectionManager.Views().slice(-1)[0].rootDoc]);
+            DragManager.StartWindowDrag?.(e, [SelectionManager.Views().slice(-1)[0].rootDoc]);
             return true;
         }, emptyFunction, this.onMaximizeClick, false, false);
     }
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 1a4080d81..4a327a842 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -114,6 +114,7 @@ export class KeyManager {
                 DocumentLinksButton.StartLinkView = undefined;
                 InkStrokeProperties.Instance._controlButton = false;
                 CurrentUserUtils.SelectedTool = InkTool.None;
+                DragManager.CompleteWindowDrag?.(true);
                 var doDeselect = true;
                 if (SnappingManager.GetIsDragging()) {
                     DragManager.AbortDrag();
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 9e8374605..6931d9896 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -11,6 +11,7 @@ import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { inheritParentAcls } from '../../../fields/util';
+import { emptyFunction } from '../../../Utils';
 import { DocServer } from "../../DocServer";
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -46,7 +47,7 @@ export class CollectionDockingView extends CollectionSubView() {
     private _reactionDisposer?: IReactionDisposer;
     private _lightboxReactionDisposer?: IReactionDisposer;
     private _containerRef = React.createRef<HTMLDivElement>();
-    private _flush: UndoManager.Batch | undefined;
+    public _flush: UndoManager.Batch | undefined;
     private _ignoreStateChange = "";
     public tabMap: Set<any> = new Set();
     public get initialized() { return this._goldenLayout !== null; }
@@ -62,15 +63,37 @@ export class CollectionDockingView extends CollectionSubView() {
         DragManager.StartWindowDrag = this.StartOtherDrag;
     }
 
-    public StartOtherDrag = (e: any, dragDocs: Doc[]) => {
-        !this._flush && (this._flush = UndoManager.StartBatch("golden layout drag"));
+    /**
+     * Switches from dragging a document around a freeform canvas to dragging it as a tab to be docked.
+     * 
+     * @param e fake mouse down event position data containing pageX and pageY coordinates
+     * @param dragDocs the documents to be dragged
+     * @param batch optionally an undo batch that has been started to use instead of starting a new batch
+    */
+    public StartOtherDrag = (e: { pageX: number, pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => {
+        this._flush = this._flush ?? UndoManager.StartBatch("golden layout drag");
         const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) :
-            { type: 'row', content: dragDocs.map((doc, i) => CollectionDockingView.makeDocumentConfig(doc)) };
+            { type: 'row', content: dragDocs.map(doc => CollectionDockingView.makeDocumentConfig(doc)) };
         const dragSource = this._goldenLayout.createDragSource(document.createElement("div"), config);
-        //dragSource._dragListener.on("dragStop", dragSource.destroy);
-        dragSource._dragListener.onMouseDown(e);
+        this.tabDragStart(dragSource, finishDrag);
+        dragSource._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 });
     }
 
+    tabItemDropped = () => DragManager.CompleteWindowDrag?.(false);
+    tabDragStart = (proxy: any, finishDrag?: (aborted: boolean) => void) => {
+        DragManager.CompleteWindowDrag = (aborted: boolean) => {
+            if (aborted) {
+                proxy._dragListener.AbortDrag();
+                if (this._flush) {
+                    this._flush.cancel();  // cancel the undo change being logged
+                    this._flush = undefined;
+                    this.setupGoldenLayout(); // restore golden layout to where it was before the drag (this is a no-op when using StartOtherDrag because the proxy dragged item was never in the golden layout)
+                }
+                DragManager.CompleteWindowDrag = undefined;
+            }
+            finishDrag?.(aborted);
+        }
+    }
     @undoBatch
     public CloseFullScreen = () => {
         this._goldenLayout._maximisedItem?.toggleMaximise();
@@ -106,7 +129,6 @@ export class CollectionDockingView extends CollectionSubView() {
         docconfig.callDownwards('_$init');
         instance._goldenLayout._$maximiseItem(docconfig);
         instance._goldenLayout.emit('stateChanged');
-        instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig());
         instance.stateChanged();
         return true;
     }
@@ -255,7 +277,6 @@ export class CollectionDockingView extends CollectionSubView() {
     layoutChanged() {
         this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
         this._goldenLayout.emit('stateChanged');
-        this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         this.stateChanged();
         return true;
     }
@@ -294,6 +315,9 @@ export class CollectionDockingView extends CollectionSubView() {
                 }
             }
             this._goldenLayout.init();
+            this._goldenLayout.root.layoutManager.on('itemDropped', this.tabItemDropped);
+            this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart)
+            this._goldenLayout.root.layoutManager.on('activeContentItemChanged', this.stateChanged);
         }
     }
 
@@ -335,13 +359,12 @@ export class CollectionDockingView extends CollectionSubView() {
     @action
     onPointerUp = (e: MouseEvent): void => {
         window.removeEventListener("pointerup", this.onPointerUp);
-        if (this._flush) {
-            setTimeout(() => {
-                CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
-                this.stateChanged();
-                this._flush?.end();
-                this._flush = undefined;
-            }, 10);
+        const flush = this._flush;
+        this._flush = undefined;
+        if (flush) {
+            DragManager.CompleteWindowDrag = undefined;
+            if (!this.stateChanged()) flush.cancel();
+            else flush.end();
         }
     }
 
@@ -352,9 +375,12 @@ export class CollectionDockingView extends CollectionSubView() {
             hitFlyout = (par.className === "dockingViewButtonSelector");
         }
         if (!hitFlyout) {
+            const htmlTarget = e.target as HTMLElement;
             window.addEventListener("mouseup", this.onPointerUp);
-            if (!(e.target as HTMLElement).closest("*.lm_content") && ((e.target as HTMLElement).closest("*.lm_tab") || (e.target as HTMLElement).closest("*.lm_stack"))) {
-                this._flush = UndoManager.StartBatch("golden layout edit");
+            if (!htmlTarget.closest("*.lm_content") && (htmlTarget.closest("*.lm_tab") || htmlTarget.closest("*.lm_stack"))) {
+                if (htmlTarget.className !== "lm_close_tab") {
+                    this._flush = UndoManager.StartBatch("golden layout edit");
+                }
             }
         }
         if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) &&
@@ -388,38 +414,43 @@ export class CollectionDockingView extends CollectionSubView() {
 
     @action
     stateChanged = () => {
+        this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         const json = JSON.stringify(this._goldenLayout.toConfig());
         const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
         const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", ""));
         const docs = !docids ? [] : docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc);
-
-        this.props.Document.dockingConfig = json;
-        setTimeout(async () => {
-            const sublists = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
-            const tabs = sublists && Cast(sublists[0], Doc, null);
-            // const other = sublists && Cast(sublists[1], Doc, null);
-            const tabdocs = await DocListCastAsync(tabs?.data);
-            // const otherdocs = await DocListCastAsync(other?.data);
-            if (tabs) {
-                tabs.data = new List<Doc>(docs);
-                // DocListCast(tabs.aliases).forEach(tab => tab !== tabs && (tab.data = new List<Doc>(docs)));
-            }
-            // const otherSet = new Set<Doc>();
-            // otherdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
-            // tabdocs?.filter(doc => !docs.includes(doc) && doc.type !== DocumentType.KVP).forEach(doc => otherSet.add(doc));
-            // const vals = Array.from(otherSet.values()).filter(val => val instanceof Doc).map(d => d).filter(d => d.type !== DocumentType.KVP);
-            // this.props.Document[DataSym][this.props.fieldKey + "-all"] = new List<Doc>([...docs, ...vals]);
-            // if (other) {
-            //     other.data = new List<Doc>(vals);
-            //     // DocListCast(other.aliases).forEach(tab => tab !== other && (tab.data = new List<Doc>(vals)));
-            // }
-        }, 0);
+        const changesMade = this.props.Document.dockingConfig !== json;
+        if (changesMade && !this._flush) {
+            this.props.Document.dockingConfig = json;
+            setTimeout(async () => {
+                const sublists = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
+                const tabs = sublists && Cast(sublists[0], Doc, null);
+                // const other = sublists && Cast(sublists[1], Doc, null);
+                const tabdocs = await DocListCastAsync(tabs?.data);
+                // const otherdocs = await DocListCastAsync(other?.data);
+                if (tabs) {
+                    tabs.data = new List<Doc>(docs);
+                    // DocListCast(tabs.aliases).forEach(tab => tab !== tabs && (tab.data = new List<Doc>(docs)));
+                }
+                // const otherSet = new Set<Doc>();
+                // otherdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
+                // tabdocs?.filter(doc => !docs.includes(doc) && doc.type !== DocumentType.KVP).forEach(doc => otherSet.add(doc));
+                // const vals = Array.from(otherSet.values()).filter(val => val instanceof Doc).map(d => d).filter(d => d.type !== DocumentType.KVP);
+                // this.props.Document[DataSym][this.props.fieldKey + "-all"] = new List<Doc>([...docs, ...vals]);
+                // if (other) {
+                //     other.data = new List<Doc>(vals);
+                //     // DocListCast(other.aliases).forEach(tab => tab !== other && (tab.data = new List<Doc>(vals)));
+                // }
+            }, 0);
+        }
+        return changesMade;
     }
 
     tabDestroyed = (tab: any) => {
         this.tabMap.delete(tab);
         tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
         tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
+        this.stateChanged();
     }
     tabCreated = (tab: any) => {
         tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab);  // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 8b5022593..ad5c0efb3 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -190,7 +190,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
                     Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true);
                     SelectionManager.DeselectAll();
-                    tab.contentItem.remove();
+                    UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab");
                 });
         }
     }
@@ -277,7 +277,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     private 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;
-            (CollectionDockingView.Instance as any)._goldenLayout?.isInitialised && CollectionDockingView.Instance.stateChanged();
             !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.
         }
     }
-- 
cgit v1.2.3-70-g09d2


From 8f6a065c192c091393e654bdac682c285a63ad8f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 5 Apr 2022 14:45:06 -0400
Subject: from last

---
 src/client/views/collections/TabDocView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(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 ad5c0efb3..bd3e810c9 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -179,7 +179,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
 
             // highlight the tab when the tab document is brushed in any part of the UI
             tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
-                titleEle.value = title;
+                //titleEle.value = title;
                 // titleEle.style.padding = degree ? 0 : 2;
                 // titleEle.style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
             }, { fireImmediately: true });
-- 
cgit v1.2.3-70-g09d2


From 18d21c0616adbdc7e0d0619c3c2d8bdf9b34cc01 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 22 Apr 2022 21:39:08 -0400
Subject: changes to allow PresBox to render using a Tree view

---
 src/client/documents/Documents.ts                 |  7 +--
 src/client/views/collections/TabDocView.tsx       |  9 ++++
 src/client/views/collections/TreeView.tsx         | 54 ++++++++++++-----------
 src/client/views/nodes/DocumentView.tsx           |  3 +-
 src/client/views/nodes/trails/PresBox.tsx         | 13 +++---
 src/client/views/nodes/trails/PresElementBox.scss |  2 +-
 src/client/views/nodes/trails/PresElementBox.tsx  |  4 +-
 7 files changed, 54 insertions(+), 38 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 69ea21541..49e53a214 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -284,6 +284,7 @@ export class DocumentOptions {
     treeViewHideTitle?: boolean; // whether to hide the top document title of a tree view
     treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view
     treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
+    treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically
 
     // Action Button
     buttonMenu?: boolean; // whether a action button should be displayed 
@@ -365,7 +366,7 @@ export namespace Docs {
             [DocumentType.RTF, {
                 layout: { view: FormattedTextBox, dataField: "text" },
                 options: {
-                    _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true,
+                    _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, treeViewGrowsHorizontally: true,
                     links: "@links(self)"
                 }
             }],
@@ -460,8 +461,8 @@ export namespace Docs {
                 options: { links: "@links(self)" }
             }],
             [DocumentType.SLIDER, {
-                layout: { view: SliderBox, dataField: defaultDataKey },
-                options: { links: "@links(self)" }
+                layout: { view: SliderBox, dataField: defaultDataKey, },
+                options: { links: "@links(self)", treeViewGrowsHorizontally: true }
             }],
             [DocumentType.PRES, {
                 layout: { view: PresBox, dataField: defaultDataKey },
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index bd3e810c9..136486f47 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -33,6 +33,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionView, CollectionViewType } from './CollectionView';
 import "./TabDocView.scss";
 import React = require("react");
+import { List } from '../../../fields/List';
 const _global = (window /* browser */ || global /* node */) as any;
 
 interface TabDocViewProps {
@@ -209,9 +210,17 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             const pinDoc = Doc.MakeAlias(doc);
             pinDoc.presentationTargetDoc = doc;
             pinDoc.title = doc.title + " - Slide";
+            pinDoc.data = new List<Doc>();  // the children of the alias' layout are the presentation slide children.  the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
             pinDoc.presMovement = PresMovement.Zoom;
             pinDoc.groupWithUp = false;
             pinDoc.context = curPres;
+            // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements.  That way the PresBox could configure all of its children at render time
+            pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
+            pinDoc.treeViewHeaderWidth = "100%"; // forces the header to grow to be the same size as its largest sibling.
+            pinDoc.treeViewFieldKey = "data"; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
+            pinDoc.treeViewExpandedView = "data";// in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
+            pinDoc.treeViewGrowsHorizontally = true;// the document can expand horizontally when displayed as a tree view header  
+            pinDoc.treeViewHideHeader = true;  // this will force the document to render itself as the tree view header
             const presArray: Doc[] = PresBox.Instance?.sortArray();
             const size: number = PresBox.Instance?._selectedArray.size;
             const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index c2d0983da..a35304787 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -112,7 +112,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containerCollection.maxEmbedHeight, 200); }
     @computed get dataDoc() { return this.doc[DataSym]; }
     @computed get layoutDoc() { return Doc.Layout(this.doc); }
-    @computed get fieldKey() { return Doc.LayoutFieldKey(this.doc); }
+    @computed get fieldKey() { return StrCast(this.doc._treeViewFieldKey, Doc.LayoutFieldKey(this.doc)); }
     @computed get childDocs() { return this.childDocList(this.fieldKey); }
     @computed get childLinks() { return this.childDocList("links"); }
     @computed get childAliases() { return this.childDocList("aliases"); }
@@ -156,18 +156,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             docView.select(false);
         }
     }
-    @action
-    openLevel = (docView: DocumentView) => {
-        if (this.props.document.isFolder || Doc.IsSystem(this.props.document)) {
-            this.treeViewOpen = !this.treeViewOpen;
-        } else {
-            // choose an appropriate alias or make one. --- choose the first alias that (1) user owns,  (2) has no context field ... otherwise make a new alias
-            // this.props.addDocTab(CurrentUserUtils.ActiveDashboard.isShared ? Doc.MakeAlias(this.props.document) : this.props.document, "add:right");
-            const bestAlias = DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
-            this.props.addDocTab(bestAlias ?? Doc.MakeAlias(this.props.document), "add:right");
 
-        }
-    }
     constructor(props: any) {
         super(props);
         if (!TreeView._openLevelScript) {
@@ -379,8 +368,14 @@ export class TreeView extends React.Component<TreeViewProps> {
         return rows;
     }
 
-    rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), (this.props.panelWidth() - treeBulletWidth())) / (this.props.treeView.props.scaling?.() || 1);
-    rtfHeight = () => this.rtfWidth() <= this.layoutDoc?.[WidthSym]() ? Math.min(this.layoutDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+    rtfWidth = () => {
+        const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ""))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+        return Math.min(layout[WidthSym](), (this.props.panelWidth() - treeBulletWidth())) / (this.props.treeView.props.scaling?.() || 1);
+    }
+    rtfHeight = () => {
+        const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ""))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+        return this.rtfWidth() <= layout[WidthSym]() ? Math.min(layout[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+    }
     rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), treeBulletWidth());
     expandPanelHeight = () => {
         if (this.layoutDoc._fitWidth) return this.docHeight();
@@ -580,13 +575,20 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
     onKeyDown = (e: React.KeyboardEvent) => {
         if (this.doc.treeViewHideHeader || this.props.treeView.outlineMode) {
-            e.stopPropagation();
-            e.preventDefault();
             switch (e.key) {
-                case "Tab": setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
+                case "Tab":
+                    e.stopPropagation();
+                    e.preventDefault();
+                    setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
                     return UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true), "tab");
-                case "Backspace": return !(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc);
-                case "Enter": return UndoManager.RunInBatch(this.makeTextCollection, "bullet");
+                case "Backspace":
+                    e.stopPropagation();
+                    e.preventDefault();
+                    return !(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc);
+                case "Enter":
+                    e.stopPropagation();
+                    e.preventDefault();
+                    return UndoManager.RunInBatch(this.makeTextCollection, "bullet");
             }
         }
     }
@@ -691,7 +693,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     renderBulletHeader = (contents: JSX.Element, editing: boolean) => {
         return <>
             <div className={`treeView-header` + (editing ? "-editing" : "")} key="titleheader"
-                style={{ width: "max-content" }}
+                style={{ width: StrCast(this.doc.treeViewHeaderWidth, "max-content") }}
                 ref={this._header}
                 onClick={this.ignoreEvent}
                 onPointerDown={this.ignoreEvent}
@@ -705,8 +707,7 @@ export class TreeView extends React.Component<TreeViewProps> {
 
 
     renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => {
-        const layout = StrCast(Doc.LayoutField(this.layoutDoc));
-        const isExpandable = layout.includes(FormattedTextBox.name) || layout.includes(SliderBox.name);
+        const isExpandable = this.doc._treeViewGrowsHorizontally;
         const panelWidth = asText || isExpandable ? this.rtfWidth : this.expandPanelWidth;
         const panelHeight = asText ? this.rtfOutlineHeight : isExpandable ? this.rtfHeight : this.expandPanelHeight;
         return <DocumentView key={this.doc[Id]} ref={action((r: DocumentView | null) => this._dref = r)}
@@ -717,6 +718,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined}
             NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined}
             LayoutTemplateString={asText ? FormattedTextBox.LayoutString("text") : undefined}
+            LayoutTemplate={this.props.treeView.props.childLayoutTemplate}
             isContentActive={isActive}
             isDocumentActive={isActive}
             styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
@@ -759,10 +761,10 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     // renders the document in the header field instead of a text proxy.
-    @computed get renderDocumentAsHeader() {
+    renderDocumentAsHeader = (asText: boolean) => {
         return <>
             {this.renderBullet}
-            {this.renderEmbeddedDocument(true, this.props.isContentActive)}
+            {this.renderEmbeddedDocument(asText, this.props.isContentActive)}
         </>;
     }
 
@@ -794,9 +796,9 @@ export class TreeView extends React.Component<TreeViewProps> {
                 //onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
                 onKeyDown={this.onKeyDown}>
                 <li className="collection-child">
-                    {hideTitle && this.doc.type !== DocumentType.RTF ?
+                    {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader ? // should test for prop 'treeViewRenderDocWithBulletAsHeader"
                         this.renderEmbeddedDocument(false, returnFalse) :
-                        this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader : this.renderTitleAsHeader, this._editTitle)}
+                        this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeViewRenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
                 </li>
             </div>;
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1eaff3c1c..bcf00e88d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1308,6 +1308,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         TraceMobx();
         const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
         const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+        const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
         const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
         return (<div className="contentFittingDocumentView">
             {!this.props.Document || !this.props.PanelWidth() ? (null) : (
@@ -1316,7 +1317,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         transition: this.props.dataTransition,
                         position: this.props.Document.isInkMask ? "absolute" : undefined,
                         transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
-                        width: isButton ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
+                        width: isButton || isPresTreeElement ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
                         height: (!this.props.ignoreAutoHeight && this.layoutDoc.autoHeight && this.layoutDoc.type === DocumentType.RTF) || isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
                             `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
                     }}>
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 2e312ee51..c9c74eca4 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -122,7 +122,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (Doc.UserDoc().activePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this);
         if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations.
             Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
-                title: "pres element template", type: DocumentType.PRESELEMENT, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"
+                title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"
             }));
             // this script will be called by each presElement to get rendering-specific info that the PresBox knows about but which isn't written to the PresElement
             // this is a design choice -- we could write this data to the presElements which would require a reaction to keep it up to date, and it would prevent
@@ -308,7 +308,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             }
             if (!group) this._selectedArray.clear();
             this.childDocs[index] && this._selectedArray.set(this.childDocs[index], undefined); //Update selected array
-            if (this.layoutDoc._viewType === "stacking" && !group) this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list
+            if ([CollectionViewType.Stacking, CollectionViewType.Tree].includes(this.layoutDoc._viewType as any) && !group) this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list
             this.onHideDocument(); //Handles hide after/before
         }
     });
@@ -620,9 +620,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         //@ts-ignore
         const viewType = e.target.selectedOptions[0].value as CollectionViewType;
         // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
-        viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined);
+        [CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType) && (this.rootDoc._pivotField = undefined);
         this.rootDoc._viewType = viewType;
-        if (viewType === CollectionViewType.Stacking) this.layoutDoc._gridGap = 0;
+        if ([CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType)) this.layoutDoc._gridGap = 0;
     });
 
     /**
@@ -696,7 +696,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         });
         return true;
     }
-    childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement;
+    childLayoutTemplate = () => ![CollectionViewType.Stacking, CollectionViewType.Tree].includes(this.rootDoc._viewType as any) ? undefined : this.presElement;
     removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
     getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
     panelHeight = () => this.props.PanelHeight() - 40;
@@ -2262,6 +2262,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     onChange={this.viewChanged}
                     value={mode}>
                     <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option>
+                    <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Tree}>Tree</option>
                     <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}>3D Carousel</option>
                 </select>}
                 <div className="presBox-presentPanel" style={{ opacity: this.childDocs.length ? 1 : 0.3 }}>
@@ -2459,7 +2460,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 }
 ScriptingGlobals.add(function lookupPresBoxField(container: Doc, field: string, data: Doc) {
     if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data);
-    if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 35 : 31;
+    if (field === 'presCollapsedHeight') return [CollectionViewType.Tree || CollectionViewType.Stacking].includes(container._viewType as any) ? 35 : 31;
     if (field === 'presStatus') return container.presStatus;
     if (field === '_itemIndex') return container._itemIndex;
     if (field === 'presBox') return container;
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
index 1ad4b820e..a178be910 100644
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresElementBox.scss
@@ -42,7 +42,7 @@ $slide-active: #5B9FDD;
     background-color: #d5dce2;
     border-radius: 5px;
     height: calc(100% - 7px);
-    width: calc(100% - 15px);
+    width: 100%;
     display: grid;
     grid-template-rows: 16px 10px auto;
     grid-template-columns: max-content max-content max-content max-content auto;
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index a4ec559f5..5c16d743a 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -23,6 +23,7 @@ import { PresBox } from "./PresBox";
 import "./PresElementBox.scss";
 import { PresMovement } from "./PresEnums";
 import React = require("react");
+import { CollectionViewType } from "../../collections/CollectionView";
 /**
  * This class models the view a document added to presentation will have in the presentation.
  * It involves some functionality for its buttons and options.
@@ -87,6 +88,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     isContentActive={this.props.isContentActive}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
+                    fitContentsToDoc={returnTrue}
                     PanelWidth={this.embedWidth}
                     PanelHeight={this.embedHeight}
                     ScreenToLocalTransform={Transform.Identity}
@@ -321,7 +323,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
                         boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined
                     }}>
-                    <div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}>
+                    <div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - (this.presBox._viewType === CollectionViewType.Stacking ? 195 : 220)) : toolbarWidth - (this.presBox._viewType === CollectionViewType.Stacking ? 105 : 145), cursor: isSelected ? 'text' : 'grab' }}>
                         <EditableView
                             ref={this._titleRef}
                             editing={!isSelected ? false : undefined}
-- 
cgit v1.2.3-70-g09d2


From afeb2ac0d4aa914f94596e08fe4030abba050496 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 23 Apr 2022 16:11:14 -0400
Subject: more fixes for treeView to support  hierarchical presentations.

---
 src/client/views/DocComponent.tsx                |  4 ++-
 src/client/views/DocumentButtonBar.tsx           | 46 ++++++++++++------------
 src/client/views/collections/TabDocView.tsx      |  3 +-
 src/client/views/collections/TreeView.tsx        |  9 +++--
 src/client/views/nodes/trails/PresBox.tsx        |  8 ++---
 src/client/views/nodes/trails/PresElementBox.tsx |  4 +++
 6 files changed, 44 insertions(+), 30 deletions(-)

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

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 73a261c40..79aaf2158 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -11,6 +11,7 @@ import { DocUtils } from '../documents/Documents';
 import { CurrentUserUtils } from '../util/CurrentUserUtils';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { UndoManager } from '../util/UndoManager';
+import { DocumentView } from './nodes/DocumentView';
 import { Touchable } from './Touchable';
 
 
@@ -41,6 +42,7 @@ interface ViewBoxBaseProps {
     Document: Doc;
     DataDoc?: Doc;
     ContainingCollectionDoc: Opt<Doc>;
+    DocumentView?: () => DocumentView;
     fieldKey: string;
     layerProvider?: (doc: Doc, assign?: boolean) => boolean;
     isSelected: (outsideReaction?: boolean) => boolean;
@@ -63,7 +65,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
         // key where data is stored
         @computed get fieldKey() { return this.props.fieldKey; }
 
-        lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.ContainingCollectionDoc }).result;
+        lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc }).result;
 
         isContentActive = (outsideReaction?: boolean) => (
             this.props.isContentActive?.() === false ? false :
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index aa9318310..9b95f1525 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -215,28 +215,30 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     pinWithView = (targetDoc: Doc) => {
         if (targetDoc) {
             TabDocView.PinDoc(targetDoc);
-            const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1];
-            const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking;
-            const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG);
-            if (scrollable) {
-                const scroll = targetDoc._scrollTop;
-                activeDoc.presPinView = true;
-                activeDoc.presPinViewScroll = scroll;
-            } else if (targetDoc.type === DocumentType.VID) {
-                activeDoc.presPinTimecode = targetDoc._currentTimecode;
-            } else if (pannable) {
-                const x = targetDoc._panX;
-                const y = targetDoc._panY;
-                const scale = targetDoc._viewScale;
-                activeDoc.presPinView = true;
-                activeDoc.presPinViewX = x;
-                activeDoc.presPinViewY = y;
-                activeDoc.presPinViewScale = scale;
-            } else if (targetDoc.type === DocumentType.COMPARISON) {
-                const width = targetDoc._clipWidth;
-                activeDoc.presPinClipWidth = width;
-                activeDoc.presPinView = true;
-            }
+            setTimeout(() => {
+                const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1];
+                const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking;
+                const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG);
+                if (scrollable) {
+                    const scroll = targetDoc._scrollTop;
+                    activeDoc.presPinView = true;
+                    activeDoc.presPinViewScroll = scroll;
+                } else if (targetDoc.type === DocumentType.VID) {
+                    activeDoc.presPinTimecode = targetDoc._currentTimecode;
+                } else if (pannable) {
+                    const x = targetDoc._panX;
+                    const y = targetDoc._panY;
+                    const scale = targetDoc._viewScale;
+                    activeDoc.presPinView = true;
+                    activeDoc.presPinViewX = x;
+                    activeDoc.presPinViewY = y;
+                    activeDoc.presPinViewScale = scale;
+                } else if (targetDoc.type === DocumentType.COMPARISON) {
+                    const width = targetDoc._clipWidth;
+                    activeDoc.presPinClipWidth = width;
+                    activeDoc.presPinView = true;
+                }
+            });
         }
     }
 
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 136486f47..98d6049f0 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -217,9 +217,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements.  That way the PresBox could configure all of its children at render time
             pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
             pinDoc.treeViewHeaderWidth = "100%"; // forces the header to grow to be the same size as its largest sibling.
+            pinDoc.treeViewChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc.
             pinDoc.treeViewFieldKey = "data"; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
             pinDoc.treeViewExpandedView = "data";// in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
-            pinDoc.treeViewGrowsHorizontally = true;// the document can expand horizontally when displayed as a tree view header  
+            pinDoc.treeViewGrowsHorizontally = true;// the document expands horizontally when displayed as a tree view header  
             pinDoc.treeViewHideHeader = true;  // this will force the document to render itself as the tree view header
             const presArray: Doc[] = PresBox.Instance?.sortArray();
             const size: number = PresBox.Instance?._selectedArray.size;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index a35304787..342424d41 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -110,7 +110,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     @computed get treeViewOpen() { return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, "treeViewOpen", "boolean", true)) || this._transientOpenState; }
     @computed get treeViewExpandedView() { return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView; }
     @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containerCollection.maxEmbedHeight, 200); }
-    @computed get dataDoc() { return this.doc[DataSym]; }
+    @computed get dataDoc() { return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym]; }
     @computed get layoutDoc() { return Doc.Layout(this.doc); }
     @computed get fieldKey() { return StrCast(this.doc._treeViewFieldKey, Doc.LayoutFieldKey(this.doc)); }
     @computed get childDocs() { return this.childDocList(this.fieldKey); }
@@ -280,7 +280,12 @@ export class TreeView extends React.Component<TreeViewProps> {
             (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean);
         const move = (!dropAction || dropAction === "proto" || dropAction === "move" || dropAction === "same") && moveDocument;
         if (canAdd) {
-            return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === "proto" ? addDoc(d) : false) : addDoc(d)) || added, false));
+            return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) =>
+                (move ?
+                    move(d, undefined, addDoc) || (dropAction === "proto" ? addDoc(d) : false)
+                    :
+                    addDoc(d)) || added,
+                false));
         }
         return false;
     }
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index c9c74eca4..30ad43562 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -96,7 +96,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable private openMovementDropdown: boolean = false;
     @observable private openEffectDropdown: boolean = false;
     @observable private presentTools: boolean = false;
-    @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); }
+    @computed get childDocs() { return DocListCast(this.rootDoc[this.fieldKey]); }
     @computed get tagDocs() {
         const tagDocs: Doc[] = [];
         for (const doc of this.childDocs) {
@@ -697,7 +697,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return true;
     }
     childLayoutTemplate = () => ![CollectionViewType.Stacking, CollectionViewType.Tree].includes(this.rootDoc._viewType as any) ? undefined : this.presElement;
-    removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
+    removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc);
     getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
     panelHeight = () => this.props.PanelHeight() - 40;
     isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.SelectedTool === InkTool.None && this.props.layerProvider?.(this.layoutDoc) !== false) &&
@@ -2256,14 +2256,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const isMini: boolean = this.toolbarWidth <= 100;
         return (
             <div className="presBox-buttons" style={{ display: !this.rootDoc._chromeHidden ? "none" : undefined }}>
-                {isMini || Doc.UserDoc().noviceMode ? (null) : <select className="presBox-viewPicker"
+                {isMini ? (null) : <select className="presBox-viewPicker"
                     style={{ display: this.layoutDoc.presStatus === "edit" ? "block" : "none" }}
                     onPointerDown={e => e.stopPropagation()}
                     onChange={this.viewChanged}
                     value={mode}>
                     <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option>
                     <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Tree}>Tree</option>
-                    <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}>3D Carousel</option>
+                    {Doc.UserDoc().noviceMode ? (null) : <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}>3D Carousel</option>}
                 </select>}
                 <div className="presBox-presentPanel" style={{ opacity: this.childDocs.length ? 1 : 0.3 }}>
                     <span className={`presBox-button ${this.layoutDoc.presStatus === "edit" ? "present" : ""}`}>
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 5c16d743a..f4dc9b615 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -159,6 +159,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const activeItem = this.rootDoc;
         const dragArray = PresBox.Instance._dragArray;
         const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray());
+        if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
+        dragData.dropAction = "move";
+        dragData.treeViewDoc = this.props.docViewPath().lastElement()?.props.treeViewDoc;
+        dragData.moveDocument = this.props.docViewPath().lastElement()?.props.moveDocument;
         const dragItem: HTMLElement[] = [];
         if (dragArray.length === 1) {
             const doc = dragArray[0];
-- 
cgit v1.2.3-70-g09d2


From ce94cdd070035ef6374eeb471ecf6e4542b4ba20 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 28 Apr 2022 13:53:09 -0400
Subject: fixed rendering of slides in filesystem view to not render the UI,
 just the doc name

---
 src/client/documents/Documents.ts           | 1 +
 src/client/views/collections/TabDocView.tsx | 2 +-
 src/client/views/collections/TreeView.tsx   | 4 ++--
 3 files changed, 4 insertions(+), 3 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 087d32a60..741868a99 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -282,6 +282,7 @@ export class DocumentOptions {
     strokeWidth?: number;
     freezeChildren?: string; // whether children are now allowed to be added and or removed from a collection
     treeViewHideTitle?: boolean; // whether to hide the top document title of a tree view
+    treeViewHideHeaderIfTemplate?: boolean; // whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)
     treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view
     treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
     treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 98d6049f0..8e45ec3b3 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -221,7 +221,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             pinDoc.treeViewFieldKey = "data"; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
             pinDoc.treeViewExpandedView = "data";// in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
             pinDoc.treeViewGrowsHorizontally = true;// the document expands horizontally when displayed as a tree view header  
-            pinDoc.treeViewHideHeader = true;  // this will force the document to render itself as the tree view header
+            pinDoc.treeViewHideHeaderIfTemplate = true;  // this will force the document to render itself as the tree view header
             const presArray: Doc[] = PresBox.Instance?.sortArray();
             const size: number = PresBox.Instance?._selectedArray.size;
             const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 3a49297f8..647476784 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -596,7 +596,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
     }
     onKeyDown = (e: React.KeyboardEvent) => {
-        if (this.doc.treeViewHideHeader || this.props.treeView.outlineMode) {
+        if (this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) {
             switch (e.key) {
                 case "Tab":
                     e.stopPropagation();
@@ -810,7 +810,7 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     render() {
         TraceMobx();
-        const hideTitle = this.doc.treeViewHideHeader || this.props.treeView.outlineMode;
+        const hideTitle = this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode;
         return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? "<" + this.doc.title + ">" : // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
             <div className={`treeView-container${this.props.isContentActive() ? "-active" : ""}`}
                 ref={this.createTreeDropTarget}
-- 
cgit v1.2.3-70-g09d2

