From d182a3462a06ea58c4a0c937190aaa66eced0c01 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 24 Sep 2022 01:57:34 -0400
Subject: Fixed: treeView now doesn't get pointer events if it's not active. 
 fixed layout of treeview for pres box.  fixed horiz/vert scrolling for trees.
  fixed not adding Loading docs to recently closed.

---
 .../views/collections/CollectionTreeView.tsx       | 73 ++++++++++++----------
 1 file changed, 41 insertions(+), 32 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 dce792d19..fe5dc17f5 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -83,7 +83,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         return this.doc === Doc.MyDashboards;
     }
 
-    @observable _explainerHeight = 0; // height of the description of the tree view
+    @observable _titleHeight = 0; // height of the title bar
 
     MainEle = () => this._mainEle;
 
@@ -111,7 +111,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     computeHeight = () => {
         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());
+            const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()) + 6;
             this.layoutDoc._autoHeightMargins = bodyHeight;
             this.props.setHeight?.(bodyHeight + titleHeight);
         }
@@ -298,7 +298,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     }
     @computed get titleBar() {
         return this.dataDoc === null ? null : (
-            <div className="collectionTreeView-titleBar" key={this.doc[Id]} style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}} ref={r => (this._titleRef = r)}>
+            <div
+                className="collectionTreeView-titleBar"
+                ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.props.ScreenToLocalTransform().Scale))}
+                key={this.doc[Id]}
+                style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
                 {this.outlineMode ? this.documentTitle : this.editableTitle}
             </div>
         );
@@ -378,40 +382,45 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined);
         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: '100%', //this.layoutDoc._autoHeight ? "max-content" : "100%"
-                }}>
-                {titleBar}
+            <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
+                {!this.buttonMenu && !this.noviceExplainer ? null : (
+                    <div className="documentButtonMenu">
+                        {this.buttonMenu}
+                        {this.noviceExplainer}
+                    </div>
+                )}
                 <div
-                    className="collectionTreeView-container"
+                    className="collectionTreeView-contents"
+                    key="tree"
                     style={{
-                        transform: this.outlineMode ? `scale(${this.nativeDimScaling})` : '',
-                        paddingLeft: `${this.marginX()}px`,
-                        width: this.outlineMode ? `calc(${100 / this.nativeDimScaling}%)` : '',
-                    }}
-                    onContextMenu={this.onContextMenu}>
-                    {!this.buttonMenu && !this.noviceExplainer ? null : (
-                        <div className="documentButtonMenu" ref={action((r: HTMLDivElement) => r && (this._explainerHeight = r.getBoundingClientRect().height))}>
-                            {this.buttonMenu}
-                            {this.noviceExplainer}
-                        </div>
-                    )}
+                        ...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
+                        overflow: 'auto',
+                        width: '100%',
+                        height: '100%',
+                    }}>
+                    {titleBar}
                     <div
-                        className="collectionTreeView-dropTarget"
+                        className="collectionTreeView-container"
                         style={{
-                            background: background(),
-                            height: `calc(100% - ${this._explainerHeight}px)`,
-                            pointerEvents: pointerEvents(),
+                            transform: this.outlineMode ? `scale(${this.nativeDimScaling})` : '',
+                            paddingLeft: `${this.marginX()}px`,
+                            width: this.outlineMode ? `calc(${100 / this.nativeDimScaling}%)` : '',
+                            minHeight: `calc(100% - ${this._titleHeight}px)`,
                         }}
-                        onWheel={e => e.stopPropagation()}
-                        onDrop={this.onTreeDrop}
-                        ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}>
-                        <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul>
+                        onContextMenu={this.onContextMenu}>
+                        <div
+                            className="collectionTreeView-dropTarget"
+                            style={{
+                                background: background(),
+                                pointerEvents: pointerEvents(),
+                                height: `max-content`,
+                                minHeight: '100%',
+                            }}
+                            onWheel={e => e.stopPropagation()}
+                            onDrop={this.onTreeDrop}
+                            ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}>
+                            <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul>
+                        </div>
                     </div>
                 </div>
             </div>,
-- 
cgit v1.2.3-70-g09d2


From ba73479fe6c0594ebe37eed6cf295e5adc49c8e1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 25 Oct 2022 11:10:12 -0400
Subject: various tweaks: show border highlights in tree views, uniform
 treatment of embedding using title bar, even for free form views, choose
 bestAlias when dragging from files menu,  text boxes honor document moves,
 show title for timeline annotations, fix dragoffset for stacks with
 buttons/description headers,

---
 src/Utils.ts                                       |   6 +-
 src/client/util/DocumentManager.ts                 |   4 +-
 src/client/util/DragManager.ts                     |  18 ++-
 src/client/views/DocumentDecorations.tsx           |   7 +-
 src/client/views/StyleProvider.tsx                 |  17 ++-
 src/client/views/collections/CollectionSubView.tsx |   7 +-
 .../views/collections/CollectionTreeView.tsx       |   7 +-
 src/client/views/collections/TreeView.tsx          |  19 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  32 +++--
 src/client/views/nodes/DocumentView.tsx            | 136 ++++++++++-----------
 .../views/nodes/formattedText/FormattedTextBox.tsx |   3 +
 src/fields/Doc.ts                                  |   5 +
 12 files changed, 133 insertions(+), 128 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index bf1f72774..5e0514bc6 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -728,10 +728,10 @@ export function getWordAtPoint(elem: any, x: number, y: number): string | undefi
     return undefined;
 }
 
-export function hasDescendantTarget(x: number, y: number, target: HTMLDivElement | null) {
+export function isTargetChildOf(ele: HTMLDivElement | null, target: Element | null) {
     let entered = false;
-    for (let child = document.elementFromPoint(x, y); !entered && child; child = child.parentElement) {
-        entered = entered || child === target;
+    for (let child = target; !entered && child; child = child.parentElement) {
+        entered = child === ele;
     }
     return entered;
 }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 9a46d20de..b046d950f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -335,9 +335,7 @@ export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) {
     } else {
         const context = doc.context !== Doc.MyFilesystem && Cast(doc.context, Doc, null);
         const showDoc = context || doc;
-        const bestAlias = showDoc === Doc.GetProto(showDoc) ? DocListCast(showDoc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : showDoc;
-
-        CollectionDockingView.AddSplit(bestAlias ? bestAlias : Doc.MakeAlias(showDoc), 'right') && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc, {}));
+        CollectionDockingView.AddSplit(Doc.BestAlias(showDoc), 'right') && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc, {}));
     }
 }
 ScriptingGlobals.add(DocFocusOrOpen);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 160aba294..899764866 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -206,7 +206,7 @@ export namespace DragManager {
                         !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
                             ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result)
                             : docDragData.dropAction === 'alias'
-                            ? Doc.MakeAlias(d)
+                            ? Doc.BestAlias(d)
                             : docDragData.dropAction === 'proto'
                             ? Doc.GetProto(d)
                             : docDragData.dropAction === 'copy'
@@ -260,10 +260,6 @@ export namespace DragManager {
         StartDrag([ele], dragData, downX, downY, options);
     }
 
-    export function StartImgDrag(ele: HTMLElement, downX: number, downY: number) {
-        StartDrag([ele], {}, downX, downY);
-    }
-
     export function SetSnapLines(horizLines: number[], vertLines: number[]) {
         SnappingManager.setSnapLines(horizLines, vertLines);
     }
@@ -325,7 +321,7 @@ export namespace DragManager {
     export let DocDragData: DocumentDragData | undefined;
     export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
         if (dragData.dropAction === 'none') return;
-        DocDragData = dragData instanceof DocumentDragData ? dragData : undefined;
+        DocDragData = dragData as DocumentDragData;
         const batch = UndoManager.StartBatch('dragging');
         eles = eles.filter(e => e);
         CanEmbed = dragData.canEmbed || false;
@@ -357,15 +353,15 @@ export namespace DragManager {
         let rot = 0;
         const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : [];
         const dragElements = eles.map(ele => {
-            let useDim = false;
+            // bcz: very hacky -- if dragged element is a freeForm view with a rotation, then extract the rotation in order to apply it to the dragged element
+            let useDim = false; // if doc is rotated by freeformview, then the dragged elements width and height won't reflect the unrotated dimensions, so we need to rely on the element knowing its own width/height.  \
+            // if the parent isn't a freeform view, then the element's width and height are presumed to match the acutal doc's dimensions (eg, dragging from import sidebar menu)
             if (ele?.parentElement?.parentElement?.parentElement?.className === 'collectionFreeFormDocumentView-container') {
                 ele = ele.parentElement.parentElement.parentElement;
-                const rotStr = ele.style.transform.replace(/.*rotate\(([-0-9.e]*)deg\).*/, '$1');
-                if (rotStr) rot = Number(rotStr);
+                rot = Number(ele.style.transform.replace(/.*rotate\(([-0-9.e]*)deg\).*/, '$1') || 0);
             } else {
                 useDim = true;
             }
-            if (rot < 0) rot += 360;
             if (!ele.parentNode) dragDiv.appendChild(ele);
             const dragElement = ele.parentNode === dragDiv ? ele : (ele.cloneNode(true) as HTMLElement);
             const children = Array.from(dragElement.children);
@@ -387,7 +383,7 @@ export namespace DragManager {
             const rect = ele.getBoundingClientRect();
             const w = ele.offsetWidth || rect.width;
             const h = ele.offsetHeight || rect.height;
-            const rotR = -(rot / 180) * Math.PI;
+            const rotR = -((rot < 0 ? rot + 360 : rot) / 180) * Math.PI;
             const tl = [0, 0];
             const tr = [Math.cos(rotR) * w, Math.sin(-rotR) * w];
             const bl = [Math.sin(rotR) * h, Math.cos(-rotR) * h];
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6cf7df357..10517d829 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -177,6 +177,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         );
         dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
         dragData.moveDocument = dragDocView.props.moveDocument;
+        dragData.removeDocument = dragDocView.props.removeDocument;
         dragData.isDocDecorationMove = true;
         dragData.canEmbed = dragTitle;
         this._hidden = this.Interacting = true;
@@ -250,8 +251,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         if (selectedDocs.length) {
             if (e.ctrlKey) {
                 // open an alias in a new tab with Ctrl Key
-                const bestAlias = DocListCast(selectedDocs[0].props.Document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
-                CollectionDockingView.AddSplit(bestAlias ?? Doc.MakeAlias(selectedDocs[0].props.Document), 'right');
+                CollectionDockingView.AddSplit(Doc.BestAlias(selectedDocs[0].props.Document), 'right');
             } else if (e.shiftKey) {
                 // open centered in a new workspace with Shift Key
                 const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
@@ -766,6 +766,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 onBlur={e => !hideTitle && this.titleBlur()}
                 onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
                 onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
+                onPointerDown={e => e.stopPropagation()}
             />
         ) : (
             <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown}>
@@ -825,7 +826,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                             <div className="documentDecorations-topbar" onPointerDown={this.onContainerDown}>
                                 {hideDeleteButton ? <div /> : topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')}
                                 {hideResizers || hideDeleteButton ? <div /> : topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')}
-                                {hideResizers ? <div /> : titleArea}
+                                {titleArea}
                                 {hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')}
                             </div>
                             {hideResizers ? null : (
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index a5a886f42..2eb3dd532 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -3,8 +3,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, runInAction } from 'mobx';
 import { extname } from 'path';
 import { Doc, Opt } from '../../fields/Doc';
-import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types';
-import { DashColor, lightOrDark } from '../../Utils';
+import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types';
+import { DashColor, emptyFunction, lightOrDark } from '../../Utils';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { DocFocusOrOpen } from '../util/DocumentManager';
 import { ColorScheme } from '../util/SettingsManager';
@@ -42,6 +42,7 @@ export enum StyleProp {
     FontSize = 'fontSize', // size of text font
     FontFamily = 'fontFamily', // font family of text
     FontWeight = 'fontWeight', // font weight of text
+    Highlighting = 'highlighting', // border highlighting
 }
 
 function darkScheme() {
@@ -107,6 +108,18 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             if (doc?._viewType === CollectionViewType.Freeform) allSorts[TreeSort.Zindex] = { color: 'green', label: 'z' };
             allSorts[TreeSort.None] = { color: 'darkgray', label: '\u00A0\u00A0\u00A0' };
             return allSorts;
+        case StyleProp.Highlighting:
+            if (doc) {
+                const highlightIndex = Doc.isBrushedHighlightedDegree(doc);
+                const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
+                const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
+                const excludeTypes = !props?.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
+                let highlighting = !props?.disableDocBrushing && highlightIndex && !excludeTypes.includes(doc.type as any) && doc._viewType !== CollectionViewType.Linear; // bcz: hack to turn off highlighting onsidebar panel documents.  need to flag a document as not highlightable in a more direct way
+                if (highlighting && props?.focus !== emptyFunction && StrCast(doc.title) !== '[pres element template]') {
+                    return { highlightStyle, highlightColor, highlightIndex };
+                }
+            }
+            return undefined;
         case StyleProp.DocContents:
             return undefined;
         case StyleProp.WidgetColor:
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 30759b766..7bc273d7d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -218,12 +218,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                     const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
                     const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
                     if (movedDocs.length) {
-                        const canAdd =
-                            this.props.Document._viewType === CollectionViewType.Pile ||
-                            de.embedKey ||
-                            !this.props.isAnnotationOverlay ||
-                            this.props.Document.allowOverlayDrop ||
-                            Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document);
+                        const canAdd = this.props.Document._viewType === CollectionViewType.Pile || de.embedKey || this.props.Document.allowOverlayDrop || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document);
                         added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse);
                     } else {
                         ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index fe5dc17f5..1169f692a 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -147,6 +147,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         }
     };
 
+    screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this._headerHeight);
+
     @action
     remove = (doc: Doc | Doc[]): boolean => {
         const docs = doc instanceof Doc ? [doc] : doc;
@@ -275,7 +277,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             dropAction,
             this.props.addDocTab,
             this.props.styleProvider,
-            this.props.ScreenToLocalTransform,
+            this.screenToLocalTransform,
             this.isContentActive,
             this.panelWidth,
             this.props.renderDepth,
@@ -377,6 +379,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     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;
 
+    @observable _headerHeight = 0;
     contentFunc = () => {
         const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
         const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined);
@@ -384,7 +387,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         return [
             <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
                 {!this.buttonMenu && !this.noviceExplainer ? null : (
-                    <div className="documentButtonMenu">
+                    <div className="documentButtonMenu" ref={action((r: HTMLDivElement | null) => r && (this._headerHeight = Number(getComputedStyle(r).height.replace(/px/, ''))))}>
                         {this.buttonMenu}
                         {this.noviceExplainer}
                     </div>
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 409b615f4..b9f86fa6b 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -10,11 +10,12 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, returnTrue, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, simulateMouseClick, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
@@ -31,7 +32,6 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
 import { CollectionView } from './CollectionView';
 import './TreeView.scss';
 import React = require('react');
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 
 export interface TreeViewProps {
     treeView: CollectionTreeView;
@@ -792,12 +792,14 @@ export class TreeView extends React.Component<TreeViewProps> {
     titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
 
+        // prettier-ignore
         switch (property.split(':')[0]) {
-            case StyleProp.Opacity:
-                return this.props.treeView.outlineMode ? undefined : 1;
-            case StyleProp.BackgroundColor:
-                return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
+            case StyleProp.Opacity:         return this.props.treeView.outlineMode ? undefined : 1;
+            case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
+            case StyleProp.Highlighting:    if (this.props.treeView.outlineMode) return undefined;
             case StyleProp.DocContents:
+                const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc);
+                const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                 return this.props.treeView.outlineMode ? null : (
                     <div
                         className="treeView-label"
@@ -805,13 +807,13 @@ export class TreeView extends React.Component<TreeViewProps> {
                             // just render a title for a tree view label (identified by treeViewDoc being set in 'props')
                             maxWidth: props?.PanelWidth() || undefined,
                             background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor),
+                            outline: `solid ${highlightColor} ${highlightIndex}px`,
                         }}>
                         {StrCast(doc?.title)}
                     </div>
                 );
-            default:
-                return this.props?.treeView?.props.styleProvider?.(doc, props, property);
         }
+        return this.props?.treeView?.props.styleProvider?.(doc, props, property);
     };
     embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (property.startsWith(StyleProp.Decorations)) return null;
@@ -1011,6 +1013,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 pinToPres={this.props.treeView.props.pinToPres}
                 disableDocBrushing={this.props.treeView.props.disableDocBrushing}
                 bringToFront={returnFalse}
+                scriptContext={this}
             />
         );
     };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 88422cbd4..eca95e1e0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -113,7 +113,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _clusterSets: Doc[][] = [];
     @observable _deleteList: DocumentView[] = [];
     @observable _timelineRef = React.createRef<Timeline>();
-    @observable _marqueeRef = React.createRef<HTMLDivElement>();
+    @observable _marqueeRef: HTMLDivElement | null = null;
     @observable _marqueeViewRef = React.createRef<MarqueeView>();
     @observable ChildDrag: DocumentView | undefined; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
     @observable _brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 }; // highlighted region of freeform canvas used by presentations to indicate a region
@@ -1011,22 +1011,22 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         switch (Doc.UserDoc().freeformScrollMode) {
             case freeformScrollMode.Pan:
                 // if shift is selected then zoom
-                if(e.ctrlKey) {
+                if (e.ctrlKey) {
                     if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
                         // things that can scroll vertically should do that instead of zooming
                     } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
                         !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
                     }
-                // otherwise pan
+                    // otherwise pan
                 } else if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
                     // things that can scroll vertically should do that instead of zooming
                 } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
                     const dx = e.deltaX;
                     const dy = e.deltaY;
                     if (e.shiftKey) {
-                        !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dy, deltaY: 0 }); 
+                        !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dy, deltaY: 0 });
                     } else {
-                        !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dx, deltaY: dy }); 
+                        !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dx, deltaY: dy });
                     }
                 }
                 break;
@@ -1548,9 +1548,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         setTimeout(
             action(() => {
                 this._firstRender = false;
-
-                this._marqueeRef.current?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
-
                 this._disposers.groupBounds = reaction(
                     () => {
                         if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
@@ -1668,7 +1665,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     componentWillUnmount() {
         Object.values(this._disposers).forEach(disposer => disposer?.());
-        this._marqueeRef.current?.removeEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
+        this._marqueeRef?.removeEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
     }
 
     @action
@@ -1681,10 +1678,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if ((e as any).handlePan || this.props.isAnnotationOverlay) return;
         (e as any).handlePan = true;
 
-        if (!this.layoutDoc._noAutoscroll && !this.props.renderDepth && this._marqueeRef?.current) {
+        if (!this.layoutDoc._noAutoscroll && !this.props.renderDepth && this._marqueeRef) {
             const dragX = e.detail.clientX;
             const dragY = e.detail.clientY;
-            const bounds = this._marqueeRef.current?.getBoundingClientRect();
+            const bounds = this._marqueeRef?.getBoundingClientRect();
 
             const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
             const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
@@ -1842,10 +1839,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         DragManager.SetSnapLines(horizLines, vertLines);
     };
 
-    onPointerOver = (e: React.PointerEvent) => {
-        e.stopPropagation();
-    };
-
     incrementalRender = action(() => {
         if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
             const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
@@ -1889,7 +1882,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 getContainerTransform={this.getContainerTransform}
                 getTransform={this.getTransform}
                 isAnnotationOverlay={this.isAnnotationOverlay}>
-                <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0.7 : undefined }}>
+                <div
+                    className="marqueeView-div"
+                    ref={r => {
+                        this._marqueeRef = r;
+                        r?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
+                    }}
+                    style={{ opacity: this.props.dontRenderDocuments ? 0.7 : undefined }}>
                     {this.layoutDoc._backgroundGridShow ? (
                         <div>
                             <CollectionFreeFormBackgroundGrid // bcz : UGHH  don't know why, but if we don't wrap in a div, then PDF's don't render whenn taking snapshot of a dashboard and the background grid is on!!?
@@ -1965,7 +1964,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             <div
                 className={'collectionfreeformview-container'}
                 ref={this.createDashEventsTarget}
-                onPointerOver={this.onPointerOver}
                 onWheel={this.onPointerWheel}
                 onClick={this.onClick}
                 onPointerDown={this.onPointerDown}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d3858c25e..fecbc2de2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -15,7 +15,7 @@ import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../.
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, hasDescendantTarget, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { DocServer } from '../../DocServer';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -956,60 +956,58 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 moreItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
             }
             !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
-
-            const help = cm.findByDescription('Help...');
-            const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
-            helpItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), 'add:right'), icon: 'layer-group' });
-            !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), 'add:right'), icon: 'keyboard' });
-            !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' });
-            !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' });
-            
-            let documentationDescription: string | undefined = undefined;
-            let documentationLink: string | undefined = undefined;
-            console.log("type: ", this.props.Document.type);
-            switch(this.props.Document.type) {
-                case DocumentType.COL:
-                    documentationDescription = "See collection documentation";
-                    documentationLink = "https://brown-dash.github.io/Dash-Documentation/views/";
-                    break;
-                case DocumentType.PDF:
-                    documentationDescription = "See PDF node documentation";
-                    documentationLink = "https://brown-dash.github.io/Dash-Documentation/documents/pdf/";
-                    break;
-                case DocumentType.VID:
-                    documentationDescription = "See video node documentation";
-                    documentationLink = "https://brown-dash.github.io/Dash-Documentation/documents/tempMedia/video";
-                    break;
-                case DocumentType.AUDIO:
-                    documentationDescription = "See audio node documentation";
-                    documentationLink = "https://brown-dash.github.io/Dash-Documentation/documents/tempMedia/audio";
-                    break;
-                case DocumentType.WEB:
-                    documentationDescription = "See webpage node documentation";
-                    documentationLink = "https://brown-dash.github.io/Dash-Documentation/documents/webpage/";
-                    break;
-                case DocumentType.IMG:
-                    documentationDescription = "See image node documentation";
-                    documentationLink = "https://brown-dash.github.io/Dash-Documentation/documents/images/";
-                    break;
-                case DocumentType.RTF:
-                    documentationDescription = "See text node documentation";
-                    documentationLink = "https://brown-dash.github.io/Dash-Documentation/documents/text/";
-                    break;
-                default:
-                    break;
-            }
-
-            // Add link to help documentation
-            if (documentationDescription && documentationLink) {
-                console.log("add documentation item");
-                helpItems.push({ description: documentationDescription, event: () => {
+        }
+        const help = cm.findByDescription('Help...');
+        const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
+        helpItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), 'add:right'), icon: 'layer-group' });
+        !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), 'add:right'), icon: 'keyboard' });
+        !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' });
+        !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' });
+
+        let documentationDescription: string | undefined = undefined;
+        let documentationLink: string | undefined = undefined;
+        switch (this.props.Document.type) {
+            case DocumentType.COL:
+                documentationDescription = 'See collection documentation';
+                documentationLink = 'https://brown-dash.github.io/Dash-Documentation/views/';
+                break;
+            case DocumentType.PDF:
+                documentationDescription = 'See PDF node documentation';
+                documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/pdf/';
+                break;
+            case DocumentType.VID:
+                documentationDescription = 'See video node documentation';
+                documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/tempMedia/video';
+                break;
+            case DocumentType.AUDIO:
+                documentationDescription = 'See audio node documentation';
+                documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/tempMedia/audio';
+                break;
+            case DocumentType.WEB:
+                documentationDescription = 'See webpage node documentation';
+                documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/webpage/';
+                break;
+            case DocumentType.IMG:
+                documentationDescription = 'See image node documentation';
+                documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/images/';
+                break;
+            case DocumentType.RTF:
+                documentationDescription = 'See text node documentation';
+                documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/text/';
+                break;
+        }
+        // Add link to help documentation
+        if (documentationDescription && documentationLink) {
+            console.log('add documentation item');
+            helpItems.push({
+                description: documentationDescription,
+                event: () => {
                     window.open(documentationLink, '_blank');
-                }, icon: 'book' })
-            };
-
-            cm.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'question' });
+                },
+                icon: 'book',
+            });
         }
+        cm.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'question' });
 
         if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
@@ -1411,29 +1409,23 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
     render() {
         TraceMobx();
-        const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : Doc.DocBrushStatus.unbrushed) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
-        const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
-        const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
-        const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
-        let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
-        highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== '[pres element template]'; // bcz: hack to turn off highlighting onsidebar panel documents.  need to flag a document as not highlightable in a more direct way
-
+        const highlighting = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.Highlighting);
         const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath) || { path: undefined };
-        const boxShadow = this.props.treeViewDoc
-            ? null
-            : highlighting && this.borderRounding && highlightStyle !== 'dashed'
-            ? `0 0 0 ${highlightIndex}px ${highlightColor}`
-            : this.boxShadow || (this.props.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
+        const boxShadow =
+            this.props.treeViewDoc || !highlighting
+                ? null
+                : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
+                ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
+                : this.boxShadow || (this.props.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
         const renderDoc = this.renderDoc({
             borderRadius: this.borderRounding,
-            outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : 'solid 0px',
-            border: highlighting && this.borderRounding && highlightStyle === 'dashed' ? `${highlightStyle} ${highlightColor} ${highlightIndex}px` : undefined,
+            outline: highlighting && !this.borderRounding && highlighting ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px',
+            border: highlighting && this.borderRounding && highlighting && highlighting.highlightStyle === 'dashed' ? `${highlighting.highlightStyle} ${highlighting.highlightColor} ${highlighting.highlightIndex}px` : undefined,
             boxShadow,
             clipPath: borderPath.path ? `path('${borderPath.path}')` : undefined,
         });
 
         const animRenderDoc = PresBox.Instance?.isActiveItemTarget(this.layoutDoc) ? PresBox.AnimationEffect(renderDoc, PresBox.Instance.activeItem) : renderDoc;
-        // Return surrounding highlight
         return (
             <div
                 className={`${DocumentView.ROOT_DIV} docView-hack`}
@@ -1442,8 +1434,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 onKeyDown={this.onKeyDown}
                 onPointerDown={this.onPointerDown}
                 onClick={this.onClick}
-                onPointerEnter={action(e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document))}
-                onPointerLeave={action(e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document))}
+                onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)}
+                onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)}
+                onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.props.Document)}
                 style={{
                     display: this.hidden ? 'inline' : undefined,
                     borderRadius: this.borderRounding,
@@ -1453,9 +1446,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     animRenderDoc
                 ) : (
                     <>
-                        {/* <div style={{ clipPath: `path('${borderPath.fill}')` }}>
-                        {animRenderDoc}
-                    </div> */}
                         {animRenderDoc}
                         <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
                             <svg style={{ overflow: 'visible' }} viewBox={`0 0 ${this.props.PanelWidth()} ${this.props.PanelHeight()}`}>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a94e2c96d..f5d00c2b8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -538,6 +538,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     docid: target[Id],
                     float: 'unset',
                 });
+                if (!['alias', 'copy'].includes((dragData.dropAction ?? '') as any)) {
+                    dragData.removeDocument?.(dragData.draggedDocuments[0]);
+                }
                 const view = this._editorView!;
                 view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
                 e.stopPropagation();
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 0ceaff968..460dd6081 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -680,6 +680,11 @@ export namespace Doc {
         return alias;
     }
 
+    export function BestAlias(doc: Doc) {
+        const bestAlias = Doc.GetProto(doc) ? DocListCast(doc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : doc;
+        return bestAlias ?? Doc.MakeAlias(doc);
+    }
+
     export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<Doc, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
         if (Doc.IsBaseProto(doc)) return doc;
         if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
-- 
cgit v1.2.3-70-g09d2


From 2fc88a931cb2fc3408297b000208990633445585 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 27 Oct 2022 15:57:09 -0400
Subject: cleaned up treeViews to work across PresBox/ppt slides/collections/ -
 modified DocumentView rendering to remove pres box refs.  fixed mainView
 width.

---
 src/client/documents/Documents.ts                  |   4 +-
 src/client/util/CurrentUserUtils.ts                |   8 +-
 src/client/views/DocumentDecorations.tsx           |  18 +-
 src/client/views/LightboxView.tsx                  |  26 +--
 src/client/views/MainView.tsx                      |   1 +
 src/client/views/StyleProvider.tsx                 |   5 +-
 .../views/collections/CollectionStackingView.tsx   |   2 +
 .../views/collections/CollectionTreeView.scss      |   2 -
 .../views/collections/CollectionTreeView.tsx       |  62 ++++---
 src/client/views/collections/CollectionView.tsx    |   2 +
 src/client/views/collections/TabDocView.tsx        |   1 -
 src/client/views/collections/TreeView.scss         |   3 +-
 src/client/views/collections/TreeView.tsx          | 179 +++++++++----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   6 +-
 src/client/views/nodes/DocumentView.tsx            |  37 ++--
 src/client/views/nodes/FieldView.tsx               |   2 -
 src/client/views/nodes/VideoBox.tsx                |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   8 +-
 src/client/views/nodes/trails/PresBox.tsx          |   7 +-
 src/client/views/nodes/trails/PresElementBox.scss  |  14 +-
 src/client/views/nodes/trails/PresElementBox.tsx   | 197 ++++++++++++---------
 src/fields/Doc.ts                                  |   3 +
 23 files changed, 301 insertions(+), 290 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2d82ffdf9..bbb896d6e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -335,7 +335,6 @@ export class DocumentOptions {
     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
     treeViewChildDoubleClick?: ScriptField; //
     // Action Button
     buttonMenu?: boolean; // whether a action button should be displayed
@@ -395,7 +394,6 @@ export namespace Docs {
                         _xMargin: 10,
                         _yMargin: 10,
                         nativeDimModifiable: true,
-                        treeViewGrowsHorizontally: true,
                         nativeHeightUnfrozen: true,
                         forceReflow: true,
                         links: '@links(self)',
@@ -570,7 +568,7 @@ export namespace Docs {
                 DocumentType.SLIDER,
                 {
                     layout: { view: SliderBox, dataField: defaultDataKey },
-                    options: { links: '@links(self)', treeViewGrowsHorizontally: true },
+                    options: { links: '@links(self)' },
                 },
             ],
             [
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 2bc464127..a2974177e 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -480,7 +480,7 @@ export class CurrentUserUtils {
         const childContextMenuIcons = ["chalkboard", "tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
         const reqdOpts:DocumentOptions = {
             title: "My Dashboards", childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, boxShadow: "0 0", childDontRegisterViews: true,
-            targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true,  treeViewTruncateTitleWidth: 150, ignoreClick: true, 
+            targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true,  treeViewTruncateTitleWidth: 350, ignoreClick: true, 
             buttonMenu: true, buttonMenuDoc: newDashboardButton, childDropAction: "alias",
             _showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
             contextMenuLabels:new List<string>(contextMenuLabels),
@@ -518,7 +518,7 @@ export class CurrentUserUtils {
         const reqdOpts:DocumentOptions = { _showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true, 
             title: "My Documents", buttonMenu: true, buttonMenuDoc: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", system: true,
             isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, boxShadow: "0 0", childDontRegisterViews: true, 
-            treeViewTruncateTitleWidth: 150, ignoreClick: true, childDropAction: "alias",
+            treeViewTruncateTitleWidth: 350, ignoreClick: true, childDropAction: "alias",
             childContextMenuLabels: new List<string>(["Create new folder"]),
             childContextMenuIcons: new List<string>(["plus"]),
             explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
@@ -535,7 +535,7 @@ export class CurrentUserUtils {
     static setupRecentlyClosed(doc: Doc, field:string) {
         const reqdOpts:DocumentOptions = { _showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, 
             title: "My Recently Closed", buttonMenu: true, childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "alias", system: true,
-            treeViewTruncateTitleWidth: 150, ignoreClick: true,  boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same",
+            treeViewTruncateTitleWidth: 350, ignoreClick: true,  boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same",
             contextMenuLabels: new List<string>(["Empty recently closed"]),
             contextMenuIcons:new List<string>(["trash"]),
             explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
@@ -561,7 +561,7 @@ export class CurrentUserUtils {
         const reqdOpts:DocumentOptions = {
             _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
             boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true,
-            treeViewHideTitle: true, treeViewTruncateTitleWidth: 150
+            treeViewHideTitle: true, treeViewTruncateTitleWidth: 350
         };
         if (!doc[field]) DocUtils.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" });
         return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 10517d829..06eb6c6d7 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -112,7 +112,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                             }
                         }
                         //@ts-ignore
-                        const titleField = +this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle;
+                        const titleField = +this._accumulatedTitle == this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle;
                         Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
 
                         if (d.rootDoc.syncLayoutFieldWithTitle) {
@@ -345,7 +345,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
         const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
         const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
-        const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.rootDoc._jitterRotation) / 180) * Math.PI);
+        const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.rootDoc._rotation) / 180) * Math.PI);
         seldocview.rootDoc.rotateCenterX = final.x / NumCast(seldocview.layoutDoc._width);
         seldocview.rootDoc.rotateCenterY = final.y / NumCast(seldocview.layoutDoc._height);
     };
@@ -381,7 +381,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         const infos = new Map<Doc, { unrotatedDocPos: { x: number; y: number }; startRotCtr: { x: number; y: number }; accumRot: number }>();
         const seldocview = SelectionManager.Views()[0];
         SelectionManager.Views().forEach(dv => {
-            const accumRot = (NumCast(dv.rootDoc._jitterRotation) / 180) * Math.PI;
+            const accumRot = (NumCast(dv.rootDoc._rotation) / 180) * Math.PI;
             const localRotCtr = dv.props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y);
             const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.rootDoc.width) / 2, localRotCtr[1] - NumCast(dv.rootDoc.height) / 2];
             const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot);
@@ -396,7 +396,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     infos.set(dv.rootDoc, { unrotatedDocPos, startRotCtr, accumRot: isAbs ? angle : accumRot + angle });
                     dv.rootDoc.x = infos.get(dv.rootDoc)!.unrotatedDocPos.x - (endRotCtr.x - startRotCtr.x);
                     dv.rootDoc.y = infos.get(dv.rootDoc)!.unrotatedDocPos.y - (endRotCtr.y - startRotCtr.y);
-                    dv.rootDoc._jitterRotation = ((isAbs ? 0 : NumCast(dv.rootDoc._jitterRotation)) + (angle * 180) / Math.PI) % 360; // Rotation between -360 and 360
+                    dv.rootDoc._rotation = ((isAbs ? 0 : NumCast(dv.rootDoc._rotation)) + (angle * 180) / Math.PI) % 360; // Rotation between -360 and 360
                 })
             );
         };
@@ -416,7 +416,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 return false;
             }, // moveEvent
             action(() => {
-                const oldRotation = NumCast(seldocview.rootDoc._jitterRotation);
+                const oldRotation = NumCast(seldocview.rootDoc._rotation);
                 const diff = oldRotation - Math.round(oldRotation / 45) * 45;
                 if (Math.abs(diff) < 5) {
                     if (selectedInk.length) {
@@ -611,7 +611,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         }
                     } else {
                         const rotCtr = [NumCast(doc._width) / 2, NumCast(doc._height) / 2];
-                        const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._jitterRotation) / 180) * Math.PI);
+                        const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._rotation) / 180) * Math.PI);
 
                         const maxHeight = doc.nativHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling();
                         dH && (doc._height = actualdH > maxHeight && maxHeight ? maxHeight : actualdH);
@@ -619,7 +619,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         dH && (doc._autoHeight = false);
 
                         const rotCtr2 = [NumCast(doc._width) / 2, NumCast(doc._height) / 2];
-                        const tlRotated2 = Utils.rotPt(-rotCtr2[0], -rotCtr2[1], (NumCast(doc._jitterRotation) / 180) * Math.PI);
+                        const tlRotated2 = Utils.rotPt(-rotCtr2[0], -rotCtr2[1], (NumCast(doc._rotation) / 180) * Math.PI);
                         doc.x = NumCast(doc.x) + tlRotated.x + rotCtr[0] - (tlRotated2.x + rotCtr2[0]); // doc shifts by amount topleft moves because rotation is about center of doc
                         doc.y = NumCast(doc.y) + tlRotated.y + rotCtr[1] - (tlRotated2.y + rotCtr2[1]);
                     }
@@ -695,7 +695,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             const loccenter = Utils.rotPt(
                 NumCast(seldocview.rootDoc.rotateCenterX) * NumCast(seldocview.layoutDoc._width),
                 NumCast(seldocview.rootDoc.rotateCenterY) * NumCast(seldocview.layoutDoc._height),
-                (NumCast(seldocview.rootDoc._jitterRotation) / 180) * Math.PI
+                (NumCast(seldocview.rootDoc._rotation) / 180) * Math.PI
             );
             return seldocview.props
                 .ScreenToLocalTransform()
@@ -783,7 +783,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
 
         const useRotation = seldocview.rootDoc.type !== DocumentType.EQUATION; // when do we want an object to not rotate?
-        const rotation = NumCast(seldocview.rootDoc._jitterRotation);
+        const rotation = NumCast(seldocview.rootDoc._rotation);
 
         const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : '';
 
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index bd6cea28a..22b0380a2 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -289,29 +289,29 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                             })}
                             Document={LightboxView.LightboxDoc}
                             DataDoc={undefined}
+                            PanelWidth={this.lightboxWidth}
+                            PanelHeight={this.lightboxHeight}
                             LayoutTemplate={LightboxView.LightboxDocTemplate}
-                            addDocument={undefined}
                             isDocumentActive={returnFalse}
                             isContentActive={returnTrue}
-                            addDocTab={this.addDocTab}
-                            pinToPres={TabDocView.PinDoc}
-                            onBrowseClick={MainView.Instance.exploreMode}
+                            styleProvider={DefaultStyleProvider}
+                            ScreenToLocalTransform={this.lightboxScreenToLocal}
+                            renderDepth={0}
                             rootSelected={returnTrue}
                             docViewPath={returnEmptyDoclist}
                             docFilters={this.docFilters}
-                            removeDocument={undefined}
-                            styleProvider={DefaultStyleProvider}
-                            ScreenToLocalTransform={this.lightboxScreenToLocal}
-                            PanelWidth={this.lightboxWidth}
-                            PanelHeight={this.lightboxHeight}
-                            focus={DocUtils.DefaultFocus}
-                            whenChildContentsActiveChanged={emptyFunction}
-                            bringToFront={emptyFunction}
                             docRangeFilters={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
                             ContainingCollectionView={undefined}
                             ContainingCollectionDoc={undefined}
-                            renderDepth={0}
+                            addDocument={undefined}
+                            removeDocument={undefined}
+                            whenChildContentsActiveChanged={emptyFunction}
+                            addDocTab={this.addDocTab}
+                            pinToPres={TabDocView.PinDoc}
+                            bringToFront={emptyFunction}
+                            onBrowseClick={MainView.Instance.exploreMode}
+                            focus={DocUtils.DefaultFocus}
                         />
                     </GestureOverlay>
                 </div>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 03c0642ed..4dc1ebd99 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -647,6 +647,7 @@ export class MainView extends React.Component {
                         e.preventDefault();
                     }}
                     style={{
+                        width: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
                         minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
                         transform: LightboxView.LightboxDoc ? 'scale(0.0001)' : undefined,
                     }}>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 2eb3dd532..bc8bd7b7f 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -85,7 +85,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
     const isAnnotated = property.includes(':annotated');
     const isOpen = property.includes(':open');
     const fieldKey = props?.fieldKey ? props.fieldKey + '-' : isCaption ? 'caption-' : '';
-    const comicStyle = () => doc && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic';
     const isBackground = () => doc && BoolCast(doc._lockedPosition);
     const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
     const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
@@ -167,11 +166,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.TitleHeight:
             return 15;
         case StyleProp.BorderPath:
-            return comicStyle() && props?.renderDepth && doc?.type !== DocumentType.INK
+            return Doc.IsComicStyle(doc) && props?.renderDepth && doc?.type !== DocumentType.INK
                 ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 }
                 : { path: undefined, width: 0 };
         case StyleProp.JitterRotation:
-            return comicStyle() ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
+            return Doc.IsComicStyle(doc) ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
         case StyleProp.HeaderMargin:
             return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) ||
                 (doc?.type === DocumentType.RTF && !showTitle()?.includes('noMargin')) ||
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ba29b1d6f..77b47ed82 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -350,6 +350,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 searchFilterDocs={this.searchFilterDocs}
                 ContainingCollectionDoc={this.props.CollectionView?.props.Document}
                 ContainingCollectionView={this.props.CollectionView}
+                xPadding={NumCast(this.layoutDoc._childXPadding, this.props.childXPadding)}
+                yPadding={NumCast(this.layoutDoc._childYPadding, this.props.childYPadding)}
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
                 removeDocument={this.props.removeDocument}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 1aef29b2f..3785b7d61 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -13,8 +13,6 @@
     width: 100%;
     position: relative;
     top: 0;
-    padding-left: 10px;
-    padding-right: 10px;
     background: $light-gray;
     font-size: 13px;
     overflow: auto;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 1169f692a..0ff89c5a7 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -58,7 +58,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     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 monitoring tree view items.
-    private static expandViewLabelSize = 20;
 
     @computed get doc() {
         return this.props.Document;
@@ -100,7 +99,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
+    shrinkWrap = () => {}; // placeholder to allow setContentView to work
+
     componentDidMount() {
+        //this.props.setContentView?.(this);
         this._disposers.autoheight = reaction(
             () => this.rootDoc.autoHeight,
             auto => auto && this.computeHeight(),
@@ -197,9 +199,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     onTreeDrop = (e: React.DragEvent, addDocs?: (docs: Doc[]) => void) => this.onExternalDrop(e, {}, addDocs);
 
     @undoBatch
-    makeTextCollection = (childDocs: Doc[]) => {
-        this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true);
-    };
+    makeTextCollection = (childDocs: Doc[]) => this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true);
 
     get editableTitle() {
         return (
@@ -258,6 +258,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         const icons = StrListCast(this.doc.childContextMenuIcons);
         return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
     };
+    headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields);
     @computed get treeViewElements() {
         TraceMobx();
         const dropAction = StrCast(this.doc.childDropAction) as dropActionType;
@@ -281,7 +282,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             this.isContentActive,
             this.panelWidth,
             this.props.renderDepth,
-            () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields),
+            this.headerFields,
             [],
             this.props.onCheckedClick,
             this.onChildClick,
@@ -304,7 +305,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 className="collectionTreeView-titleBar"
                 ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.props.ScreenToLocalTransform().Scale))}
                 key={this.doc[Id]}
-                style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
+                style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
                 {this.outlineMode ? this.documentTitle : this.editableTitle}
             </div>
         );
@@ -372,7 +373,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     documentTitleHeight = () => (this.layoutDoc?.[HeightSym]() || 0) - NumCast(this.layoutDoc.autoHeightMargins);
     truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
     onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
-    panelWidth = () => Math.max(0, this.props.PanelWidth() - this.marginX() - CollectionTreeView.expandViewLabelSize) * (this.props.NativeDimScaling?.() || 1);
+    panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 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;
@@ -396,7 +397,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                     className="collectionTreeView-contents"
                     key="tree"
                     style={{
-                        ...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
+                        ...(!titleBar ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
                         overflow: 'auto',
                         width: '100%',
                         height: '100%',
@@ -405,9 +406,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                     <div
                         className="collectionTreeView-container"
                         style={{
-                            transform: this.outlineMode ? `scale(${this.nativeDimScaling})` : '',
-                            paddingLeft: `${this.marginX()}px`,
-                            width: this.outlineMode ? `calc(${100 / this.nativeDimScaling}%)` : '',
+                            marginLeft: `${this.marginX()}px`,
                             minHeight: `calc(100% - ${this._titleHeight}px)`,
                         }}
                         onContextMenu={this.onContextMenu}>
@@ -432,24 +431,29 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     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()
+        const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1) || 1;
+        return (
+            <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
+                {!(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()
+                )}
+            </div>
         );
     }
 }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index dcaad5632..9f63a11aa 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -54,6 +54,8 @@ interface CollectionViewProps_ extends FieldViewProps {
     childHideDecorationTitle?: () => boolean;
     childHideResizeHandles?: () => boolean;
     childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
+    childXPadding?: number;
+    childYPadding?: number;
     childLayoutString?: string;
     childIgnoreNativeSize?: boolean;
     childClickScript?: ScriptField;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index a6cfbd6b2..cde5132a3 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -256,7 +256,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             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 expands horizontally when displayed as a 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;
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 57bb5274d..83fee013a 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -24,6 +24,7 @@
         // width: $TREE_BULLET_WIDTH;
         width: 100%;
         height: 100%;
+        position: absolute;
 
         .treeView-expandIcon {
             display: none;
@@ -118,7 +119,7 @@
     display: flex; // needed for PresBox's treeView
     border: transparent 1px solid;
     align-items: center;
-    width: 100%;
+    width: max-content;
     border-radius: 5px;
 
     &:hover {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index b9f86fa6b..1e97eee37 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -10,7 +10,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -321,6 +321,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             _viewType: CollectionViewType.Tree,
             hideLinkButton: true,
             _showSidebar: true,
+            _fitWidth: true,
             treeViewType: TreeViewType.outline,
             x: 0,
             y: 0,
@@ -398,29 +399,22 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
     docTransform = () => this.refTransform(this._dref?.ContentRef?.current);
     getTransform = () => this.refTransform(this._tref.current);
-    docWidth = () => {
-        const layoutDoc = this.layoutDoc;
-        const aspect = Doc.NativeAspect(layoutDoc);
-        if (layoutDoc._fitWidth) return Math.min(this.props.panelWidth() - treeBulletWidth(), layoutDoc[WidthSym]());
-        if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.panelWidth() - treeBulletWidth()));
-        return Math.min((this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.NativeDimScaling?.() || 1), Doc.NativeWidth(layoutDoc) ? layoutDoc[WidthSym]() : this.layoutDoc[WidthSym]());
-    };
-    docHeight = () => {
-        const layoutDoc = this.layoutDoc;
-        return Math.max(
-            70,
-            Math.min(
-                this.MAX_EMBED_HEIGHT,
-                (() => {
-                    const aspect = Doc.NativeAspect(layoutDoc);
-                    if (aspect) return this.docWidth() / (aspect || 1);
-                    return layoutDoc._fitWidth
-                        ? !Doc.NativeHeight(this.doc)
-                            ? NumCast(this.props.containerCollection._height)
-                            : Math.min((this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)))
-                        : layoutDoc[HeightSym]() || 50;
-                })()
-            )
+    embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1);
+    embeddedPanelHeight = () => {
+        console.log(this.props.treeView.rootDoc.title);
+        const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+        return Math.min(
+            layoutDoc[HeightSym](),
+            this.MAX_EMBED_HEIGHT,
+            (() => {
+                const aspect = Doc.NativeAspect(layoutDoc);
+                if (aspect) return this.embeddedPanelWidth() / (aspect || 1);
+                return layoutDoc._fitWidth
+                    ? !Doc.NativeHeight(layoutDoc)
+                        ? NumCast(layoutDoc._height) //this.props.containerCollection._height)
+                        : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)))
+                    : (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym]();
+            })()
         );
     };
 
@@ -512,28 +506,6 @@ export class TreeView extends React.Component<TreeViewProps> {
         return rows;
     }
 
-    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.NativeDimScaling?.() || 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();
-        const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym]();
-        const docAspect = this.docWidth() / this.docHeight();
-        return docAspect < aspect ? this.docWidth() / aspect : this.docHeight();
-    };
-    expandPanelWidth = () => {
-        if (this.layoutDoc._fitWidth) return this.docWidth();
-        const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym]();
-        const docAspect = this.docWidth() / this.docHeight();
-        return docAspect > aspect ? this.docHeight() * aspect : this.docWidth();
-    };
-
     @computed get renderContent() {
         TraceMobx();
         const expandKey = this.treeViewExpandedView;
@@ -792,15 +764,18 @@ export class TreeView extends React.Component<TreeViewProps> {
     titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
 
+        const treeView = this.props.treeView;
         // prettier-ignore
         switch (property.split(':')[0]) {
             case StyleProp.Opacity:         return this.props.treeView.outlineMode ? undefined : 1;
             case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
             case StyleProp.Highlighting:    if (this.props.treeView.outlineMode) return undefined;
+            case StyleProp.Hidden:          return false;
+            case StyleProp.BoxShadow:       return undefined;
             case StyleProp.DocContents:
                 const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
-                return this.props.treeView.outlineMode ? null : (
+                return treeView.outlineMode ? null : (
                     <div
                         className="treeView-label"
                         style={{
@@ -808,15 +783,20 @@ export class TreeView extends React.Component<TreeViewProps> {
                             maxWidth: props?.PanelWidth() || undefined,
                             background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor),
                             outline: `solid ${highlightColor} ${highlightIndex}px`,
+                            paddingLeft: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
+                            paddingRight: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
+                            paddingTop: treeView.props.childYPadding,
+                            paddingBottom: treeView.props.childYPadding,
                         }}>
                         {StrCast(doc?.title)}
                     </div>
                 );
         }
-        return this.props?.treeView?.props.styleProvider?.(doc, props, property);
+        return treeView.props.styleProvider?.(doc, props, property);
     };
     embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (property.startsWith(StyleProp.Decorations)) return null;
+        if (property.startsWith(StyleProp.Hidden)) return false;
         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, fieldProps: FieldViewProps) => {
@@ -843,7 +823,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         }
         return false;
     };
-    titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth() - 2 * treeBulletWidth()));
+    titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - 3 * treeBulletWidth();
 
     return18 = () => 18;
     /**
@@ -851,6 +831,7 @@ export class TreeView extends React.Component<TreeViewProps> {
      */
     @computed
     get renderTitle() {
+        //
         TraceMobx();
         const view = this._editTitle ? (
             <EditableView
@@ -887,6 +868,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     }
                 })}
                 Document={this.doc}
+                fitWidth={(doc: Doc) => true}
                 DataDoc={undefined}
                 scriptContext={this}
                 hideDecorationTitle={this.props.treeView.outlineMode}
@@ -905,7 +887,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 removeDocument={this.props.removeDoc}
                 ScreenToLocalTransform={this.getTransform}
                 NativeHeight={this.return18}
-                NativeWidth={this.titleWidth}
+                NativeWidth={returnZero}
                 PanelWidth={this.titleWidth}
                 PanelHeight={this.return18}
                 contextMenuItems={this.contextMenuItems}
@@ -918,10 +900,12 @@ export class TreeView extends React.Component<TreeViewProps> {
                 disableDocBrushing={this.props.treeView.props.disableDocBrushing}
                 hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
                 dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
+                xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)}
+                yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)}
                 docFilters={returnEmptyFilter}
                 docRangeFilters={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionView={undefined}
+                ContainingCollectionView={this.props.treeView.props.CollectionView}
                 ContainingCollectionDoc={this.props.treeView.props.Document}
             />
         );
@@ -968,53 +952,52 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
 
     renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => {
-        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))}
-                Document={this.doc}
-                DataDoc={undefined}
-                PanelWidth={panelWidth}
-                PanelHeight={panelHeight}
-                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}
-                hideTitle={asText}
-                fitContentsToBox={returnTrue}
-                hideDecorationTitle={this.props.treeView.outlineMode}
-                hideResizeHandles={this.props.treeView.outlineMode}
-                onClick={this.onChildClick}
-                focus={this.refocus}
-                onKey={this.onKeyDown}
-                hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
-                dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
-                ScreenToLocalTransform={this.docTransform}
-                renderDepth={this.props.renderDepth + 1}
-                treeViewDoc={this.props.treeView?.props.Document}
-                rootSelected={returnTrue}
-                docViewPath={this.props.treeView.props.docViewPath}
-                docFilters={returnEmptyFilter}
-                docRangeFilters={returnEmptyFilter}
-                searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionDoc={this.props.containerCollection}
-                ContainingCollectionView={undefined}
-                addDocument={this.props.addDocument}
-                moveDocument={this.move}
-                removeDocument={this.props.removeDoc}
-                whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
-                addDocTab={this.props.addDocTab}
-                pinToPres={this.props.treeView.props.pinToPres}
-                disableDocBrushing={this.props.treeView.props.disableDocBrushing}
-                bringToFront={returnFalse}
-                scriptContext={this}
-            />
+            <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
+                <DocumentView
+                    key={this.doc[Id]}
+                    ref={action((r: DocumentView | null) => (this._dref = r))}
+                    Document={this.doc}
+                    DataDoc={undefined}
+                    PanelWidth={this.embeddedPanelWidth}
+                    PanelHeight={this.embeddedPanelHeight}
+                    LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
+                    LayoutTemplate={this.props.treeView.props.childLayoutTemplate}
+                    isContentActive={isActive}
+                    isDocumentActive={isActive}
+                    styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
+                    hideTitle={asText}
+                    //fitContentsToBox={returnTrue}
+                    hideDecorationTitle={this.props.treeView.outlineMode}
+                    hideResizeHandles={this.props.treeView.outlineMode}
+                    onClick={this.onChildClick}
+                    focus={this.refocus}
+                    onKey={this.onKeyDown}
+                    hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
+                    dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
+                    ScreenToLocalTransform={this.docTransform}
+                    renderDepth={this.props.renderDepth + 1}
+                    treeViewDoc={this.props.treeView?.props.Document}
+                    rootSelected={returnTrue}
+                    docViewPath={this.props.treeView.props.docViewPath}
+                    docFilters={returnEmptyFilter}
+                    docRangeFilters={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                    ContainingCollectionDoc={this.props.containerCollection}
+                    ContainingCollectionView={undefined}
+                    addDocument={this.props.addDocument}
+                    moveDocument={this.move}
+                    removeDocument={this.props.removeDoc}
+                    whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+                    xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)}
+                    yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)}
+                    addDocTab={this.props.addDocTab}
+                    pinToPres={this.props.treeView.props.pinToPres}
+                    disableDocBrushing={this.props.treeView.props.disableDocBrushing}
+                    bringToFront={returnFalse}
+                    scriptContext={this}
+                />
+            </div>
         );
     };
 
@@ -1148,7 +1131,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         }
 
         const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None));
-        const rowWidth = () => panelWidth() - treeBulletWidth();
+        const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1);
         const treeViewRefs = new Map<Doc, TreeView | undefined>();
         return docs
             .filter(child => child instanceof Doc)
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 559951efc..2f246e74f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1317,7 +1317,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 dontScaleFilter={this.props.dontScaleFilter}
                 dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
                 pointerEvents={this.pointerEvents}
-                jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
+                rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
                 //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
             />
         );
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 04c7b96e3..260c98816 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -25,7 +25,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     renderCutoffProvider: (doc: Doc) => boolean;
     zIndex?: number;
     highlight?: boolean;
-    jitterRotation: number;
+    rotation: number;
     dataTransition?: string;
     replica: string;
     CollectionFreeFormView: CollectionFreeFormView;
@@ -38,7 +38,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         { key: '_width' },
         { key: 'x' },
         { key: 'y' },
-        { key: '_jitterRotation', val: 0 },
+        { key: '_rotation', val: 0 },
         { key: '_scrollTop' },
         { key: 'opacity', val: 1 },
         { key: 'viewScale', val: 1 },
@@ -55,7 +55,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     get transform() {
-        return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
+        return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.rotation, this.props.rotation)}deg)`;
     }
     get X() {
         return this.dataProvider?.x ?? NumCast(this.Document.x);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fecbc2de2..5b26469ed 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -53,6 +53,7 @@ import { RadialMenu } from './RadialMenu';
 import { ScriptingBox } from './ScriptingBox';
 import { PresBox } from './trails/PresBox';
 import React = require('react');
+import { CollectionTreeView } from '../collections/CollectionTreeView';
 const { Howl } = require('howler');
 
 interface Window {
@@ -147,6 +148,8 @@ export interface DocumentViewSharedProps {
     pinToPres: (document: Doc) => void;
     ScreenToLocalTransform: () => Transform;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
+    xPadding?: number;
+    yPadding?: number;
     dropAction?: dropActionType;
     dontRegisterView?: boolean;
     hideLinkButton?: boolean;
@@ -1349,17 +1352,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             this.contents
         ) : (
             <div className="documentView-styleWrapper">
-                {!this.headerMargin ? (
-                    <>
-                        {' '}
-                        {this.contents} {titleView}{' '}
-                    </>
-                ) : (
-                    <>
-                        {' '}
-                        {titleView} {this.contents}{' '}
-                    </>
-                )}
+                {' '}
+                {!this.headerMargin ? this.contents : titleView}
+                {!this.headerMargin ? titleView : this.contents}
+                {' ' /* */}
                 {captionView}
             </div>
         );
@@ -1372,7 +1368,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         TraceMobx();
         const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
         const isButton = this.props.Document.type === DocumentType.FONTICON;
-        if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || (this.hidden && !this.props.treeViewDoc)) return null;
+        if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null;
         return (
             this.docContents ?? (
                 <div
@@ -1448,7 +1444,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     <>
                         {animRenderDoc}
                         <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
-                            <svg style={{ overflow: 'visible' }} viewBox={`0 0 ${this.props.PanelWidth()} ${this.props.PanelHeight()}`}>
+                            <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${this.props.PanelWidth()} ${this.props.PanelHeight()}`}>
                                 <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
                             </svg>
                         </div>
@@ -1552,7 +1548,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, !this.fitWidth));
     }
     @computed get shouldNotScale() {
-        return (this.fitWidth && !this.nativeWidth) || this.props.dontScaleFilter?.(this.Document) || this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any);
+        return (this.fitWidth && !this.nativeWidth) || this.props.dontScaleFilter?.(this.Document) || [CollectionViewType.Docking].includes(this.Document._viewType as any);
     }
     @computed get effectiveNativeWidth() {
         return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width);
@@ -1597,7 +1593,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight());
     focus = (doc: Doc, options: DocFocusOptions) => this.docView?.focus(doc, options);
     getBounds = () => {
-        if (!this.docView || !this.docView.ContentDiv || this.props.Document.presBox || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
+        if (!this.docView || !this.docView.ContentDiv || this.props.Document.presBox || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
             return undefined;
         }
         const xf = this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
@@ -1687,10 +1683,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     render() {
         TraceMobx();
-        const xshift = () => (Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
-        const yshift = () => (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;
+        const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined;
+        const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined;
+        const isButton = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
         return (
             <div className="contentFittingDocumentView">
                 {!this.props.Document || !this.props.PanelWidth() ? null : (
@@ -1700,11 +1695,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         style={{
                             transition: this.props.dataTransition,
                             transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
-                            width: isButton || isPresTreeElement ? '100%' : xshift() ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
+                            width: isButton ? '100%' : xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
                             height:
                                 isButton || this.props.forceAutoHeight
                                     ? undefined
-                                    : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`),
+                                    : yshift ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`),
                         }}>
                         <DocumentViewInternal
                             {...this.props}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 77aaa4441..e53422ab7 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -36,8 +36,6 @@ export interface FieldViewProps extends DocumentViewSharedProps {
     fontSize?: number;
     height?: number;
     width?: number;
-    xPadding?: number;
-    yPadding?: number;
     noSidebar?: boolean;
     dontScale?: boolean;
     dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field)
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index f7f558bb4..fb47bfc07 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -935,7 +935,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     className="videoBox-ui"
                     style={{
                         transformOrigin: 'top left',
-                        transform: `rotate(${NumCast(this.rootDoc.jitterRotation)}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
+                        transform: `rotate(${NumCast(this.rootDoc.rotation)}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
                         left: xPos,
                         top: yMid,
                         height: uiHeight,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 5470d6ba9..096f9a92c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1004,11 +1004,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             () => this.autoHeight,
             autoHeight => autoHeight && this.tryUpdateScrollHeight()
         );
+        this._disposers.width = reaction(
+            () => this.props.PanelWidth(),
+            width => this.tryUpdateScrollHeight()
+        );
         this._disposers.scrollHeight = reaction(
             () => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }),
-            ({ width, scrollHeight, autoHeight }) => {
-                width && autoHeight && this.resetNativeHeight(scrollHeight);
-            },
+            ({ width, scrollHeight, autoHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight),
             { fireImmediately: true }
         );
         this._disposers.componentHeights = reaction(
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 258dad39c..4e8aed8a6 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -429,7 +429,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             pinDoc.presPinLayout = true;
             pinDoc.presX = NumCast(targetDoc.x);
             pinDoc.presY = NumCast(targetDoc.y);
-            pinDoc.presRot = NumCast(targetDoc.jitterRotation);
+            pinDoc.presRot = NumCast(targetDoc.rotation);
             pinDoc.presWidth = NumCast(targetDoc.width);
             pinDoc.presHeight = NumCast(targetDoc.height);
         }
@@ -521,7 +521,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             targetDoc._dataTransition = presTransitionTime;
             targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x));
             targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y));
-            targetDoc.jitterRotation = NumCast(activeItem.presRot, NumCast(targetDoc.jitterRotation));
+            targetDoc.rotation = NumCast(activeItem.presRot, NumCast(targetDoc.rotation));
             targetDoc.width = NumCast(activeItem.presWidth, NumCast(targetDoc.width));
             targetDoc.height = NumCast(activeItem.presHeight, NumCast(targetDoc.height));
             setTimeout(() => (targetDoc._dataTransition = undefined), transTime + 10);
@@ -2652,9 +2652,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 PanelHeight={this.panelHeight}
                                 childIgnoreNativeSize={true}
                                 moveDocument={returnFalse}
-                                childFitWidth={returnTrue}
+                                //childFitWidth={returnTrue}
                                 childOpacity={returnOne}
                                 childLayoutTemplate={this.childLayoutTemplate}
+                                childXPadding={Doc.IsComicStyle(this.rootDoc) ? 20 : undefined}
                                 filterAddDocument={this.addDocumentFilter}
                                 removeDocument={returnFalse}
                                 dontRegisterView={true}
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
index 8a6c2a6dc..415253af1 100644
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresElementBox.scss
@@ -42,10 +42,7 @@ $slide-active: #5b9fdd;
     width: 100%;
     border-bottom: 0.5px solid grey;
     display: flex;
-    justify-content: space-between;
     align-items: center;
-    grid-template-rows: 16px 10px auto;
-    grid-template-columns: max-content max-content max-content max-content auto;
 
     .presItem-name {
         display: flex;
@@ -102,13 +99,7 @@ $slide-active: #5b9fdd;
         grid-row: 3;
         grid-column: 1/8;
         position: relative;
-        display: flex;
-        width: auto;
-        justify-content: center;
-        margin: auto;
-        margin-bottom: 2px;
-        border-bottom-left-radius: 5px;
-        border-bottom-right-radius: 5px;
+        display: inline-block;
     }
 
     .presItem-embeddedMask {
@@ -124,8 +115,7 @@ $slide-active: #5b9fdd;
 
     .presItem-slideButtons {
         display: flex;
-        grid-column: 7;
-        grid-row: 1/3;
+        position: absolute;
         width: max-content;
         justify-self: right;
         justify-content: flex-end;
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index f4ab845f3..5d14a0e9a 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -4,7 +4,9 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
+import { InkField } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
+import { RichTextField } from '../../../../fields/RichTextField';
 import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
 import { Docs, DocUtils } from '../../../documents/Documents';
@@ -14,6 +16,7 @@ import { DragManager } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
+import { MarqueeView } from '../../collections/collectionFreeForm';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
@@ -24,9 +27,6 @@ import { PresBox } from './PresBox';
 import './PresElementBox.scss';
 import { PresMovement } from './PresEnums';
 import React = require('react');
-import { InkField } from '../../../../fields/InkField';
-import { RichTextField } from '../../../../fields/RichTextField';
-import { MarqueeView } from '../../collections/collectionFreeForm';
 /**
  * This class models the view a document added to presentation will have in the presentation.
  * It involves some functionality for its buttons and options.
@@ -44,8 +44,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get indexInPres() {
         return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc);
     } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
+    @computed get expandViewHeight() {
+        return 100;
+    }
     @computed get collapsedHeight() {
-        return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(this.presBox._viewType as any) ? 35 : 31;
+        return 35;
     } // the collapsed height changes depending on the state of the presBox.  We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
     @computed get presStatus() {
         return this.presBox.presStatus;
@@ -58,7 +61,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as PresBox) : undefined;
     }
     @computed get presBox() {
-        return (this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc)!;
+        return this.props.ContainingCollectionDoc!;
     }
     @computed get targetDoc() {
         return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc;
@@ -67,8 +70,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     componentDidMount() {
         this.layoutDoc.hideLinkButton = true;
         this._heightDisposer = reaction(
-            () => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
-            params => (this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0)),
+            () => ({ expand: this.rootDoc.presExpandInlineButton, height: this.collapsedHeight }),
+            ({ expand, height }) => (this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0)),
             { fireImmediately: true }
         );
     }
@@ -84,10 +87,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     presExpandDocumentClick = () => (this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton);
 
-    embedHeight = (): number => 97;
+    embedHeight = (): number => this.collapsedHeight + this.expandViewHeight;
     // embedWidth = () => this.props.PanelWidth();
     // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
-    embedWidth = (): number => this.props.PanelWidth() - 35;
+    embedWidth = (): number => this.props.PanelWidth() / 2;
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (property === StyleProp.Opacity) return 1;
         return this.props.styleProvider?.(doc, props, property);
@@ -98,35 +101,35 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     @computed get renderEmbeddedInline() {
         return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? null : (
-            <div className="presItem-embedded" style={{ height: this.embedHeight(), width: this.embedWidth() }}>
+            <div className="presItem-embedded" style={{ height: this.embedHeight(), width: '50%' }}>
                 <DocumentView
                     Document={this.rootDoc}
                     DataDoc={undefined} //this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
+                    PanelWidth={this.embedWidth}
+                    PanelHeight={this.embedHeight}
+                    isContentActive={this.props.isContentActive}
                     styleProvider={this.styleProvider}
+                    hideLinkButton={true}
+                    ScreenToLocalTransform={Transform.Identity}
+                    renderDepth={this.props.renderDepth + 1}
                     docViewPath={returnEmptyDoclist}
+                    docFilters={this.props.docFilters}
+                    docRangeFilters={this.props.docRangeFilters}
+                    searchFilterDocs={this.props.searchFilterDocs}
+                    ContainingCollectionView={undefined}
+                    ContainingCollectionDoc={undefined}
                     rootSelected={returnTrue}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
-                    isContentActive={this.props.isContentActive}
-                    addDocTab={returnFalse}
-                    pinToPres={returnFalse}
                     fitContentsToBox={returnTrue}
-                    PanelWidth={this.embedWidth}
-                    PanelHeight={this.embedHeight}
-                    ScreenToLocalTransform={Transform.Identity}
                     moveDocument={this.props.moveDocument!}
-                    renderDepth={this.props.renderDepth + 1}
                     focus={DocUtils.DefaultFocus}
                     whenChildContentsActiveChanged={returnFalse}
+                    addDocTab={returnFalse}
+                    pinToPres={returnFalse}
                     bringToFront={returnFalse}
-                    docFilters={this.props.docFilters}
-                    docRangeFilters={this.props.docRangeFilters}
-                    searchFilterDocs={this.props.searchFilterDocs}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
-                    hideLinkButton={true}
                 />
-                <div className="presItem-embeddedMask" />
+                {/* <div className="presItem-embeddedMask" /> */}
             </div>
         );
     }
@@ -206,8 +209,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const dragData = new DragManager.DocumentDragData(this.presBoxView?.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;
+        dragData.treeViewDoc = this.presBox._viewType === CollectionViewType.Tree ? this.props.ContainingCollectionDoc : undefined; // this.props.DocumentView?.()?.props.treeViewDoc;
+        dragData.moveDocument = this.props.moveDocument;
         const dragItem: HTMLElement[] = [];
         if (dragArray.length === 1) {
             const doc = this._itemRef.current || dragArray[0];
@@ -307,7 +310,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     updateCapturedContainerLayout = (targetDoc: Doc, activeItem: Doc) => {
         activeItem.presX = NumCast(targetDoc.x);
         activeItem.presY = NumCast(targetDoc.y);
-        activeItem.presRot = NumCast(targetDoc.jitterRotation);
+        activeItem.presRot = NumCast(targetDoc.rotation);
         activeItem.presWidth = NumCast(targetDoc.width);
         activeItem.presHeight = NumCast(targetDoc.height);
     };
@@ -464,16 +467,90 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return width;
     }
 
+    @computed get presButtons() {
+        const presBox: Doc = this.presBox; //presBox
+        const presBoxColor: string = StrCast(presBox._backgroundColor);
+        const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
+        const targetDoc: Doc = this.targetDoc;
+        const activeItem: Doc = this.rootDoc;
+
+        const items: JSX.Element[] = [];
+        if (activeItem.presPinLayout) {
+            items.push(
+                <Tooltip key="slide" title={<div className="dash-tooltip">Update captured doc layout</div>}>
+                    <div className="slideButton" onClick={() => this.updateCapturedContainerLayout(targetDoc, activeItem)} style={{ fontWeight: 700, display: 'flex' }}>
+                        L
+                    </div>
+                </Tooltip>
+            );
+        }
+        if (activeItem.presPinData || activeItem.presPinView) {
+            items.push(
+                <Tooltip key="flex" title={<div className="dash-tooltip">Update captured doc content</div>}>
+                    <div className="slideButton" onClick={() => this.updateCapturedViewContents(targetDoc, activeItem)} style={{ fontWeight: 700, display: 'flex' }}>
+                        C
+                    </div>
+                </Tooltip>
+            );
+        }
+        if (!Doc.noviceMode) {
+            items.push(
+                <Tooltip key="slash" title={<div className="dash-tooltip">{this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
+                    <div className="slideButton" onClick={e => (this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
+                        <FontAwesomeIcon icon={`video${this.recordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} />
+                    </div>
+                </Tooltip>
+            );
+        }
+        if (this.indexInPres === 0) {
+            items.push(
+                <Tooltip key="arrow" title={<div className="dash-tooltip">{activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}</div>}>
+                    <div
+                        className="slideButton"
+                        onClick={() => (activeItem.groupWithUp = !activeItem.groupWithUp)}
+                        style={{
+                            zIndex: 1000 - this.indexInPres,
+                            fontWeight: 700,
+                            backgroundColor: activeItem.groupWithUp ? (presColorBool ? presBoxColor : Colors.MEDIUM_BLUE) : undefined,
+                            height: activeItem.groupWithUp ? 53 : 18,
+                            transform: activeItem.groupWithUp ? 'translate(0, -17px)' : undefined,
+                        }}>
+                        <div style={{ transform: activeItem.groupWithUp ? 'rotate(180deg) translate(0, -17.5px)' : 'rotate(0deg)' }}>
+                            <FontAwesomeIcon icon={'arrow-up'} onPointerDown={e => e.stopPropagation()} />
+                        </div>
+                    </div>
+                </Tooltip>
+            );
+        }
+        items.push(
+            <Tooltip key="eye" title={<div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? 'Minimize' : 'Expand'}</div>}>
+                <div
+                    className="slideButton"
+                    onClick={e => {
+                        e.stopPropagation();
+                        this.presExpandDocumentClick();
+                    }}>
+                    <FontAwesomeIcon icon={this.rootDoc.presExpandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
+                </div>
+            </Tooltip>
+        );
+        items.push(
+            <Tooltip key="trash" title={<div className="dash-tooltip">Remove from presentation</div>}>
+                <div className={'slideButton'} onClick={this.removeItem}>
+                    <FontAwesomeIcon icon={'trash'} onPointerDown={e => e.stopPropagation()} />
+                </div>
+            </Tooltip>
+        );
+        return items;
+    }
+
     @computed get mainItem() {
         const isSelected: boolean = this.selectedArray?.has(this.rootDoc) ? true : false;
         const isCurrent: boolean = this.presBox._itemIndex === this.indexInPres;
-        const toolbarWidth: number = this.toolbarWidth;
-        const showMore: boolean = this.toolbarWidth >= 300;
         const miniView: boolean = this.toolbarWidth <= 110;
         const presBox: Doc = this.presBox; //presBox
         const presBoxColor: string = StrCast(presBox._backgroundColor);
         const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
-        const targetDoc: Doc = this.targetDoc;
         const activeItem: Doc = this.rootDoc;
 
         return (
@@ -484,6 +561,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 style={{
                     backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
                     opacity: this._dragging ? 0.3 : 1,
+                    paddingLeft: NumCast(this.layoutDoc._xPadding, this.props.xPadding),
+                    paddingRight: NumCast(this.layoutDoc._xPadding, this.props.xPadding),
+                    paddingTop: NumCast(this.layoutDoc._yPadding, this.props.yPadding),
+                    paddingBottom: NumCast(this.layoutDoc._yPadding, this.props.yPadding),
                 }}
                 onClick={e => {
                     e.stopPropagation();
@@ -507,6 +588,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         ref={this._dragRef}
                         className={`presItem-slide ${isCurrent ? 'active' : ''}`}
                         style={{
+                            display: 'infline-block',
                             backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
                             //boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
                             border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined,
@@ -514,8 +596,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <div
                             className="presItem-name"
                             style={{
+                                display: 'inline-flex',
                                 pointerEvents: isSelected ? undefined : 'none',
-                                maxWidth: showMore ? toolbarWidth - 195 : toolbarWidth - 105,
+                                width: `calc(100% ${this.rootDoc.presExpandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
                                 cursor: isSelected ? 'text' : 'grab',
                             }}>
                             <div>{`${this.indexInPres + 1}.  `}</div>
@@ -523,56 +606,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         </div>
                         {/* <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip> */}
                         {/* <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip> */}
-                        <div className="presItem-slideButtons">
-                            <Tooltip title={<div className="dash-tooltip">Update captured doc layout</div>}>
-                                <div className="slideButton" onClick={() => this.updateCapturedContainerLayout(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinLayout ? 'flex' : 'none' }}>
-                                    L
-                                </div>
-                            </Tooltip>
-                            <Tooltip title={<div className="dash-tooltip">Update captured doc content</div>}>
-                                <div className="slideButton" onClick={() => this.updateCapturedViewContents(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinData || activeItem.presPinView ? 'flex' : 'none' }}>
-                                    C
-                                </div>
-                            </Tooltip>
-                            {!Doc.noviceMode && (
-                                <Tooltip title={<div className="dash-tooltip">{this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
-                                    <div className="slideButton" onClick={e => (this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
-                                        <FontAwesomeIcon icon={`video${this.recordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} />
-                                    </div>
-                                </Tooltip>
-                            )}
-                            <Tooltip title={<div className="dash-tooltip">{activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}</div>}>
-                                <div
-                                    className="slideButton"
-                                    onClick={() => (activeItem.groupWithUp = !activeItem.groupWithUp)}
-                                    style={{
-                                        display: this.indexInPres === 0 ? 'none' : '',
-                                        zIndex: 1000 - this.indexInPres,
-                                        fontWeight: 700,
-                                        backgroundColor: activeItem.groupWithUp ? (presColorBool ? presBoxColor : Colors.MEDIUM_BLUE) : undefined,
-                                        height: activeItem.groupWithUp ? 53 : 18,
-                                        transform: activeItem.groupWithUp ? 'translate(0, -17px)' : undefined,
-                                    }}>
-                                    <div style={{ transform: activeItem.groupWithUp ? 'rotate(180deg) translate(0, -17.5px)' : 'rotate(0deg)' }}>
-                                        <FontAwesomeIcon icon={'arrow-up'} onPointerDown={e => e.stopPropagation()} />
-                                    </div>
-                                </div>
-                            </Tooltip>
-                            <Tooltip title={<div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? 'Minimize' : 'Expand'}</div>}>
-                                <div
-                                    className="slideButton"
-                                    onClick={e => {
-                                        e.stopPropagation();
-                                        this.presExpandDocumentClick();
-                                    }}>
-                                    <FontAwesomeIcon icon={this.rootDoc.presExpandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
-                                </div>
-                            </Tooltip>
-                            <Tooltip title={<div className="dash-tooltip">Remove from presentation</div>}>
-                                <div className={'slideButton'} onClick={this.removeItem}>
-                                    <FontAwesomeIcon icon={'trash'} onPointerDown={e => e.stopPropagation()} />
-                                </div>
-                            </Tooltip>
+                        <div className="presItem-slideButtons" style={{ position: 'absolute', right: 0 }}>
+                            {...this.presButtons}
                         </div>
                         {this.renderEmbeddedInline}
                     </div>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 466b43287..fc43325fe 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -238,6 +238,9 @@ export class Doc extends RefField {
         Doc.UserDoc().activePage = val;
         DocServer.UPDATE_SERVER_CACHE();
     }
+    public static IsComicStyle(doc?: Doc) {
+        return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic';
+    }
     public static get ActiveDashboard() {
         return DocCast(Doc.UserDoc().activeDashboard);
     }
-- 
cgit v1.2.3-70-g09d2


From 13002bb819e54f3e2f2d25c4b043abf1c15386bb Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 9 Nov 2022 12:22:04 -0500
Subject: fixed treeViews again to make room for hover buttons properly.  fixed
 copying text from pdf to highlight copied regions without always displaying
 anchor.

---
 src/client/util/CurrentUserUtils.ts                |  8 ++++----
 src/client/views/LightboxView.tsx                  |  2 +-
 .../views/collections/CollectionTreeView.scss      |  3 ++-
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/TreeView.scss         |  3 ++-
 src/client/views/collections/TreeView.tsx          | 24 ++++++++++++++--------
 src/client/views/pdf/Annotation.tsx                |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 ++
 src/fields/util.ts                                 |  1 -
 9 files changed, 29 insertions(+), 18 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 114dbac93..f2d851a40 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -214,7 +214,7 @@ export class CurrentUserUtils {
         title: string, toolTip: string, icon: string,  ignoreClick?: boolean, dragFactory?: Doc, 
         backgroundColor?: string,  clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string},
     }[] {
-        const standardOps = (key:string) => ({ title : "Untitled "+ key, _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List<string>(["system"]) });
+        const standardOps = (key:string) => ({ title : "Untitled "+ key, _fitWidth: false, system: true, "dragFactory-count": 0, cloneFieldFilter: new List<string>(["system"]) });
         const json = {
             doc: {
                 type: "doc",
@@ -260,7 +260,7 @@ export class CurrentUserUtils {
             {key: "Note",       creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _autoHeight: true }},
             {key: "Noteboard",  creator: opts => Docs.Create.NoteTakingDocument([], opts),  opts: { _width: 250, _height: 200 }},
             {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 150, _height: 100 }},
-            {key: "Equation",   creator: opts => Docs.Create.EquationDocument(opts),        opts: { _width: 300, _height: 35, _fitWidth:false, _backgroundGridShow: true, }},
+            {key: "Equation",   creator: opts => Docs.Create.EquationDocument(opts),        opts: { _width: 300, _height: 35, _backgroundGridShow: true, }},
             {key: "Webpage",    creator: opts => Docs.Create.WebDocument("",opts),          opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }},
             {key: "Comparison", creator: Docs.Create.ComparisonDocument,                    opts: { _width: 300, _height: 300 }},
             {key: "Audio",      creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }},
@@ -271,8 +271,8 @@ export class CurrentUserUtils {
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             // {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument(opts),         opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}},
-            {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true,  _chromeHidden: true, boxShadow: "0 0" }},
-            {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _backgroundGridShow: true, }},
+            {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true, _fitWidth:true, _chromeHidden: true, boxShadow: "0 0" }},
+            {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _fitWidth: true, _backgroundGridShow: true, }},
             {key: "Slide",      creator: opts => Docs.Create.TreeDocument([], opts),        opts: { _width: 300, _height: 200, _viewType: CollectionViewType.Tree, 
                                                                                                 treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, 
                                                                                                 allowOverlayDrop: true, treeViewType: TreeViewType.outline, 
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index a9fba3688..91773419a 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -292,7 +292,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                             PanelWidth={this.lightboxWidth}
                             PanelHeight={this.lightboxHeight}
                             LayoutTemplate={LightboxView.LightboxDocTemplate}
-                            isDocumentActive={returnTrue}
+                            isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected.
                             isContentActive={returnTrue}
                             styleProvider={DefaultStyleProvider}
                             ScreenToLocalTransform={this.lightboxScreenToLocal}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 3785b7d61..273b08247 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -67,7 +67,8 @@
     font-style: italic;
     font-size: 8pt;
     margin-left: 3px;
-    display: none;
+    opacity: 0;
+    pointer-events: none;
 }
 
 .collectionTreeView-contents {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 0ff89c5a7..1a265af4a 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -115,7 +115,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             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()) + 6;
             this.layoutDoc._autoHeightMargins = bodyHeight;
-            this.props.setHeight?.(bodyHeight + titleHeight);
+            !this.props.dontRegisterView && this.props.setHeight?.(bodyHeight + titleHeight);
         }
     };
     unobserveHeight = (ref: any) => {
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 83fee013a..7eab03e1d 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -173,7 +173,8 @@
 
 .treeView-header:hover {
     .collectionTreeView-keyHeader {
-        display: inherit;
+        opacity: unset;
+        pointer-events: unset;
     }
 
     .treeView-openRight {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index ac8562d5a..13cf64558 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -416,7 +416,6 @@ export class TreeView extends React.Component<TreeViewProps> {
             })()
         );
     };
-
     @computed get expandedField() {
         const ids: { [key: string]: string } = {};
         const rows: JSX.Element[] = [];
@@ -428,6 +427,8 @@ export class TreeView extends React.Component<TreeViewProps> {
             const contents = doc[key];
             let contentElement: (JSX.Element | null)[] | JSX.Element = [];
 
+            let leftOffset = observable({ width: 0 });
+            const expandedWidth = () => this.props.panelWidth() - leftOffset.width;
             if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) {
                 const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
                 const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
@@ -452,7 +453,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     this.titleStyleProvider,
                     this.props.ScreenToLocalTransform,
                     this.props.isContentActive,
-                    this.props.panelWidth,
+                    expandedWidth,
                     this.props.renderDepth,
                     this.props.treeViewHideHeaderFields,
                     [...this.props.renderedIds, doc[Id]],
@@ -483,15 +484,21 @@ export class TreeView extends React.Component<TreeViewProps> {
                 );
             }
             rows.push(
-                <div style={{ display: 'flex' }} key={key}>
-                    <span style={{ fontWeight: 'bold' }}>{key + ':'}</span>
-                    &nbsp;
+                <div style={{ display: 'flex', overflow: 'auto' }} key={key}>
+                    <span
+                        ref={action((r: any) => {
+                            if (r) leftOffset.width = r.getBoundingClientRect().width;
+                        })}
+                        style={{ fontWeight: 'bold' }}>
+                        {key + ':'}
+                        &nbsp;
+                    </span>
                     {contentElement}
                 </div>
             );
         }
         rows.push(
-            <div style={{ display: 'flex' }} key={'newKeyValue'}>
+            <div style={{ display: 'flex', overflow: 'auto' }} key={'newKeyValue'}>
                 <EditableView
                     key="editableView"
                     contents={'+key:value'}
@@ -692,6 +699,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         this.treeViewOpen = true;
     };
 
+    @observable headerEleWidth = 0;
     @computed get headerElements() {
         return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : (
             <>
@@ -822,7 +830,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         }
         return false;
     };
-    titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - 3 * treeBulletWidth();
+    titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth();
 
     return18 = () => 18;
     /**
@@ -923,7 +931,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     }}>
                     {view}
                 </div>
-                <div className="treeView-rightButtons">
+                <div className="treeView-rightButtons" ref={action((r: any) => r && (this.headerEleWidth = r.getBoundingClientRect().width))}>
                     {buttons} {/* hide and lock buttons */}
                     {this.headerElements}
                 </div>
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index ee418a02f..9af0949eb 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -22,7 +22,7 @@ interface IAnnotationProps extends FieldViewProps {
 export class Annotation extends React.Component<IAnnotationProps> {
     render() {
         return (
-            <div>
+            <div style={{ display: this.props.anno.textCopied && !Doc.isBrushedHighlightedDegree(this.props.anno) ? 'none' : undefined }}>
                 {DocListCast(this.props.anno.textInlineAnnotations).map(a => (
                     <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />
                 ))}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 7f99c30e3..6ff87ef9f 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -133,6 +133,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             e.clipboardData.setData('text/plain', this._selectionText);
             const anchor = this._getAnchor();
             if (anchor) {
+                anchor.textCopied = true;
                 e.clipboardData.setData('dash/pdfAnchor', anchor[Id]);
             }
             e.preventDefault();
@@ -473,6 +474,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})` }} ref={this._annotationLayer}>
                 {this.inlineTextAnnotations
                     .sort((a, b) => NumCast(a.y) - NumCast(b.y))
+                    .filter(anno => !anno.hidden)
                     .map(anno => (
                         <Annotation {...this.props} fieldKey={this.props.fieldKey + '-annotations'} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
                     ))}
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 4a62a6a1f..f222e4555 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -4,7 +4,6 @@ import { DocServer } from '../client/DocServer';
 import { CollectionViewType } from '../client/documents/DocumentTypes';
 import { SerializationHelper } from '../client/util/SerializationHelper';
 import { UndoManager } from '../client/util/UndoManager';
-import { CollectionDockingView } from '../client/views/collections/CollectionDockingView';
 import { returnZero } from '../Utils';
 import CursorField from './CursorField';
 import {
-- 
cgit v1.2.3-70-g09d2


From f303131b40fac4909ab2730b369ef3bdb8e00b1d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 21 Feb 2023 09:53:18 -0500
Subject: fixed explore mode zooming.  don't show doc decorations until you
 move outside of the document, fixed dragging radius button over webbox to
 still get pointer events. fixed selection text in coments for web boxes..
 fixed setting default background color for notes.  fixed mode buttons to
 trigger click behavior before double click behavior.  fixed events on nested
 text boxes that are linkAnchors (like text quotes in sidebar comments),

---
 src/Utils.ts                                       |  9 +++---
 src/client/documents/Documents.ts                  |  5 ++--
 src/client/util/CurrentUserUtils.ts                |  7 +++--
 src/client/views/DocumentDecorations.tsx           | 20 +++++++------
 src/client/views/Main.tsx                          |  5 ++--
 src/client/views/MarqueeAnnotator.tsx              |  2 +-
 src/client/views/SidebarAnnos.tsx                  | 25 ++++++++--------
 src/client/views/StyleProvider.tsx                 |  3 +-
 .../views/collections/CollectionStackingView.tsx   |  8 ++++--
 .../views/collections/CollectionTreeView.tsx       |  5 +++-
 src/client/views/collections/TreeView.tsx          | 33 +++++++++++++++++-----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  6 +++-
 src/client/views/nodes/DocumentView.tsx            |  6 ++--
 src/client/views/nodes/ImageBox.tsx                |  4 +--
 src/client/views/nodes/WebBox.tsx                  | 11 ++++++--
 src/client/views/nodes/button/FontIconBox.tsx      | 15 +++-------
 .../views/nodes/formattedText/DashFieldView.tsx    | 22 +++++++++++++--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 22 ++++++---------
 .../views/nodes/formattedText/RichTextMenu.tsx     | 21 ++++++++------
 src/client/views/topbar/TopBar.tsx                 |  2 +-
 20 files changed, 145 insertions(+), 86 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 9d3b9eb2b..22c8cb902 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -686,8 +686,9 @@ export function DashColor(color: string) {
     try {
         return color ? Color(color.toLowerCase()) : Color('transparent');
     } catch (e) {
-        console.log('COLOR error:', e);
-        return Color('red');
+        if (color.includes('gradient')) console.log("using color 'white' in place of :" + color);
+        else console.log('COLOR error:', e);
+        return Color('white');
     }
 }
 
@@ -816,7 +817,7 @@ export function setupMoveUpEvents(
             (target as any)._noClick = clickEvent(e, (target as any)._doubleTap);
         }
         document.removeEventListener('pointermove', _moveEvent);
-        document.removeEventListener('pointerup', _upEvent);
+        document.removeEventListener('pointerup', _upEvent, true);
     };
     const _clickEvent = (e: MouseEvent): void => {
         if ((target as any)._noClick) e.stopPropagation();
@@ -827,6 +828,6 @@ export function setupMoveUpEvents(
         e.preventDefault();
     }
     document.addEventListener('pointermove', _moveEvent);
-    document.addEventListener('pointerup', _upEvent);
+    document.addEventListener('pointerup', _upEvent, true);
     document.addEventListener('click', _clickEvent, true);
 }
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 3faa6e11d..debb11066 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -224,6 +224,7 @@ export class DocumentOptions {
     contextMenuScripts?: List<ScriptField>;
     contextMenuLabels?: List<string>;
     contextMenuIcons?: List<string>;
+    allowClickBeforeDoubleClick?: boolean; // whether a click function can fire before the timeout for a double click has expired
     dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel)
     description?: string; // added for links
     layout?: string | Doc; // default layout string for a document
@@ -583,7 +584,7 @@ export namespace Docs {
                 DocumentType.FONTICON,
                 {
                     layout: { view: FontIconBox, dataField: defaultDataKey },
-                    options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: '100%', links: '@links(self)' },
+                    options: { allowClickBeforeDoubleClick: true, hideLinkButton: true, _width: 40, _height: 40, borderRounding: '100%', links: '@links(self)' },
                 },
             ],
             [
@@ -1808,7 +1809,7 @@ export namespace DocUtils {
             _yMargin: noMargins ? 0 : undefined,
             annotationOn,
             docMaxAutoHeight: maxHeight,
-            backgroundColor: backgroundColor,
+            backgroundColor,
             _width: width || 200,
             _height: 35,
             x: x,
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index f678c8936..3d95cb947 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -612,6 +612,7 @@ export class CurrentUserUtils {
               btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
             { title: "Size",    toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton,               ignoreClick: true, scripts: {script:  '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions },
             { title: "Color",   toolTip: "Font color",           btnType: ButtonType.ColorButton,  icon: "font", ignoreClick: true, scripts: {script:  '{ return setFontColor(value, _readOnly_);}'}},
+            { title: "Highlight",toolTip:"Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", ignoreClick: true, scripts: {script:  '{ return setFontHighlight(value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
             { title: "Bold",    toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",                    scripts: {onClick: '{ return toggleBold(_readOnly_); }'} },
             { title: "Italic",  toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",                  scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} },
             { title: "Under",   toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",               scripts: {onClick: '{ return toggleUnderline(_readOnly_);}'} },
@@ -635,9 +636,9 @@ export class CurrentUserUtils {
             { title: "Write",  toolTip: "Write (Ctrl+Shift+P)",  btnType: ButtonType.ToggleButton, icon: "pen",                         scripts: {onClick:'{ return setActiveTool("write", false, _readOnly_);}'} },
             { title: "Eraser", toolTip: "Eraser (Ctrl+E)",       btnType: ButtonType.ToggleButton, icon: "eraser",                      scripts: {onClick:'{ return setActiveTool("eraser", false, _readOnly_);}' }},
             // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} },
-            { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",                      scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", true, _readOnly_);}`} },
-            { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",                      scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", true, _readOnly_);}`} },
-            { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",                       scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", true, _readOnly_);}`} },
+            { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",           scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", true, _readOnly_);}`} },
+            { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",           scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", true, _readOnly_);}`} },
+            { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",            scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", true, _readOnly_);}`} },
             { title: "Mask",   toolTip: "Mask",                  btnType: ButtonType.ToggleButton, icon: "user-circle",                 scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} },
             { title: "Fill",   toolTip: "Fill color",            btnType: ButtonType.ColorButton,  icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} },
             { title: "Width",  toolTip: "Stroke width",          btnType: ButtonType.NumberButton,                   ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1},
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 41f4a17fb..21f63ada4 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { IconButton } from 'browndash-components';
-import { action, computed, observable, reaction } from 'mobx';
+import { action, computed, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { FaUndo } from 'react-icons/fa';
 import { DateField } from '../../fields/DateField';
@@ -182,7 +182,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         dragData.removeDocument = dragDocView.props.removeDocument;
         dragData.isDocDecorationMove = true;
         dragData.canEmbed = dragTitle;
-        this._hidden = this.Interacting = true;
+        this._hidden = true;
         DragManager.StartDocumentDrag(
             SelectionManager.Views().map(dv => dv.ContentDiv!),
             dragData,
@@ -191,7 +191,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             {
                 dragComplete: action(e => {
                     dragData.canEmbed && SelectionManager.DeselectAll();
-                    this._hidden = this.Interacting = false;
+                    this._hidden = false;
                 }),
                 hideSource: true,
             }
@@ -293,7 +293,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
      */
     @action
     onRadiusDown = (e: React.PointerEvent): void => {
-        this._isRounding = true;
+        this._isRounding = DocumentDecorations.Instance.Interacting = true;
         this._resizeUndo = UndoManager.StartBatch('DocDecs set radius');
         // Call util move event function
         setupMoveUpEvents(
@@ -316,10 +316,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 return false;
             }, // moveEvent
             action(e => {
-                this._isRounding = false;
+                DocumentDecorations.Instance.Interacting = this._isRounding = false;
                 this._resizeUndo?.end();
             }), // upEvent
-            e => {} // clickEvent
+            e => {}, // clickEvent,
+            true
         );
     };
 
@@ -710,11 +711,14 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         return this._rotCenter;
     }
 
+    @observable _showNothing = true;
+
     render() {
         const { b, r, x, y } = this.Bounds;
         const bounds = { b, r, x, y };
-        const seldocview = SelectionManager.Views().slice(-1)[0];
+        const seldocview = SelectionManager.Views().lastElement();
         if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
+            setTimeout(action(() => (this._showNothing = true)));
             return null;
         }
         // hide the decorations if the parent chooses to hide it or if the document itself hides it
@@ -812,7 +816,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             </div>
         );
         return (
-            <div className={`documentDecorations${colorScheme}`}>
+            <div className={`documentDecorations${colorScheme}`} style={{ opacity: this._showNothing ? 0.1 : undefined }} onPointerOver={action(e => (this._showNothing = false))}>
                 <div
                     className="documentDecorations-background"
                     style={{
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index e0b4c5159..6b18caed0 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -22,7 +22,8 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0 }; // bcz: not sure
 
 (async () => {
     MainView.Live = window.location.search.includes('live');
-    ReactDOM.createRoot(document.getElementById('root')!).render(<FieldLoader />);
+    const root = ReactDOM.createRoot(document.getElementById('root')!);
+    root.render(<FieldLoader />);
     window.location.search.includes('safe') && CollectionView.SetSafeMode(true);
     const info = await CurrentUserUtils.loadCurrentUser();
     if (info.email === 'guest') DocServer.Control.makeReadOnly();
@@ -47,6 +48,6 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0 }; // bcz: not sure
         new LinkManager();
         new TrackMovements();
         new ReplayMovements();
-        ReactDOM.createRoot(document.getElementById('root')!).render(<MainView />);
+        root.render(<MainView />);
     }, 0);
 })();
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 5ab91dd70..3bdf65d01 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -49,7 +49,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         super(props);
 
         AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('', true), true);
-        AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true));
+        AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true));
         AnchorMenu.Instance.OnAudio = unimplementedFunction;
         AnchorMenu.Instance.Highlight = this.highlight;
         AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 6d06bbbf6..b9af28413 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -88,6 +88,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                 };
             });
 
+        if (!anchor.text) Doc.GetProto(anchor).text = '-selection-';
         const textLines: any = [
             {
                 type: 'paragraph',
@@ -121,16 +122,18 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
             content: taggedContent,
         };
         if (taggedContent.length) textLines.push(metadatatext);
-        Doc.GetProto(target).text = new RichTextField(
-            JSON.stringify({
-                doc: {
-                    type: 'doc',
-                    content: textLines,
-                },
-                selection: { type: 'text', anchor: 4, head: 4 }, // set selection to middle paragraph
-            }),
-            ''
-        );
+        if (textLines.length) {
+            Doc.GetProto(target).text = new RichTextField(
+                JSON.stringify({
+                    doc: {
+                        type: 'doc',
+                        content: textLines,
+                    },
+                    selection: { type: 'text', anchor: 4, head: 4 }, // set selection to middle paragraph
+                }),
+                ''
+            );
+        }
         this.addDocument(target);
         setTimeout(() => this._stackRef.current?.focusDocument(target, {}));
         return target;
@@ -233,7 +236,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                         isAnnotationOverlay={false}
                         select={emptyFunction}
                         NativeDimScaling={returnOne}
-                        childShowTitle={this.showTitle}
+                        //childShowTitle={this.showTitle}
                         childDocumentsActive={this.props.isContentActive}
                         whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
                         childHideDecorationTitle={returnTrue}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3cb920ba0..817baeae6 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -19,6 +19,7 @@ import { SliderBox } from './nodes/SliderBox';
 import './StyleProvider.scss';
 import React = require('react');
 import { Shadows } from 'browndash-components';
+import { SelectionManager } from '../util/SelectionManager';
 
 export enum StyleProp {
     TreeViewIcon = 'treeViewIcon',
@@ -116,7 +117,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 const excludeTypes = [DocumentType.FONTICON];
                 let highlighting = !props?.disableDocBrushing && highlightIndex && !excludeTypes.includes(doc.type as any) && doc._viewType !== CollectionViewType.Linear; // bcz: hack to turn off highlighting onsidebar panel documents.  need to flag a document as not highlightable in a more direct way
                 if (highlighting && props?.focus !== emptyFunction && StrCast(doc.title) !== '[pres element template]') {
-                    return { highlightStyle, highlightColor, highlightIndex, highlightStroke: doc.type === DocumentType.INK };
+                    return { highlightStyle, highlightColor: SelectionManager.Views().some(dv => dv.rootDoc === doc) ? 'black' : highlightColor, highlightIndex, highlightStroke: doc.type === DocumentType.INK };
                 }
             }
             return undefined;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 2f495d55c..6314b4529 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -129,6 +129,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         TraceMobx();
         // appears that we are going to reset the _docXfs. TODO: what is Xfs?
         this._docXfs.length = 0;
+        this._renderCount < docs.length && setTimeout(action(() => (this._renderCount = Math.min(docs.length, this._renderCount + 5))));
         return docs.map((d, i) => {
             const height = () => this.getDocHeight(d);
             const width = () => this.getDocWidth(d);
@@ -139,7 +140,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             // So we're choosing whether we're going to render a column or a masonry doc
             return (
                 <div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}>
-                    {this.getDisplayDoc(d, width)}
+                    {this.getDisplayDoc(d, width, i)}
                 </div>
             );
         });
@@ -297,12 +298,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     };
     isContentActive = () => (this.props.isSelected() || this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined);
 
+    @observable _renderCount = 5;
     isChildContentActive = () =>
         this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined;
     isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined);
     // this is what renders the document that you see on the screen
     // called in Children: this actually adds a document to our children list
-    getDisplayDoc(doc: Doc, width: () => number) {
+    getDisplayDoc(doc: Doc, width: () => number, count: number) {
         const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
         const height = () => this.getDocHeight(doc);
 
@@ -310,7 +312,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         const stackedDocTransform = () => this.getDocTransform(doc, dref);
         this._docXfs.push({ stackedDocTransform, width, height });
         //DocumentView is how the node will be rendered
-        return (
+        return count > this._renderCount ? null : (
             <DocumentView
                 ref={r => (dref = r || undefined)}
                 Document={doc}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 1a265af4a..456f2a13d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -259,11 +259,13 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
     };
     headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields);
+    @observable _renderCount = 1;
     @computed get treeViewElements() {
         TraceMobx();
         const dropAction = StrCast(this.doc.childDropAction) as dropActionType;
         const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
         const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument?.(d, target, addDoc) || false;
+        if (this.treeChildren.length < this._renderCount) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
         return TreeView.GetChildElements(
             this.treeChildren,
             this,
@@ -296,7 +298,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             //TODO: [AL] add these
             this.props.AddToMap,
             this.props.RemFromMap,
-            this.props.hierarchyIndex
+            this.props.hierarchyIndex,
+            this._renderCount
         );
     }
     @computed get titleBar() {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 2398d8f58..0d2968ba1 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -32,6 +32,7 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
 import { CollectionView } from './CollectionView';
 import './TreeView.scss';
 import React = require('react');
+import { render } from 'react-dom';
 
 export interface TreeViewProps {
     treeView: CollectionTreeView;
@@ -95,7 +96,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     private _header: React.RefObject<HTMLDivElement> = React.createRef();
     private _tref = React.createRef<HTMLDivElement>();
     @observable _docRef: Opt<DocumentView>;
-    private _selDisposer: Opt<IReactionDisposer>;
+    private _disposers: { [name: string]: IReactionDisposer } = {};
     private _editTitleScript: (() => ScriptField) | undefined;
     private _openScript: (() => ScriptField) | undefined;
     private _treedropDisposer?: DragManager.DragDropDisposer;
@@ -212,14 +213,14 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
 
     @action setEditTitle = (docView?: DocumentView) => {
-        this._selDisposer?.();
+        this._disposers.selection?.();
         if (!docView) {
             this._editTitle = false;
         } else if (docView.isSelected()) {
             const doc = docView.Document;
             SelectionManager.SelectSchemaViewDoc(doc);
             this._editTitle = true;
-            this._selDisposer = reaction(
+            this._disposers.selection = reaction(
                 () => SelectionManager.SelectedSchemaDoc(),
                 seldoc => seldoc !== doc && this.setEditTitle(undefined)
             );
@@ -259,7 +260,8 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
 
     componentWillUnmount() {
-        this._selDisposer?.();
+        this._renderTimer && clearTimeout(this._renderTimer);
+        Object.values(this._disposers).forEach(disposer => disposer?.());
         this._treeEle && this.props.unobserveHeight(this._treeEle);
         document.removeEventListener('pointermove', this.onDragMove, true);
         document.removeEventListener('pointermove', this.onDragUp, true);
@@ -268,6 +270,10 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     componentDidUpdate() {
+        this._disposers.opening = reaction(
+            () => this.treeViewOpen,
+            open => !open && (this._renderCount = 20)
+        );
         this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
     }
 
@@ -512,6 +518,8 @@ export class TreeView extends React.Component<TreeViewProps> {
         return rows;
     }
 
+    _renderTimer: any;
+    @observable _renderCount = 1;
     @computed get renderContent() {
         TraceMobx();
         const expandKey = this.treeViewExpandedView;
@@ -543,6 +551,14 @@ export class TreeView extends React.Component<TreeViewProps> {
             const docs = expandKey === 'aliases' ? this.childAliases : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs;
             let downX = 0,
                 downY = 0;
+            if (docs?.length && this._renderCount < docs?.length) {
+                this._renderTimer && clearTimeout(this._renderTimer);
+                this._renderTimer = setTimeout(
+                    action(() => {
+                        this._renderCount = Math.min(docs!.length, this._renderCount + 20);
+                    })
+                );
+            }
             return (
                 <>
                     {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : (
@@ -599,7 +615,8 @@ export class TreeView extends React.Component<TreeViewProps> {
                                   // TODO: [AL] add these
                                   this.props.AddToMap,
                                   this.props.RemFromMap,
-                                  this.props.hierarchyIndex
+                                  this.props.hierarchyIndex,
+                                  this._renderCount
                               )}
                     </ul>
                 </>
@@ -651,7 +668,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return (
             <div
                 className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`}
-                key={'bullet'}
+                key="bullet"
                 title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : 'view fields'}
                 onClick={this.bulletClick}
                 style={
@@ -1131,7 +1148,8 @@ export class TreeView extends React.Component<TreeViewProps> {
         // TODO: [AL] add these
         AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
         RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
-        hierarchyIndex?: number[]
+        hierarchyIndex?: number[],
+        renderCount?: number
     ) {
         const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField);
         if (viewSpecScript) {
@@ -1144,6 +1162,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return docs
             .filter(child => child instanceof Doc)
             .map((child, i) => {
+                if (renderCount && i > renderCount) return null;
                 const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child);
                 if (!pair.layout || pair.data instanceof Promise) {
                     return null;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d6e95f97f..4d6e0dff2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -53,6 +53,7 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
 import React = require('react');
+import { DocumentDecorations } from '../../DocumentDecorations';
 
 export type collectionFreeformViewProps = {
     annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox)
@@ -1305,7 +1306,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     pointerEvents = () => {
         const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
         const pointerEvents =
-            this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
+            this.props.isContentActive() === false || DocumentDecorations.Instance.Interacting
+                ? 'none'
+                : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
         return pointerEvents;
     };
     getChildDocView(entry: PoolData) {
@@ -2287,6 +2290,7 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
     SelectionManager.DeselectAll();
     dv.props.focus(dv.props.Document, {
         willPanZoom: true,
+        zoomScale: 0.8,
         afterFocus: async didMove => {
             if (!didMove) {
                 const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 36c0240f1..3d89566ee 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -664,7 +664,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         ? this.props.select(false)
                         : '';
                 const clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click'));
-                if (this.onDoubleClickHandler) {
+                if (this.onDoubleClickHandler && !this.props.Document.allowClickBeforeDoubleClick) {
                     runInAction(() => (this._pendingDoubleClick = true));
                     this._timeout = setTimeout(() => {
                         this._timeout = undefined;
@@ -780,7 +780,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
             this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
         } else {
-            this._doubleTap = Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2;
+            const now = Date.now();
+            console.log(now - this._lastTap);
+            this._doubleTap = now - this._lastTap < 600 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2;
             // bcz: this is a placeholder.  documents, when selected, should stopPropagation on doubleClicks if they want to keep the DocumentView from getting them
             if (!this.props.isSelected(true) || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this.rootDoc.type) as any)) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
         }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 540958941..c66f2a67b 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -383,7 +383,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @computed get content() {
         TraceMobx();
 
-        const col = DashColor(this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor));
+        const backAlpha = DashColor(this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor)).alpha();
         const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
         const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
         const { nativeWidth, nativeHeight, nativeOrientation } = this.nativeSize;
@@ -403,7 +403,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
         return (
             <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}>
-                <div className="imageBox-fader" style={{ opacity: col.alpha() }}>
+                <div className="imageBox-fader" style={{ opacity: backAlpha }}>
                     <img key="paths" src={srcpath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
                     {fadepath === srcpath ? null : (
                         <div
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c392f3577..5bbe521a7 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -257,6 +257,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         // this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp);
     }
 
+    private _selectionText: string = '';
+    private _selectionContent: DocumentFragment | undefined;
+    selectionText = () => this._selectionText;
+    selectionContent = () => this._selectionContent;
     @action
     createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
         if (this._mainCont.current && selRange) {
@@ -276,6 +280,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             }
         }
         //this._selectionText = selRange.cloneContents().textContent || "";
+        this._selectionContent = selRange?.cloneContents();
+        this._selectionText = this._selectionContent?.textContent || '';
 
         // clear selection
         if (sel.empty) sel.empty(); // Chrome
@@ -854,8 +860,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return WebBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
     };
     @computed get content() {
-        const interactive =
-            !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
+        const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
         return (
             <div className={'webBox-cont' + (interactive ? '-interactive' : '')} onKeyDown={e => e.stopPropagation()} style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']) || `100%` : '100%' }}>
                 {this.urlContent}
@@ -1014,7 +1019,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                 docView={this.props.docViewPath().lastElement()}
                                 finishMarquee={this.finishMarquee}
                                 savedAnnotations={this.savedAnnotationsCreator}
-                                selectionText={returnEmptyString}
+                                selectionText={this.selectionText}
                                 annotationLayer={this._annotationLayer.current}
                                 mainCont={this._mainCont.current}
                             />
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 9d67283a0..b352f3790 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -136,7 +136,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ value, _readOnly_: false }), 'set num value');
 
         // Script for checking the outcome of the toggle
-        const checkResult: number = numScript?.script.run({ value: 0, _readOnly_: true }).result || 0;
+        const checkResult = Number(numScript?.script.run({ value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3));
 
         const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>;
 
@@ -150,7 +150,6 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                         max={NumCast(this.rootDoc.numBtnMax, 100)}
                         value={checkResult}
                         className={'menu-slider'}
-                        id="slider"
                         onPointerDown={() => (this._batch = UndoManager.StartBatch('presDuration'))}
                         onPointerUp={() => this._batch?.end()}
                         onChange={e => {
@@ -642,13 +641,7 @@ ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boo
     if (checkResult) {
         return (selected ?? Doc.UserDoc())._fontHighlight;
     }
-    if (selected) {
-        selected._fontColor = color;
-        if (color) {
-            editorView?.state && RichTextMenu.Instance.setHighlight(color, editorView, editorView?.dispatch);
-        }
-    }
-    Doc.UserDoc()._fontHighlight = color;
+    color && RichTextMenu.Instance.setHighlight(color);
 });
 
 // toggle: Set overlay status of selected document
@@ -795,7 +788,7 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean,
             GestureOverlay.Instance.KeepPrimitiveMode = keepPrim;
         }
         if (Object.values(GestureUtils.Gestures).includes(tool as any)) {
-            if (GestureOverlay.Instance.InkShape === tool) {
+            if (GestureOverlay.Instance.InkShape === tool && !keepPrim) {
                 Doc.ActiveTool = InkTool.None;
                 GestureOverlay.Instance.InkShape = undefined;
             } else {
@@ -804,7 +797,7 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean,
             }
         } else if (tool) {
             // pen or eraser
-            if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape) {
+            if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) {
                 Doc.ActiveTool = InkTool.None;
             } else {
                 Doc.ActiveTool = tool as any;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 39005a18b..b33529aeb 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -18,12 +18,18 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { NodeSelection } from 'prosemirror-state';
 import { OpenWhere } from '../DocumentView';
+import { FormattedTextBoxComment } from './FormattedTextBoxComment';
 
 export class DashFieldView {
     dom: HTMLDivElement; // container for label and value
     root: any;
+    node: any;
+    tbox: FormattedTextBox;
 
+    unclickable = () => !this.tbox.props.isSelected() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
     constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this.node = node;
+        this.tbox = tbox;
         this.dom = document.createElement('div');
         this.dom.style.width = node.attrs.width;
         this.dom.style.height = node.attrs.height;
@@ -44,7 +50,18 @@ export class DashFieldView {
 
         this.root = ReactDOM.createRoot(this.dom);
         this.root.render(
-            <DashFieldViewInternal node={node} getPos={getPos} fieldKey={node.attrs.fieldKey} docid={node.attrs.docid} width={node.attrs.width} height={node.attrs.height} hideKey={node.attrs.hideKey} editable={node.attrs.editable} tbox={tbox} />
+            <DashFieldViewInternal
+                node={node}
+                unclickable={this.unclickable}
+                getPos={getPos}
+                fieldKey={node.attrs.fieldKey}
+                docid={node.attrs.docid}
+                width={node.attrs.width}
+                height={node.attrs.height}
+                hideKey={node.attrs.hideKey}
+                editable={node.attrs.editable}
+                tbox={tbox}
+            />
         );
     }
     destroy() {
@@ -68,6 +85,7 @@ interface IDashFieldViewInternal {
     editable: boolean;
     node: any;
     getPos: any;
+    unclickable: () => boolean;
 }
 
 @observer
@@ -125,7 +143,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                 return (
                     <span
                         className="dashFieldView-fieldSpan"
-                        contentEditable={true}
+                        contentEditable={!this.props.unclickable()}
                         style={{ display: strVal.length < 2 ? 'inline-block' : undefined }}
                         suppressContentEditableWarning={true}
                         defaultValue={strVal}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 49acd8a57..0ff09a0ca 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -81,7 +81,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     static _highlightStyleSheet: any = addStyleSheet();
     static _bulletStyleSheet: any = addStyleSheet();
     static _userStyleSheet: any = addStyleSheet();
-    static _canAnnotate = true;
     static _hadSelection: boolean = false;
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
@@ -102,7 +101,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     private _rules: RichTextRules | undefined;
     private _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason.  This flags the 2nd click as happening on a selection which allows bullet points to toggle
     private _forceDownNode: Node | undefined;
-    private _downEvent: any;
     private _downX = 0;
     private _downY = 0;
     private _break = true;
@@ -118,7 +116,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     }
 
     @computed get noSidebar() {
-        return this.props.docViewPath?.()[this.props.docViewPath().length - 2]?.rootDoc.type === DocumentType.RTF || this.props.noSidebar || this.Document._noSidebar;
+        return this.props.docViewPath().lastElement()?.props.hideDecorationTitle || this.props.noSidebar || this.Document._noSidebar;
     }
     @computed get sidebarWidthPercent() {
         return this._showSidebar ? '20%' : StrCast(this.layoutDoc._sidebarWidthPercent, '0%');
@@ -254,7 +252,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             });
         };
         AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => {
-            this._editorView?.state && RichTextMenu.Instance.setHighlight(color, this._editorView, this._editorView?.dispatch);
+            this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
             return undefined;
         });
         AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
@@ -788,7 +786,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         );
 
         const uicontrols: ContextMenuProps[] = [];
-        !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, event: () => (FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate), icon: 'expand-arrows-alt' });
         uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: 'expand-arrows-alt' });
         uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
         !Doc.noviceMode &&
@@ -1455,7 +1452,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
         this._downX = e.clientX;
         this._downY = e.clientY;
-        this._downEvent = true;
         FormattedTextBoxComment.textBox = this;
         if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) {
             if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
@@ -1477,17 +1473,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         document.removeEventListener('pointermove', this.onSelectMove);
     };
     onPointerUp = (e: React.PointerEvent): void => {
-        if (!this._editorView?.state.selection.empty && !(this._editorView?.state.selection instanceof NodeSelection) && FormattedTextBox._canAnnotate && !(e.nativeEvent as any).dash) this.setupAnchorMenu();
-        if (!this._downEvent) return;
-        this._downEvent = false;
-        if (this._editorView?.state.selection.empty && this.props.isContentActive(true) && !(e.nativeEvent as any).dash) {
-            const editor = this._editorView!;
+        const editor = this._editorView!;
+        const state = editor?.state;
+        if (!state || !editor) return;
+        if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
+        else if (this.props.isContentActive(true)) {
             const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
-            !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
+            !this.props.isSelected(true) && editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(pcords?.pos || 0))));
             let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
             while (target && !target.dataset?.targethrefs) target = target.parentElement;
             FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
-            if (pcords && pcords.inside > 0 && this._editorView.state.doc.nodeAt(pcords.inside)?.type === this._editorView.state.schema.nodes.dashDoc) {
+            if (pcords && pcords.inside > 0 && state.doc.nodeAt(pcords.inside)?.type === state.schema.nodes.dashDoc) {
                 return;
             }
         }
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index b70da2e5e..f0caa1f4f 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -54,7 +54,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     @observable private _activeFontColor: string = 'black';
     @observable private showColorDropdown: boolean = false;
 
-    @observable private activeHighlightColor: string = 'transparent';
+    @observable private _activeHighlightColor: string = 'transparent';
     @observable private showHighlightDropdown: boolean = false;
 
     @observable private currentLink: string | undefined = '';
@@ -89,6 +89,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     @computed get fontColor() {
         return this._activeFontColor;
     }
+    @computed get fontHighlight() {
+        return this._activeHighlightColor;
+    }
     @computed get fontFamily() {
         return this._activeFontFamily;
     }
@@ -138,7 +141,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document.fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
         this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0];
         this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(Doc.UserDoc().fontColor, 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
-        this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
+        this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
 
         // update link in current selection
         this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle));
@@ -356,11 +359,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         this.updateMenu(this.view, undefined, this.props);
     };
 
-    setHighlight(color: String, view: EditorView, dispatch: any) {
-        const highlightMark = view.state.schema.mark(view.state.schema.marks.marker, { highlight: color });
-        if (view.state.selection.empty) return false;
-        view.focus();
-        this.setMark(highlightMark, view.state, dispatch, false);
+    setHighlight(color: string) {
+        if (this.view) {
+            const highlightMark = this.view.state.schema.mark(this.view.state.schema.marks.marker, { highlight: color });
+            this.setMark(highlightMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(highlightMark)), true);
+            this.view.focus();
+        } else Doc.UserDoc()._fontHighlight = color;
+        this.updateMenu(this.view, undefined, this.props);
     }
 
     setColor(color: string) {
@@ -563,7 +568,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     }
 
     @action setActiveHighlight(color: string) {
-        this.activeHighlightColor = color;
+        this._activeHighlightColor = color;
     }
 
     @action setCurrentLink(link: string) {
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 7e728306c..f2e9be61d 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -54,7 +54,7 @@ export class TopBar extends React.Component {
                         <span style={{ color: Colors.LIGHT_BLUE, fontWeight: 500 }}>dash</span>
                     </div>
                 )}
-                {Doc.ActiveDashboard && !Doc.noviceMode && (
+                {Doc.ActiveDashboard && (
                     <Button
                         text="Explore"
                         tooltip="Browsing mode for directly navigating to documents"
-- 
cgit v1.2.3-70-g09d2


From ea4588185f165f66a4d596d51a8a8ad955985762 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 22 Feb 2023 15:58:20 -0500
Subject: fixed pres hide before/after, select w/o nav, and updating contents..
 made collections fitWidth by default so that they resize like a normal
 window. Made freeform pan/zoom animate when changing keyframes.  allow
 nesting of keyframe collections..  made presEffects work even if there's no
 transition movement. made nested collection timelines follow parent by
 default, but with ability to change ordering.  fixed treeView incremental
 render.  treat doc anchors are preselements instead of delegating. don't
 highlight ink when dragging ctrl pts.

---
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/views/InkControlPtHandles.tsx           | 268 +++++++++++----------
 src/client/views/InkTangentHandles.tsx             | 114 +++++----
 src/client/views/InkingStroke.tsx                  |  24 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/PropertiesView.tsx                |  14 +-
 src/client/views/collections/CollectionMenu.tsx    |   9 +-
 .../views/collections/CollectionTreeView.tsx       |   2 +-
 src/client/views/collections/TabDocView.tsx        |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  54 ++++-
 .../collections/collectionFreeForm/MarqueeView.tsx |   1 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  28 +--
 src/client/views/nodes/DocumentView.tsx            |  16 +-
 src/client/views/nodes/button/FontIconBox.tsx      |   2 +-
 src/client/views/nodes/trails/PresBox.tsx          |  38 +--
 src/client/views/nodes/trails/PresElementBox.tsx   | 118 +++------
 src/fields/util.ts                                 |   1 +
 17 files changed, 345 insertions(+), 352 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 3d95cb947..67c730089 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -258,7 +258,7 @@ export class CurrentUserUtils {
               }[] = [
             {key: "Note",       creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _autoHeight: true }},
             {key: "Noteboard",  creator: opts => Docs.Create.NoteTakingDocument([], opts),  opts: { _width: 250, _height: 200 }},
-            {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 150, _height: 100 }},
+            {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 150, _height: 100, _fitWidth: true }},
             {key: "Equation",   creator: opts => Docs.Create.EquationDocument(opts),        opts: { _width: 300, _height: 35, _backgroundGridShow: true, }},
             {key: "Webpage",    creator: opts => Docs.Create.WebDocument("",opts),          opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }},
             {key: "Comparison", creator: Docs.Create.ComparisonDocument,                    opts: { _width: 300, _height: 300 }},
@@ -669,7 +669,7 @@ export class CurrentUserUtils {
               title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                    ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
             { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'},                        width: 20,  scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}},
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
-            { title: "Num",icon: "",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton,          funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { script: '{ return curKeyFrame(_readOnly_);}'}},
+            { title: "Num",icon: "",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton,          funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
             { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true, width: 20,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
             { title: "Header",  icon: "heading",       toolTip: "Header Color",         btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'}},
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index d036a636a..9447b2e72 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -1,23 +1,23 @@
-import React = require("react");
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../fields/Doc";
-import { ControlPoint, InkData, PointData, InkField } from "../../fields/InkField";
-import { List } from "../../fields/List";
-import { listSpec } from "../../fields/Schema";
-import { Cast, NumCast } from "../../fields/Types";
-import { setupMoveUpEvents, returnFalse } from "../../Utils";
-import { Transform } from "../util/Transform";
-import { UndoManager } from "../util/UndoManager";
-import { Colors } from "./global/globalEnums";
-import { InkingStroke } from "./InkingStroke";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { DocumentView } from "./nodes/DocumentView";
-import { SelectionManager } from "../util/SelectionManager";
+import React = require('react');
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../fields/Doc';
+import { ControlPoint, InkData, PointData, InkField } from '../../fields/InkField';
+import { List } from '../../fields/List';
+import { listSpec } from '../../fields/Schema';
+import { Cast, NumCast } from '../../fields/Types';
+import { setupMoveUpEvents, returnFalse } from '../../Utils';
+import { Transform } from '../util/Transform';
+import { UndoManager } from '../util/UndoManager';
+import { Colors } from './global/globalEnums';
+import { InkingStroke } from './InkingStroke';
+import { InkStrokeProperties } from './InkStrokeProperties';
+import { DocumentView } from './nodes/DocumentView';
+import { SelectionManager } from '../util/SelectionManager';
 
 export interface InkControlProps {
     inkDoc: Doc;
-    inkView: DocumentView;
+    inkView: InkingStroke;
     inkCtrlPoints: InkData;
     screenCtrlPoints: InkData;
     screenSpaceLineWidth: number;
@@ -26,16 +26,16 @@ export interface InkControlProps {
 
 @observer
 export class InkControlPtHandles extends React.Component<InkControlProps> {
-
     @observable private _overControl = -1;
-
-    @observable controlUndo: UndoManager.Batch | undefined;
+    get docView() {
+        return this.props.inkView.props.docViewPath().lastElement();
+    }
 
     componentDidMount() {
-        document.addEventListener("keydown", this.onDelete, true);
+        document.addEventListener('keydown', this.onDelete, true);
     }
     componentWillUnmount() {
-        document.removeEventListener("keydown", this.onDelete, true);
+        document.removeEventListener('keydown', this.onDelete, true);
     }
     /**
      * Handles the movement of a selected control point when the user clicks and drags.
@@ -43,30 +43,32 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
      */
     @action
     onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
-        const ptFromScreen = this.props.inkView.ComponentView?.ptFromScreen;
+        const ptFromScreen = this.props.inkView.ptFromScreen;
         if (ptFromScreen) {
             const order = controlIndex % 4;
             const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length;
             const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length;
-            const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"));
+            const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'));
             const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
             if (!wasSelected) InkStrokeProperties.Instance._currentPoint = -1;
             const origInk = this.props.inkCtrlPoints.slice();
-            setupMoveUpEvents(this, e,
+            setupMoveUpEvents(
+                this,
+                e,
                 action((e: PointerEvent, down: number[], delta: number[]) => {
-                    if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
+                    if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
                     const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
                     const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
-                    InkStrokeProperties.Instance.moveControlPtHandle(this.props.inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk);
+                    InkStrokeProperties.Instance.moveControlPtHandle(this.docView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk);
                     return false;
                 }),
                 action(() => {
-                    if (this.controlUndo) {
-                        InkStrokeProperties.Instance.snapControl(this.props.inkView, controlIndex);
+                    if (this.props.inkView.controlUndo) {
+                        InkStrokeProperties.Instance.snapControl(this.docView, controlIndex);
                     }
-                    this.controlUndo?.end();
-                    this.controlUndo = undefined;
-                    UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
+                    this.props.inkView.controlUndo?.end();
+                    this.props.inkView.controlUndo = undefined;
+                    UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
                 }),
                 action((e: PointerEvent, doubleTap: boolean | undefined) => {
                     const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
@@ -76,44 +78,52 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
                             else this.props.inkDoc.brokenInkIndices = new List<number>([controlIndex]);
                         } else {
                             if (brokenIndices?.includes(equivIndex)) {
-                                if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth");
-                                InkStrokeProperties.Instance.snapHandleTangent(this.props.inkView, equivIndex, handleIndexA, handleIndexB);
+                                if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
+                                InkStrokeProperties.Instance.snapHandleTangent(this.docView, equivIndex, handleIndexA, handleIndexB);
                             }
                             if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
-                                if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth");
-                                InkStrokeProperties.Instance.snapHandleTangent(this.props.inkView, controlIndex, handleIndexA, handleIndexB);
+                                if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
+                                InkStrokeProperties.Instance.snapHandleTangent(this.docView, controlIndex, handleIndexA, handleIndexB);
                             }
                         }
-                        this.controlUndo?.end();
-                        this.controlUndo = undefined;
+                        this.props.inkView.controlUndo?.end();
+                        this.props.inkView.controlUndo = undefined;
                     }
                     this.changeCurrPoint(controlIndex);
-                }), undefined, undefined, () => wasSelected && this.changeCurrPoint(-1));
+                }),
+                undefined,
+                undefined,
+                () => wasSelected && this.changeCurrPoint(-1)
+            );
         }
-    }
+    };
     /**
      * Updates whether a user has hovered over a particular control point or point that could be added
      * on click.
      */
-    @action onEnterControl = (i: number) => { this._overControl = i; };
-    @action onLeaveControl = () => { this._overControl = -1; };
+    @action onEnterControl = (i: number) => {
+        this._overControl = i;
+    };
+    @action onLeaveControl = () => {
+        this._overControl = -1;
+    };
 
     /**
      * Deletes the currently selected point.
      */
     @action
     onDelete = (e: KeyboardEvent) => {
-        if (["-", "Backspace", "Delete"].includes(e.key)) {
-            InkStrokeProperties.Instance.deletePoints(this.props.inkView, e.shiftKey);
+        if (['-', 'Backspace', 'Delete'].includes(e.key)) {
+            InkStrokeProperties.Instance.deletePoints(this.docView, e.shiftKey);
             e.stopPropagation();
         }
-    }
+    };
 
     /**
      * Changes the current selected control point.
      */
     @action
-    changeCurrPoint = (i: number) => InkStrokeProperties.Instance._currentPoint = i
+    changeCurrPoint = (i: number) => (InkStrokeProperties.Instance._currentPoint = i);
 
     render() {
         // Accessing the current ink's data and extracting all control points.
@@ -133,101 +143,115 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
 
         const closed = InkingStroke.IsClosed(inkData);
         const nearestScreenPt = this.props.nearestScreenPt();
-        const TagType = (broken?: boolean) => broken ? "rect" : "circle";
-        const hdl = (control: { X: number, Y: number, I: number }, scale: number, color: string) => {
-            const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"))?.includes(control.I);
+        const TagType = (broken?: boolean) => (broken ? 'rect' : 'circle');
+        const hdl = (control: { X: number; Y: number; I: number }, scale: number, color: string) => {
+            const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'))?.includes(control.I);
             const Tag = TagType((control.I === 0 || control.I === inkData.length - 1) && !closed) as keyof JSX.IntrinsicElements;
-            return <Tag key={control.I.toString() + scale}
-                x={control.X - this.props.screenSpaceLineWidth * 2 * scale}
-                y={control.Y - this.props.screenSpaceLineWidth * 2 * scale}
-                cx={control.X}
-                cy={control.Y}
-                r={this.props.screenSpaceLineWidth * 2 * scale}
-                opacity={this.controlUndo ? 0.15 : 1}
-                height={this.props.screenSpaceLineWidth * 4 * scale}
-                width={this.props.screenSpaceLineWidth * 4 * scale}
-                strokeWidth={this.props.screenSpaceLineWidth / 2}
-                stroke={Colors.MEDIUM_BLUE}
-                fill={broken ? Colors.MEDIUM_BLUE : color}
-                onPointerDown={(e: React.PointerEvent) => this.onControlDown(e, control.I)}
-                onMouseEnter={() => this.onEnterControl(control.I)}
-                onMouseLeave={this.onLeaveControl}
-                pointerEvents="all"
-                cursor="default"
-            />;
-        };
-        return (<svg>
-            {!nearestScreenPt ? (null) :
-                <circle key={"npt"}
-                    cx={nearestScreenPt.X}
-                    cy={nearestScreenPt.Y}
-                    r={this.props.screenSpaceLineWidth * 2}
-                    fill={"#00007777"}
-                    stroke={"#00007777"}
-                    strokeWidth={0}
-                    pointerEvents="none"
+            return (
+                <Tag
+                    key={control.I.toString() + scale}
+                    x={control.X - this.props.screenSpaceLineWidth * 2 * scale}
+                    y={control.Y - this.props.screenSpaceLineWidth * 2 * scale}
+                    cx={control.X}
+                    cy={control.Y}
+                    r={this.props.screenSpaceLineWidth * 2 * scale}
+                    opacity={this.props.inkView.controlUndo ? 0.15 : 1}
+                    height={this.props.screenSpaceLineWidth * 4 * scale}
+                    width={this.props.screenSpaceLineWidth * 4 * scale}
+                    strokeWidth={this.props.screenSpaceLineWidth / 2}
+                    stroke={Colors.MEDIUM_BLUE}
+                    fill={broken ? Colors.MEDIUM_BLUE : color}
+                    onPointerDown={(e: React.PointerEvent) => this.onControlDown(e, control.I)}
+                    onMouseEnter={() => this.onEnterControl(control.I)}
+                    onMouseLeave={this.onLeaveControl}
+                    pointerEvents="all"
+                    cursor="default"
                 />
-            }
-            {sreenCtrlPoints.map(control => hdl(control, this._overControl !== control.I ? 1 : 3 / 2, Colors.WHITE))}
-        </svg>
+            );
+        };
+        return (
+            <svg>
+                {!nearestScreenPt ? null : <circle key={'npt'} cx={nearestScreenPt.X} cy={nearestScreenPt.Y} r={this.props.screenSpaceLineWidth * 2} fill={'#00007777'} stroke={'#00007777'} strokeWidth={0} pointerEvents="none" />}
+                {sreenCtrlPoints.map(control => hdl(control, this._overControl !== control.I ? 1 : 3 / 2, Colors.WHITE))}
+            </svg>
         );
     }
 }
 
-
 export interface InkEndProps {
     inkDoc: Doc;
-    inkView: DocumentView;
+    inkView: InkingStroke;
     screenSpaceLineWidth: number;
     startPt: PointData;
     endPt: PointData;
 }
 @observer
 export class InkEndPtHandles extends React.Component<InkEndProps> {
-    @observable controlUndo: UndoManager.Batch | undefined;
     @observable _overStart: boolean = false;
     @observable _overEnd: boolean = false;
 
     @action
-    dragRotate = (e: React.PointerEvent, p1: () => { X: number, Y: number }, p2: () => { X: number, Y: number }) => {
-        setupMoveUpEvents(this, e, (e) => {
-            if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("stretch ink");
-            // compute stretch factor by finding scaling along axis between start and end points
-            const v1 = { X: p1().X - p2().X, Y: p1().Y - p2().Y };
-            const v2 = { X: e.clientX - p2().X, Y: e.clientY - p2().Y };
-            const v1len = Math.sqrt(v1.X * v1.X + v1.Y * v1.Y);
-            const v2len = Math.sqrt(v2.X * v2.X + v2.Y * v2.Y);
-            const scaling = v2len / v1len;
-            const v1n = { X: v1.X / v1len, Y: v1.Y / v1len };
-            const v2n = { X: v2.X / v2len, Y: v2.Y / v2len };
-            const angle = Math.acos(v1n.X * v2n.X + v1n.Y * v2n.Y) * Math.sign(v1.X * v2.Y - v2.X * v1.Y);
-            InkStrokeProperties.Instance.stretchInk(SelectionManager.Views(), scaling, p2(), v1n, e.shiftKey);
-            InkStrokeProperties.Instance.rotateInk(SelectionManager.Views(), angle, p2());
-            return false;
-        }, action(() => {
-            this.controlUndo?.end();
-            this.controlUndo = undefined;
-            UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
-        }), returnFalse);
-    }
+    dragRotate = (e: React.PointerEvent, p1: () => { X: number; Y: number }, p2: () => { X: number; Y: number }) => {
+        setupMoveUpEvents(
+            this,
+            e,
+            action(e => {
+                if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
+                // compute stretch factor by finding scaling along axis between start and end points
+                const v1 = { X: p1().X - p2().X, Y: p1().Y - p2().Y };
+                const v2 = { X: e.clientX - p2().X, Y: e.clientY - p2().Y };
+                const v1len = Math.sqrt(v1.X * v1.X + v1.Y * v1.Y);
+                const v2len = Math.sqrt(v2.X * v2.X + v2.Y * v2.Y);
+                const scaling = v2len / v1len;
+                const v1n = { X: v1.X / v1len, Y: v1.Y / v1len };
+                const v2n = { X: v2.X / v2len, Y: v2.Y / v2len };
+                const angle = Math.acos(v1n.X * v2n.X + v1n.Y * v2n.Y) * Math.sign(v1.X * v2.Y - v2.X * v1.Y);
+                InkStrokeProperties.Instance.stretchInk(SelectionManager.Views(), scaling, p2(), v1n, e.shiftKey);
+                InkStrokeProperties.Instance.rotateInk(SelectionManager.Views(), angle, p2());
+                return false;
+            }),
+            action(() => {
+                this.props.inkView.controlUndo?.end();
+                this.props.inkView.controlUndo = undefined;
+                UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
+            }),
+            returnFalse
+        );
+    };
 
     render() {
-        const hdl = (key: string, pt: PointData, dragFunc: (e: React.PointerEvent) => void) => <circle key={key}
-            cx={pt.X}
-            cy={pt.Y}
-            r={this.props.screenSpaceLineWidth * 2}
-            fill={this._overStart ? "#aaaaaa" : "#99999977"}
-            stroke={"#00007777"}
-            strokeWidth={0}
-            onPointerLeave={action(() => this._overStart = false)}
-            onPointerEnter={action(() => this._overStart = true)}
-            onPointerDown={dragFunc}
-            pointerEvents="all"
-        />;
-        return (<svg>
-            {hdl("start", this.props.startPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.startPt, () => this.props.endPt))}
-            {hdl("end", this.props.endPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.endPt, () => this.props.startPt))}
-        </svg>
+        const hdl = (key: string, pt: PointData, dragFunc: (e: React.PointerEvent) => void) => (
+            <circle
+                key={key}
+                cx={pt.X}
+                cy={pt.Y}
+                r={this.props.screenSpaceLineWidth * 2}
+                fill={this._overStart ? '#aaaaaa' : '#99999977'}
+                stroke={'#00007777'}
+                strokeWidth={0}
+                onPointerLeave={action(() => (this._overStart = false))}
+                onPointerEnter={action(() => (this._overStart = true))}
+                onPointerDown={dragFunc}
+                pointerEvents="all"
+            />
+        );
+        return (
+            <svg>
+                {hdl('start', this.props.startPt, (e: React.PointerEvent) =>
+                    this.dragRotate(
+                        e,
+                        () => this.props.startPt,
+                        () => this.props.endPt
+                    )
+                )}
+                {hdl('end', this.props.endPt, (e: React.PointerEvent) =>
+                    this.dragRotate(
+                        e,
+                        () => this.props.endPt,
+                        () => this.props.startPt
+                    )
+                )}
+            </svg>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index ae35bc980..c4a2f603e 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -1,21 +1,21 @@
-import React = require("react");
-import { action } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../fields/Doc";
-import { HandleLine, HandlePoint, InkData } from "../../fields/InkField";
-import { List } from "../../fields/List";
-import { listSpec } from "../../fields/Schema";
-import { Cast } from "../../fields/Types";
-import { emptyFunction, setupMoveUpEvents } from "../../Utils";
-import { Transform } from "../util/Transform";
-import { UndoManager } from "../util/UndoManager";
-import { Colors } from "./global/globalEnums";
-import { InkingStroke } from "./InkingStroke";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { DocumentView } from "./nodes/DocumentView";
+import React = require('react');
+import { action } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../fields/Doc';
+import { HandleLine, HandlePoint, InkData } from '../../fields/InkField';
+import { List } from '../../fields/List';
+import { listSpec } from '../../fields/Schema';
+import { Cast } from '../../fields/Types';
+import { emptyFunction, setupMoveUpEvents } from '../../Utils';
+import { Transform } from '../util/Transform';
+import { UndoManager } from '../util/UndoManager';
+import { Colors } from './global/globalEnums';
+import { InkingStroke } from './InkingStroke';
+import { InkStrokeProperties } from './InkStrokeProperties';
+import { DocumentView } from './nodes/DocumentView';
 export interface InkHandlesProps {
     inkDoc: Doc;
-    inkView: DocumentView;
+    inkView: InkingStroke;
     screenCtrlPoints: InkData;
     screenSpaceLineWidth: number;
     ScreenToLocalTransform: () => Transform;
@@ -23,49 +23,51 @@ export interface InkHandlesProps {
 
 @observer
 export class InkTangentHandles extends React.Component<InkHandlesProps> {
+    get docView() {
+        return this.props.inkView.props.docViewPath().lastElement();
+    }
     /**
      * Handles the movement of a selected handle point when the user clicks and drags.
      * @param handleNum The index of the currently selected handle point.
      */
     onHandleDown = (e: React.PointerEvent, handleIndex: number): void => {
-        const ptFromScreen = this.props.inkView.ComponentView?.ptFromScreen;
-        if (!ptFromScreen) return;
-        const screenScale = this.props.ScreenToLocalTransform().Scale;
-        var controlUndo: UndoManager.Batch | undefined;
         const order = handleIndex % 4;
         const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
         const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length;
         const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length;
-        setupMoveUpEvents(this, e,
+        setupMoveUpEvents(
+            this,
+            e,
             (e: PointerEvent, down: number[], delta: number[]) => {
-                if (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent");
+                if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('DocDecs move tangent');
                 if (e.altKey) this.onBreakTangent(controlIndex);
-                const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
-                const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
-                InkStrokeProperties.Instance.moveTangentHandle(this.props.inkView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex);
+                const inkMoveEnd = this.props.inkView.ptFromScreen({ X: delta[0], Y: delta[1] });
+                const inkMoveStart = this.props.inkView.ptFromScreen({ X: 0, Y: 0 });
+                InkStrokeProperties.Instance.moveTangentHandle(this.docView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex);
                 return false;
-            }, () => {
-                controlUndo?.end();
-                UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
-            }, emptyFunction
+            },
+            () => {
+                this.props.inkView.controlUndo?.end();
+                UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
+            },
+            emptyFunction
         );
-    }
+    };
 
     /**
-     * Breaks tangent handle movement when ‘Alt’ key is held down. Adds the current handle index and 
+     * Breaks tangent handle movement when ‘Alt’ key is held down. Adds the current handle index and
      * its matching (opposite) handle to a list of broken handle indices.
      * @param handleNum The index of the currently selected handle point.
      */
     @action
     onBreakTangent = (controlIndex: number) => {
         const closed = InkingStroke.IsClosed(this.props.screenCtrlPoints);
-        const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"));
-        if (!brokenIndices?.includes(controlIndex) &&
-            ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) {
+        const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'));
+        if (!brokenIndices?.includes(controlIndex) && ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) {
             if (brokenIndices) brokenIndices.push(controlIndex);
             else this.props.inkDoc.brokenInkIndices = new List<number>([controlIndex]);
         }
-    }
+    };
 
     render() {
         // Accessing the current ink's data and extracting all handle points and handle lines.
@@ -81,10 +83,18 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
             // Adding first and last (single) handle lines.
             if (closed) {
                 tangentLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 });
-            }
-            else {
+            } else {
                 tangentLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 });
-                tangentLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
+                tangentLines.push({
+                    X1: data[data.length - 2].X,
+                    Y1: data[data.length - 2].Y,
+                    X2: data[data.length - 1].X,
+                    Y2: data[data.length - 1].Y,
+                    X3: data[data.length - 1].X,
+                    Y3: data[data.length - 1].Y,
+                    dot1: data.length - 1,
+                    dot2: data.length - 1,
+                });
             }
             for (let i = 2; i < data.length - 4; i += 4) {
                 tangentLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
@@ -94,7 +104,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
 
         return (
             <>
-                {tangentHandles.map((pts, i) =>
+                {tangentHandles.map((pts, i) => (
                     <svg height="10" width="10" key={`hdl${i}`}>
                         <circle
                             cx={pts.X}
@@ -106,25 +116,31 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
                             onPointerDown={e => this.onHandleDown(e, pts.I)}
                             pointerEvents="all"
                             cursor="default"
-                            display={(pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint) ? "inherit" : "none"} />
-                    </svg>)}
+                            display={pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint ? 'inherit' : 'none'}
+                        />
+                    </svg>
+                ))}
                 {tangentLines.map((pts, i) => {
-                    const tangentLine = (x1: number, y1: number, x2: number, y2: number) =>
+                    const tangentLine = (x1: number, y1: number, x2: number, y2: number) => (
                         <line
                             x1={x1}
                             y1={y1}
                             x2={x2}
                             y2={y2}
                             stroke={Colors.MEDIUM_BLUE}
-                            strokeDasharray={"1 1"}
+                            strokeDasharray={'1 1'}
                             strokeWidth={1}
-                            display={(pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint) ? "inherit" : "none"} />;
-                    return <svg height="100" width="100" key={`line${i}`}>
-                        {tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)}
-                        {tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)}
-                    </svg>;
+                            display={pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint ? 'inherit' : 'none'}
+                        />
+                    );
+                    return (
+                        <svg height="100" width="100" key={`line${i}`}>
+                            {tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)}
+                            {tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)}
+                        </svg>
+                    );
                 })}
             </>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 8291ff3f1..76c1aa80a 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -124,6 +124,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     public static toggleMask = action((inkDoc: Doc) => {
         inkDoc.isInkMask = !inkDoc.isInkMask;
     });
+    @observable controlUndo: UndoManager.Batch | undefined;
     /**
      * Drags the a simple bezier segment of the stroke.
      * Also adds a control point when double clicking on the stroke.
@@ -143,15 +144,15 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
         const controlIndex = nearestSeg;
         const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
-        var controlUndo: UndoManager.Batch | undefined;
         const isEditing = InkStrokeProperties.Instance._controlButton && this.props.isSelected();
+        this.controlUndo = undefined;
         setupMoveUpEvents(
             this,
             e,
             !isEditing
                 ? returnFalse
                 : action((e: PointerEvent, down: number[], delta: number[]) => {
-                      if (!controlUndo) controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
+                      if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
                       const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] });
                       const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 });
                       InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
@@ -161,8 +162,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             !isEditing
                 ? returnFalse
                 : action(() => {
-                      controlUndo?.end();
-                      controlUndo = undefined;
+                      this.controlUndo?.end();
+                      this.controlUndo = undefined;
                       UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
                   }),
             action((e: PointerEvent, doubleTap: boolean | undefined) => {
@@ -304,7 +305,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         return SnappingManager.GetIsDragging() ? null : !InkStrokeProperties.Instance._controlButton ? (
             !this.props.isSelected() || InkingStroke.IsClosed(inkData) ? null : (
                 <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
-                    <InkEndPtHandles inkView={this.props.docViewPath().lastElement()} inkDoc={inkDoc} startPt={screenPts[0]} endPt={screenPts.lastElement()} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
+                    <InkEndPtHandles inkView={this} inkDoc={inkDoc} startPt={screenPts[0]} endPt={screenPts.lastElement()} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
                 </div>
             )
         ) : (
@@ -331,15 +332,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                     1.0,
                     false
                 )}
-                <InkControlPtHandles
-                    inkView={this.props.docViewPath().lastElement()}
-                    inkDoc={inkDoc}
-                    inkCtrlPoints={inkData}
-                    screenCtrlPoints={screenHdlPts}
-                    nearestScreenPt={this.nearestScreenPt}
-                    screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
-                />
-                <InkTangentHandles inkView={this.props.docViewPath().lastElement()} inkDoc={inkDoc} screenCtrlPoints={screenHdlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.screenToLocal} />
+                <InkControlPtHandles inkView={this} inkDoc={inkDoc} inkCtrlPoints={inkData} screenCtrlPoints={screenHdlPts} nearestScreenPt={this.nearestScreenPt} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
+                <InkTangentHandles inkView={this} inkDoc={inkDoc} screenCtrlPoints={screenHdlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.screenToLocal} />
             </div>
         );
     };
@@ -389,7 +383,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             1.0,
             false
         );
-        const highlight = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
+        const highlight = !this.controlUndo && this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
         const highlightIndex = highlight?.highlightIndex;
         const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent');
         // Invisible polygonal line that enables the ink to be selected by the user.
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 895ed9bda..625dc2748 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -606,7 +606,7 @@ export class MainView extends React.Component {
     @computed get mainDocView() {
         return (
             <>
-                {this._hideUI ? null : this.headerBarDocView}
+                {this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView}
                 <DocumentView
                     key="main"
                     Document={this.mainContainer!}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index a2bc37095..203b42cbc 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1814,12 +1814,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         </div>
                         {!selectedItem ? null : (
                             <div className="propertiesView-presTrails">
-                                <div
-                                    className="propertiesView-presTrails-title"
-                                    onPointerDown={action(() => {
-                                        this.openPresTransitions = !this.openPresTransitions;
-                                    })}
-                                    style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}>
+                                <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openPresTransitions = !this.openPresTransitions))} style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}>
                                     &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Transitions
                                     <div className="propertiesView-presTrails-title-icon">
                                         <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" color="white" />
@@ -1830,12 +1825,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         )}
                         {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
                             <div className="propertiesView-presTrails">
-                                <div
-                                    className="propertiesView-presTrails-title"
-                                    onPointerDown={action(() => {
-                                        this.openSlideOptions = !this.openSlideOptions;
-                                    })}
-                                    style={{ backgroundColor: this.openSlideOptions ? 'black' : '' }}>
+                                <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openSlideOptions = !this.openSlideOptions))} style={{ backgroundColor: this.openSlideOptions ? 'black' : '' }}>
                                     &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> &nbsp; {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'}
                                     <div className="propertiesView-presTrails-title-icon">
                                         <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" color="white" />
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index e2594b6ae..c83f4e689 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -15,6 +15,7 @@ import { RichTextField } from '../../../fields/RichTextField';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
@@ -40,7 +41,7 @@ import { CollectionLinearView } from './collectionLinear';
 import './CollectionMenu.scss';
 import { COLLECTION_BORDER_WIDTH } from './CollectionView';
 import { TabDocView } from './TabDocView';
-import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { CollectionFreeFormView } from './collectionFreeForm';
 
 interface CollectionMenuProps {
     panelHeight: () => number;
@@ -733,7 +734,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
                 doc._currentFrame = 0;
                 CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
             }
-            CollectionFreeFormDocumentView.updateKeyframe(undefined, childDocs, currentFrame || 0);
+            CollectionFreeFormView.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0);
             doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
         }
     }
@@ -747,7 +748,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
             this.document._currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
         }
-        this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0);
+        this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.document], currentFrame || 0);
         this.document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
         this.document.lastFrame = Math.max(NumCast(this.document._currentFrame), NumCast(this.document.lastFrame));
     };
@@ -759,7 +760,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
             this.document._currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
         }
-        this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice());
+        this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.document], 1000);
         this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
     };
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 456f2a13d..330e116d1 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -265,7 +265,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         const dropAction = StrCast(this.doc.childDropAction) as dropActionType;
         const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
         const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument?.(d, target, addDoc) || false;
-        if (this.treeChildren.length < this._renderCount) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
+        if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
         return TreeView.GetChildElements(
             this.treeChildren,
             this,
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index d6bc0a4b2..fa648eb44 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -248,7 +248,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 return;
             }
             const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false);
-            const pinDoc = Doc.MakeDelegate(anchorDoc ?? doc);
+            const pinDoc = anchorDoc && anchorDoc !== doc ? anchorDoc : Doc.MakeDelegate(doc);
             pinDoc.presentationTargetDoc = anchorDoc ?? 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
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4d6e0dff2..e5b4b76a8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -54,6 +54,7 @@ import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
 import React = require('react');
 import { DocumentDecorations } from '../../DocumentDecorations';
+import { PresEffect } from '../../nodes/trails';
 
 export type collectionFreeformViewProps = {
     annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox)
@@ -150,7 +151,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
             : this.props.contentBounds?.() ??
                   aggregateBounds(
-                      this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!),
+                      this._layoutElements.filter(e => e.bounds && e.bounds.width && !e.bounds.z).map(e => e.bounds!),
                       NumCast(this.layoutDoc._xPadding, 10),
                       NumCast(this.layoutDoc._yPadding, 10)
                   );
@@ -185,6 +186,32 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     _keyTimer: NodeJS.Timeout | undefined;
+
+    public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
+        if (timer) clearTimeout(timer);
+        return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true);
+    }
+    public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) {
+        if (timer) clearTimeout(timer);
+        const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, undefined, true);
+        const timecode = Math.round(time);
+        docs.forEach(doc => {
+            CollectionFreeFormDocumentView.animFields.forEach(val => {
+                const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null);
+                findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
+            });
+            CollectionFreeFormDocumentView.animStringFields.forEach(val => {
+                const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null);
+                findexed?.length <= timecode + 1 && findexed.push(undefined as any as string);
+            });
+            CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => {
+                const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null);
+                findexed?.length <= timecode + 1 && findexed.push(undefined as any);
+            });
+        });
+        return newTimer;
+    }
+
     changeKeyFrame = (back = false) => {
         const currentFrame = Cast(this.Document._currentFrame, 'number', null);
         if (currentFrame === undefined) {
@@ -192,10 +219,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
         }
         if (back) {
-            this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice());
+            this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], 1000);
             this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
         } else {
-            this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0);
+            this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], currentFrame || 0);
             this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
             this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame));
         }
@@ -1204,7 +1231,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             }
             // focus on the document in the collection
             const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
-            const focusSpeed = options?.instant ? 0 : didMove ? options.zoomTime ?? 500 : 0;
+            const focusSpeed = options?.instant ? 0 : didMove || DocCast(options.effect)?.presEffect !== PresEffect.None ? options.zoomTime ?? 500 : 0;
             // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
             if (didMove) {
                 options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale);
@@ -1392,12 +1419,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     });
 
     getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
-        const curFrame = Cast(this.Document._currentFrame, 'number');
         const childDoc = params.pair.layout;
         const childDocLayout = Doc.Layout(childDoc);
+        const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+        const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
         const { z, zIndex } = childDoc;
-        const { backgroundColor, color } = curFrame === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, curFrame);
-        const { x, y, opacity } = curFrame === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, curFrame);
+        const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber);
+        const { x, y, opacity } = layoutFrameNumber === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
         return {
             x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
             y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
@@ -1543,13 +1571,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const elements = computedElementData.slice();
         Array.from(newPool.entries())
             .filter(entry => this.isCurrent(entry[1].pair.layout))
-            .forEach((entry, i) =>
+            .forEach((entry, i) => {
+                const childData: ViewDefBounds = this.childDataProvider(entry[1].pair.layout, entry[1].replica);
+                const childSize = this.childSizeProvider(entry[1].pair.layout, entry[1].replica);
                 elements.push({
                     ele: this.getChildDocView(entry[1]),
-                    bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica),
+                    bounds: childData.opacity === 0 ? { ...childData, width: 0, height: 0 } : { ...childData, width: childSize.width, height: childSize.height },
                     inkMask: BoolCast(entry[1].pair.layout.isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1,
-                })
-            );
+                });
+            });
 
         if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) {
             // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
@@ -1988,7 +2018,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         zoomScaling={this.zoomScaling}
                         presPaths={this.showPresPaths}
                         presPinView={BoolCast(this.Document.presPinView)}
-                        transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)}
+                        transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))}
                         viewDefDivClick={this.props.viewDefDivClick}>
                         {this.children}
                     </CollectionFreeFormViewPannableContents>
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index bc3b17cd9..bd33c4d80 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -397,6 +397,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         newCollection.forceActive = makeGroup;
         newCollection.x = this.Bounds.left;
         newCollection.y = this.Bounds.top;
+        newCollection.fitwidth = true;
         selected.forEach(d => (d.context = newCollection));
         this.hideMarquee();
         return newCollection;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index f8ef87fb1..ba510e1dc 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -40,6 +40,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         { key: '_rotation', val: 0 },
         { key: '_scrollTop' },
         { key: 'opacity', val: 1 },
+        { key: '_currentFrame' },
+        { key: 'viewScale', val: 1 },
         { key: 'viewScale', val: 1 },
         { key: 'panX' },
         { key: 'panY' },
@@ -115,32 +117,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         });
     }
 
-    public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number, targetDoc?: Doc) {
-        if (timer) clearTimeout(timer);
-        const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, undefined, true);
-        const timecode = Math.round(time);
-        docs.forEach(doc => {
-            CollectionFreeFormDocumentView.animFields.forEach(val => {
-                const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
-            });
-            CollectionFreeFormDocumentView.animStringFields.forEach(val => {
-                const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as any as string);
-            });
-            CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => {
-                const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as any);
-            });
-        });
-        return newTimer;
-    }
-
-    public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration = 1000) {
-        if (timer) clearTimeout(timer);
-        return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true);
-    }
-
     public static setupZoom(doc: Doc, targDoc: Doc) {
         const width = new List<number>();
         const height = new List<number>();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3d89566ee..c3b0412de 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -17,7 +17,7 @@ import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnNone, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { DocServer } from '../../DocServer';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -252,6 +252,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _downX: number = 0;
     private _downY: number = 0;
+    private _downTime: number = 0;
     private _firstX: number = -1;
     private _firstY: number = -1;
     private _lastTap: number = 0;
@@ -610,7 +611,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             let preventDefault = true;
             const isScriptBox = () => StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name);
             (this.rootDoc._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
-            if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {
+            if (this._doubleTap && (![DocumentType.FONTICON, DocumentType.PRES].includes(this.props.Document.type as any) || this.onDoubleClickHandler)) {
                 // && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 if (this._timeout) {
                     clearTimeout(this._timeout);
@@ -718,6 +719,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         );
         this._downX = e.clientX;
         this._downY = e.clientY;
+        this._downTime = Date.now();
         if ((Doc.ActiveTool === InkTool.None || this.props.addDocTab === returnFalse) && !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) {
             // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking
             if (
@@ -777,11 +779,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this._cursorTimer && clearTimeout(this._cursorTimer);
         this._cursorPress = false;
 
+        const now = Date.now();
         if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
             this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
-        } else {
-            const now = Date.now();
-            console.log(now - this._lastTap);
+        } else if (now - this._downTime < 300) {
             this._doubleTap = now - this._lastTap < 600 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2;
             // bcz: this is a placeholder.  documents, when selected, should stopPropagation on doubleClicks if they want to keep the DocumentView from getting them
             if (!this.props.isSelected(true) || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this.rootDoc.type) as any)) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
@@ -1154,7 +1155,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 className="documentView-contentsView"
                 style={{
                     pointerEvents:
-                        (this.props.pointerEvents?.() as any) ?? this.rootDoc.layoutKey === 'layout_icon'
+                        this.opacity === 0
+                            ? 'none'
+                            : (this.props.pointerEvents?.() as any) ?? this.rootDoc.layoutKey === 'layout_icon'
                             ? 'none'
                             : (this.props.contentPointerEvents as any)
                             ? (this.props.contentPointerEvents as any)
@@ -1181,6 +1184,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 <DocumentContentsView
                     key={1}
                     {...this.props}
+                    pointerEvents={this.opacity === 0 ? returnNone : this.props.pointerEvents}
                     docViewPath={this.props.viewPath}
                     thumbShown={this.thumbShown}
                     isHovering={this.props.isHovering}
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index b352f3790..93761633c 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -179,7 +179,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                         className="list-item"
                         key={`${value}`}
                         style={{
-                            backgroundColor: value === checkResult ? Colors.LIGHT_BLUE : undefined,
+                            backgroundColor: value.toString() === checkResult ? Colors.LIGHT_BLUE : undefined,
                         }}
                         onClick={() => setValue(value)}>
                         {value}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e07517113..929bf1230 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,10 +1,10 @@
 import React = require('react');
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, ObservableSet, observe, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
-import { AnimationSym, Doc, DocListCast, FieldResult, HighlightSym, Opt, StrListCast } from '../../../../fields/Doc';
+import { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -14,7 +14,7 @@ import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Ty
 import { AudioField } from '../../../../fields/URLField';
 import { emptyFunction, emptyPath, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
-import { Docs, DocumentOptions } from '../../../documents/Documents';
+import { Docs } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
@@ -23,19 +23,17 @@ import { SettingsManager } from '../../../util/SettingsManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
 import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from '../../collections/collectionFreeForm';
+import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
 import { CollectionView } from '../../collections/CollectionView';
 import { TabDocView } from '../../collections/TabDocView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
-import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView';
 import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
-import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
-import { PresElementBox } from './PresElementBox';
 const { Howl } = require('howler');
 
 export interface PinProps {
@@ -296,7 +294,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 if (context) {
                     const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.ComponentView as CollectionFreeFormView;
                     if (ffview?.childDocs) {
-                        this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, ffview.childDocs.slice(), transTime);
+                        this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, ffview.childDocs, transTime);
                         context._currentFrame = NumCast(activeFrame);
                     }
                 }
@@ -598,7 +596,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     tagDoc.opacity = 1;
                 }
             }
-            const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex);
+            const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex) ?? itemIndexes.slice().reverse().lastElement();
             if (curDoc.presHideBefore && index === hidingIndBef) {
                 if (index > this.itemIndex) {
                     tagDoc.opacity = 0;
@@ -606,10 +604,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     tagDoc.opacity = 1;
                 }
             }
-            const hidingIndAft = itemIndexes
-                .slice()
-                .reverse()
-                .find(item => item < this.itemIndex);
+            const hidingIndAft =
+                itemIndexes
+                    .slice()
+                    .reverse()
+                    .find(item => item <= this.itemIndex) ?? itemIndexes.lastElement();
             if (curDoc.presHideAfter && index === hidingIndAft) {
                 if (index < this.itemIndex) {
                     tagDoc.opacity = 0;
@@ -875,7 +874,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     selectElement = async (doc: Doc, noNav = false) => {
         CollectionStackedTimeline.CurrentlyPlaying?.map((clip, i) => DocumentManager.Instance.getDocumentView(clip)?.ComponentView?.Pause?.());
-        !noNav && this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
+        if (noNav) {
+            const index = this.childDocs.indexOf(doc);
+            if (index >= 0 && index < this.childDocs.length) {
+                this.rootDoc._itemIndex = index;
+            }
+        } else this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
         this.updateCurrentPresentation(DocCast(doc.context));
     };
 
@@ -911,19 +915,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     //regular click
     @action
-    regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, selectPres = true, noNav = false) => {
+    regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, noNav: boolean, selectPres = true) => {
         this.clearSelectedArray();
         this.addToSelectedArray(doc);
         this._eleArray.splice(0, this._eleArray.length, ref);
         this._dragArray.splice(0, this._dragArray.length, drag);
-        focus && this.selectElement(doc, noNav);
+        this.selectElement(doc, noNav);
         selectPres && this.selectPres();
     };
 
-    modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, cmdClick: boolean, shiftClick: boolean, noNav: boolean = false) => {
+    modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, noNav: boolean, cmdClick: boolean, shiftClick: boolean) => {
         if (cmdClick) this.multiSelect(doc, ref, drag);
         else if (shiftClick) this.shiftSelect(doc, ref, drag);
-        else this.regularSelect(doc, ref, drag, focus, noNav);
+        else this.regularSelect(doc, ref, drag, noNav);
     };
 
     static keyEventsWrapper = (e: KeyboardEvent) => PresBox.Instance?.keyEvents(e);
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index f265c1315..40535c8cb 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -139,7 +139,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 onClick={e => {
                     e.stopPropagation();
                     e.preventDefault();
-                    this.presBoxView?.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+                    this.presBoxView?.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
                     this.presExpandDocumentClick();
                 }}>
                 <div className="presItem-groupNum">{`${ind + 1}.`}</div>
@@ -177,22 +177,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const element = e.target as any;
         e.stopPropagation();
         e.preventDefault();
-        if (element && !(e.ctrlKey || e.metaKey)) {
-            if (this.selectedArray?.has(this.rootDoc)) {
-                this.selectedArray.size === 1 && this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false);
-                setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
-            } else {
-                setupMoveUpEvents(
-                    this,
-                    e,
-                    (e: PointerEvent) => {
-                        this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false);
-                        return this.startDrag(e);
-                    },
-                    emptyFunction,
-                    emptyFunction
-                );
-            }
+        if (element && !(e.ctrlKey || e.metaKey || e.button === 2)) {
+            this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true, false);
+            setupMoveUpEvents(this, e, this.startDrag, emptyFunction, e => {
+                e.stopPropagation();
+                e.preventDefault();
+                this.presBoxView?.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+                this.presBoxView?.activeItem && this.showRecording(this.presBoxView?.activeItem);
+            });
         }
     };
 
@@ -314,50 +306,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     /**
      * Method called for updating the view of the currently selected document
      *
-     * @param targetDoc
+     * @param presTargetDoc
      * @param activeItem
      */
     @undoBatch
     @action
-    updateCapturedViewContents = (targetDoc: Doc, activeItem: Doc) => {
-        switch (targetDoc.type) {
-            case DocumentType.PDF:
-            case DocumentType.WEB:
-            case DocumentType.RTF:
-                const scroll = targetDoc._scrollTop;
-                activeItem.presPinViewScroll = scroll;
-                if (targetDoc.type === DocumentType.RTF) {
-                    activeItem.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof RichTextField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as RichTextField)[Copy]() : targetDoc.text;
-                }
-                break;
-            case DocumentType.INK:
-                activeItem.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof InkField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as InkField)[Copy]() : targetDoc.data;
-                break;
-            case DocumentType.VID:
-            case DocumentType.AUDIO:
-                activeItem.presStartTime = targetDoc._currentTimecode;
-                break;
-            case DocumentType.COMPARISON:
-                const clipWidth = targetDoc._clipWidth;
-                activeItem.presPinClipWidth = clipWidth;
-                break;
-            case DocumentType.COL:
-                activeItem.presPinLayoutData = new List<string>(DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) })));
-            default:
-                const bestView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
-                if (activeItem.presPinViewBounds && bestView) {
-                    const bounds = MarqueeView.CurViewBounds(targetDoc, bestView.props.PanelWidth(), bestView.props.PanelHeight());
-                    activeItem.presPinView = true;
-                    activeItem.presPinViewScale = NumCast(targetDoc._viewScale, 1);
-                    activeItem.presPinViewX = bounds.left + bounds.width / 2;
-                    activeItem.presPinViewY = bounds.top + bounds.height / 2;
-                    activeItem.presPinViewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
-                } else {
-                    activeItem.presPinViewX = targetDoc._panX;
-                    activeItem.presPinViewY = targetDoc._panY;
-                    activeItem.presPinViewScale = targetDoc._viewScale;
-                }
-        }
+    updateCapturedViewContents = (presTargetDoc: Doc, activeItem: Doc) => {
+        const target = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
+        PresBox.pinDocView(activeItem, { pinDocContent: true, pinData: PresBox.pinDataTypes(target) }, target);
     };
 
     @computed get recordingIsInOverlay() {
@@ -465,24 +421,26 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const activeItem: Doc = this.rootDoc;
 
         const items: JSX.Element[] = [];
-        if (activeItem.presPinLayout) {
-            items.push(
-                <Tooltip key="slide" title={<div className="dash-tooltip">Update captured doc layout</div>}>
-                    <div className="slideButton" onClick={() => this.updateCapturedContainerLayout(targetDoc, activeItem)} style={{ fontWeight: 700, display: 'flex' }}>
-                        L
-                    </div>
-                </Tooltip>
-            );
-        }
-        if (activeItem.presPinData || activeItem.presPinView) {
-            items.push(
-                <Tooltip key="flex" title={<div className="dash-tooltip">Update captured doc content</div>}>
-                    <div className="slideButton" onClick={() => this.updateCapturedViewContents(targetDoc, activeItem)} style={{ fontWeight: 700, display: 'flex' }}>
-                        C
-                    </div>
-                </Tooltip>
-            );
-        }
+        items.push(
+            <Tooltip key="slide" title={<div className="dash-tooltip">Update captured doc layout</div>}>
+                <div
+                    className="slideButton"
+                    onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedContainerLayout(targetDoc, activeItem), true)}
+                    style={{ opacity: activeItem.presPinLayout ? 1 : 0.5, fontWeight: 700, display: 'flex' }}>
+                    L
+                </div>
+            </Tooltip>
+        );
+        items.push(
+            <Tooltip key="flex" title={<div className="dash-tooltip">Update captured doc content</div>}>
+                <div
+                    className="slideButton"
+                    onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedViewContents(targetDoc, activeItem))}
+                    style={{ opacity: activeItem.presPinData || activeItem.presPinView ? 1 : 0.5, fontWeight: 700, display: 'flex' }}>
+                    C
+                </div>
+            </Tooltip>
+        );
         if (!Doc.noviceMode) {
             items.push(
                 <Tooltip key="slash" title={<div className="dash-tooltip">{this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
@@ -557,15 +515,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     paddingTop: NumCast(this.layoutDoc._yPadding, this.props.yPadding),
                     paddingBottom: NumCast(this.layoutDoc._yPadding, this.props.yPadding),
                 }}
-                onClick={e => {
-                    e.stopPropagation();
-                    e.preventDefault();
-                    this.presBoxView?.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
-                    this.showRecording(activeItem);
-                }}
                 onDoubleClick={action(e => {
                     this.toggleProperties();
-                    this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true);
+                    this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false);
                 })}
                 onPointerOver={this.onPointerOver}
                 onPointerLeave={this.onPointerLeave}
@@ -599,7 +551,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 onPointerDown={e => {
                                     e.stopPropagation();
                                     if (this._itemRef.current && this._dragRef.current) {
-                                        this.presBoxView?.modifierSelect(activeItem, this._itemRef.current, this._dragRef.current, false, false, false, true);
+                                        this.presBoxView?.modifierSelect(activeItem, this._itemRef.current, this._dragRef.current, true, false, false);
                                     }
                                 }}
                                 onClick={e => e.stopPropagation()}>{`${this.indexInPres + 1}.  `}</div>
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 3a7484cfd..6024705ec 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,3 +1,4 @@
+import { forEach } from 'lodash';
 import { $mobx, action, observable, runInAction, trace } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { DocServer } from '../client/DocServer';
-- 
cgit v1.2.3-70-g09d2


From 096371e683b3f91edfadb2aaf9f8da3309b86014 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 9 Mar 2023 21:39:21 -0500
Subject: added reordering of progressivized docs

---
 src/client/documents/Documents.ts                  |  1 +
 src/client/util/CurrentUserUtils.ts                |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  4 +--
 src/client/views/collections/CollectionView.tsx    |  4 +--
 src/client/views/collections/TreeView.tsx          | 12 ++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/trails/PresBox.tsx          | 41 ++++++++++++----------
 7 files changed, 37 insertions(+), 29 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a7378c431..6296b0df4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -335,6 +335,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
+    treeViewHideUnrendered?: boolean; // tells tree view not to display documents that have an 'unrendered' tag unless they also have a treeViewFieldKey tag (presBox)
     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.
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index a7b2c3d04..2b0a2cdac 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -269,7 +269,7 @@ export class CurrentUserUtils {
             {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }},
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             // {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument(opts),         opts: { _width: 300, _height: 300 }},
-            {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}},
+            {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, treeViewHideUnrendered: true}},
             {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true, _fitWidth:true, _chromeHidden: true, boxShadow: "0 0" }},
             {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _fitWidth: true, _backgroundGridShow: true, }},
             {key: "Slide",      creator: opts => Docs.Create.TreeDocument([], opts),        opts: { _width: 300, _height: 200, _viewType: CollectionViewType.Tree, 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 330e116d1..553967b95 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -38,8 +38,8 @@ export type collectionTreeViewProps = {
     onCheckedClick?: () => ScriptField;
     onChildClick?: () => ScriptField;
     // TODO: [AL] add these fields
-    AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
-    RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+    AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+    RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
     hierarchyIndex?: number[];
 };
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 316f1e4e9..48e5748a0 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -64,8 +64,8 @@ interface CollectionViewProps_ extends FieldViewProps {
     childClickScript?: ScriptField;
     childDoubleClickScript?: ScriptField;
     //TODO: [AL] add these fields
-    AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
-    RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+    AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+    RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
     hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
 }
 export interface CollectionViewProps extends React.PropsWithChildren<CollectionViewProps_> {}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 0d2968ba1..c8dc68eae 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -66,8 +66,8 @@ export interface TreeViewProps {
     skipFields?: string[];
     firstLevel: boolean;
     // TODO: [AL] add these
-    AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
-    RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+    AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+    RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
     hierarchyIndex?: number[];
 }
 
@@ -1028,7 +1028,9 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     // renders the text version of a document as the header.  This is used in the file system mode and in other vanilla tree views.
     @computed get renderTitleAsHeader() {
-        return (
+        return this.props.treeView.Document.treeViewHideUnrendered && this.doc.unrendered && !this.doc.treeViewFieldKey ? (
+            <div></div>
+        ) : (
             <>
                 {this.renderBullet}
                 {this.renderTitle}
@@ -1146,8 +1148,8 @@ export class TreeView extends React.Component<TreeViewProps> {
         unobserveHeight: (ref: any) => void,
         contextMenuItems: { script: ScriptField; filter: ScriptField; label: string; icon: string }[],
         // TODO: [AL] add these
-        AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
-        RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
+        AddToMap?: (treeViewDoc: Doc, index: number[]) => void,
+        RemFromMap?: (treeViewDoc: Doc, index: number[]) => void,
         hierarchyIndex?: number[],
         renderCount?: number
     ) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b92a8ec4d..87177cd74 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -519,7 +519,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 this._clusterSets.push([doc]);
             } else if (this._clusterSets.length) {
                 for (let i = this._clusterSets.length; i <= doc.cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
-                this._clusterSets[doc.cluster].push(doc);
+                this._clusterSets[doc.cluster ?? 0].push(doc);
             }
         }
     }
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 2975e49ab..2d9aaeca4 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -9,7 +9,7 @@ import { InkField, InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { listSpec } from '../../../../fields/Schema';
-import { ScriptField } from '../../../../fields/ScriptField';
+import { ComputedField, ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { AudioField } from '../../../../fields/URLField';
 import { emptyFunction, emptyPath, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils';
@@ -35,6 +35,7 @@ import { FieldView, FieldViewProps } from '../FieldView';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
+import { tag } from 'xregexp/types';
 const { Howl } = require('howler');
 
 export interface pinDataTypes {
@@ -306,8 +307,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         this.activeItem.presIndexed = presIndexed + 1;
                     }
                     return true;
-                } else {
-                    console.log(this.activeItem.presIndexed);
                 }
             }
         };
@@ -359,8 +358,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         Doc.UnBrushAllDocs();
         if (index >= 0 && index < this.childDocs.length) {
             this.rootDoc._itemIndex = index;
-            const activeItem: Doc = this.activeItem;
-            const targetDoc: Doc = this.targetDoc;
             if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) {
                 DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
             }
@@ -368,8 +365,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 this.stopTempMedia(from.presentationTargetDoc);
             }
             // If next slide is audio / video 'Play automatically' then the next slide should be played
-            if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && activeItem.mediaStart === 'auto') {
-                this.startTempMedia(targetDoc, activeItem);
+            if (this.layoutDoc.presStatus !== PresStatus.Edit && (this.targetDoc.type === DocumentType.AUDIO || this.targetDoc.type === DocumentType.VID) && this.activeItem.mediaStart === 'auto') {
+                this.startTempMedia(this.targetDoc, this.activeItem);
             }
             if (!group) this.clearSelectedArray();
             this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array
@@ -755,17 +752,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const curDoc = Cast(doc, Doc, null);
             const tagDoc = PresBox.targetRenderedDoc(curDoc);
             const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc);
+            let opacity: Opt<number> = index === this.itemIndex ? 1 : undefined;
             if (curDoc.presHide) {
                 if (index !== this.itemIndex) {
-                    tagDoc.opacity = 1;
+                    opacity = 1;
                 }
             }
             const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex) ?? itemIndexes.slice().reverse().lastElement();
             if (curDoc.presHideBefore && index === hidingIndBef) {
                 if (index > this.itemIndex) {
-                    tagDoc.opacity = 0;
+                    opacity = 0;
                 } else if (index === this.itemIndex || !curDoc.presHideAfter) {
-                    tagDoc.opacity = 1;
+                    opacity = 1;
                     setTimeout(() => (tagDoc._dataTransition = undefined), 1000);
                 }
             }
@@ -776,17 +774,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     .find(item => item <= this.itemIndex) ?? itemIndexes.lastElement();
             if (curDoc.presHideAfter && index === hidingIndAft) {
                 if (index < this.itemIndex) {
-                    tagDoc.opacity = 0;
+                    opacity = 0;
                 } else if (index === this.itemIndex || !curDoc.presHideBefore) {
-                    tagDoc.opacity = 1;
+                    opacity = 1;
                 }
             }
             const hidingInd = itemIndexes.find(item => item === this.itemIndex);
             if (curDoc.presHide && index === hidingInd) {
                 if (index === this.itemIndex) {
-                    tagDoc.opacity = 0;
+                    opacity = 0;
                 }
             }
+            opacity !== undefined && (tagDoc.opacity = opacity);
         });
     };
 
@@ -882,7 +881,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (doc.presHideBefore && index > startIndex) tagDoc.opacity = 0;
             if (doc.presHideAfter && index < startIndex) tagDoc.opacity = 0;
             if (doc.presIndexed !== undefined && index >= startIndex) {
-                const type = DocCast(tagDoc.annotationOn)?.type ?? tagDoc.type;
                 const startInd = NumCast(doc.presIndexedStart);
                 this.progressivizedItems(doc)
                     ?.slice(startInd)
@@ -1552,6 +1550,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 const tagDoc = PresBox.targetRenderedDoc(this.activeItem);
                                 const type = DocCast(tagDoc?.annotationOn)?.type ?? tagDoc.type;
                                 activeItem.presIndexedStart = type === DocumentType.COL ? 1 : 0;
+                                // a progressivized slide doesn't have sub-slides, but rather iterates over the data list of the target being progressivized.
+                                // to avoid creating a new slide to correspond to each of the target's data list, we simply reference the target's data list.
+                                let dataField = Doc.LayoutFieldKey(tagDoc);
+                                if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField = dataField + '-annotations';
+
+                                if (DocCast(activeItem.presentationTargetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentationTargetDoc.annotationOn["${dataField}"]`);
+                                else activeItem.data = ComputedField.MakeFunction(`self.presentationTargetDoc["${dataField}"]`);
                             }}
                             checked={Cast(activeItem.presIndexed, 'number', null) !== undefined ? true : false}
                         />
@@ -2364,7 +2369,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         clearTimeout(this._presTimer);
     };
 
-    AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+    AddToMap = (treeViewDoc: Doc, index: number[]) => {
+        if (!treeViewDoc.presentationTargetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
         var indexNum = 0;
         for (let i = 0; i < index.length; i++) {
             indexNum += index[i] * 10 ** -i;
@@ -2377,15 +2383,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs
             }
         }
-        return this.childDocs;
     };
 
-    RemFromMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+    RemFromMap = (treeViewDoc: Doc, index: number[]) => {
+        if (!treeViewDoc.presentationTargetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
         if (!this._unmounting && this.isTree) {
             this._treeViewMap.delete(treeViewDoc);
             this.dataDoc[this.presFieldKey] = new List<Doc>(this.sort(this._treeViewMap));
         }
-        return this.childDocs;
     };
 
     sort = (treeViewMap: Map<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);
-- 
cgit v1.2.3-70-g09d2


From c5c2c309cd88bbeb2f1b668cb040cffaac9c8470 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 4 Apr 2023 20:12:16 -0400
Subject: fixed using freeformview in sidebar of pdfs. fixed issues with
 dragging items out of schema and with selecting the schema view by clicking
 on headers.  Fixed a lot of errors caused by using OmitKeys which masks type
 checking.  fixed some pointerevent problems with treeview and freeformview

---
 src/client/documents/Documents.ts                  |  22 ++--
 src/client/views/DocumentDecorations.tsx           |  40 +++---
 src/client/views/EditableView.tsx                  |   4 +-
 src/client/views/InkStrokeProperties.ts            |   1 -
 src/client/views/InkingStroke.tsx                  |  13 +-
 src/client/views/MainView.tsx                      |   1 +
 src/client/views/PropertiesView.tsx                |  73 ++---------
 src/client/views/SidebarAnnos.tsx                  |  15 +--
 src/client/views/StyleProvider.tsx                 |   2 +-
 src/client/views/TemplateMenu.tsx                  |   2 +-
 .../views/collections/CollectionCarousel3DView.tsx |   7 +-
 .../views/collections/CollectionCarouselView.tsx   |   8 +-
 .../collections/CollectionStackedTimeline.tsx      |  54 +++-----
 .../views/collections/CollectionTreeView.tsx       |  24 ++--
 src/client/views/collections/CollectionView.tsx    |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 139 +++++++++++---------
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +
 .../collectionGrid/CollectionGridView.tsx          |   9 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  99 +++------------
 .../collectionSchema/SchemaColumnHeader.tsx        |  10 +-
 src/client/views/nodes/AudioBox.tsx                |   6 +-
 src/client/views/nodes/ComparisonBox.tsx           |   6 +-
 src/client/views/nodes/DocumentView.tsx            |  26 +---
 src/client/views/nodes/FieldView.tsx               |   2 +-
 src/client/views/nodes/ImageBox.tsx                |  12 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   6 +-
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx |   5 +-
 src/client/views/nodes/PDFBox.tsx                  | 141 ++++++++++++++++-----
 src/client/views/nodes/ScreenshotBox.tsx           |  18 ++-
 src/client/views/nodes/VideoBox.tsx                |  12 +-
 src/client/views/nodes/WebBox.tsx                  |  14 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  27 ++--
 src/client/views/pdf/PDFViewer.tsx                 |  15 ++-
 src/fields/Doc.ts                                  |   9 +-
 34 files changed, 389 insertions(+), 437 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9b8b9c877..9cb480c4a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1420,41 +1420,39 @@ export namespace DocUtils {
 
     export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined {
         let created: Doc | undefined;
-        let layout: ((fieldKey: string) => string) | undefined;
         const field = target[fieldKey];
-        const resolved = options || {};
+        const resolved = options ?? {};
         if (field instanceof ImageField) {
             created = Docs.Create.ImageDocument(field.url.href, resolved);
-            layout = ImageBox.LayoutString;
+            created.layout = ImageBox.LayoutString(fieldKey);
         } else if (field instanceof Doc) {
             created = field;
         } else if (field instanceof VideoField) {
             created = Docs.Create.VideoDocument(field.url.href, resolved);
-            layout = VideoBox.LayoutString;
+            created.layout = VideoBox.LayoutString(fieldKey);
         } else if (field instanceof PdfField) {
             created = Docs.Create.PdfDocument(field.url.href, resolved);
-            layout = PDFBox.LayoutString;
+            created.layout = PDFBox.LayoutString(fieldKey);
         } else if (field instanceof AudioField) {
             created = Docs.Create.AudioDocument(field.url.href, resolved);
-            layout = AudioBox.LayoutString;
+            created.layout = AudioBox.LayoutString(fieldKey);
         } else if (field instanceof RecordingField) {
             created = Docs.Create.RecordingDocument(field.url.href, resolved);
-            layout = RecordingBox.LayoutString;
+            created.layout = RecordingBox.LayoutString(fieldKey);
         } else if (field instanceof InkField) {
             created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, ActiveIsInkMask(), resolved);
-            layout = InkingStroke.LayoutString;
+            created.layout = InkingStroke.LayoutString(fieldKey);
         } else if (field instanceof List && field[0] instanceof Doc) {
             created = Docs.Create.StackingDocument(DocListCast(field), resolved);
-            layout = CollectionView.LayoutString;
+            created.layout = CollectionView.LayoutString(fieldKey);
         } else if (field instanceof MapField) {
             created = Docs.Create.MapDocument(DocListCast(field), resolved);
-            layout = MapBox.LayoutString;
+            created.layout = MapBox.LayoutString(fieldKey);
         } else {
             created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved });
-            layout = FormattedTextBox.LayoutString;
+            created.layout = FormattedTextBox.LayoutString(fieldKey);
         }
         if (created) {
-            created.layout = layout?.(fieldKey);
             created.title = fieldKey;
             proto && created.proto && (created.proto = Doc.GetProto(proto));
         }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 985e6f88f..9bc583ce5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -31,6 +31,7 @@ import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { ImageBox } from './nodes/ImageBox';
 import React = require('react');
+import { RichTextField } from '../../fields/RichTextField';
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
@@ -127,19 +128,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         }
                         //@ts-ignore
                         const titleField = +this._accumulatedTitle == this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle;
-                        Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
 
-                        if (d.rootDoc.syncLayoutFieldWithTitle) {
-                            const title = titleField.toString();
+                        if (titleField.toString().startsWith('<this>')) {
+                            const title = titleField.toString().replace(/<this>\.?/, '');
                             const curKey = Doc.LayoutFieldKey(d.rootDoc);
-                            if (curKey !== title && d.dataDoc[title] === undefined) {
-                                d.rootDoc.layout = FormattedTextBox.LayoutString(title);
-                                setTimeout(() => {
-                                    const val = d.dataDoc[curKey];
-                                    d.dataDoc[curKey] = undefined;
-                                    d.dataDoc[title] = val;
-                                });
+                            if (curKey !== title) {
+                                if (title) {
+                                    if (d.dataDoc[title] === undefined || d.dataDoc[title] instanceof RichTextField || typeof d.dataDoc[title] === 'string') {
+                                        d.rootDoc.layoutKey = `layout_${title}`;
+                                        d.rootDoc[`layout_${title}`] = FormattedTextBox.LayoutString(title);
+                                        d.rootDoc[`${title}-nativeWidth`] = d.rootDoc[`${title}-nativeHeight`] = 0;
+                                    }
+                                } else {
+                                    d.rootDoc.layoutKey = undefined;
+                                }
                             }
+                        } else {
+                            Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
                         }
                     }),
                 'title blur'
@@ -453,17 +458,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        const views = SelectionManager.Views().map(dv => dv.rootDoc);
-        this._inkDragDocs = views
-            .filter(doc => doc.type === DocumentType.INK)
-            .map(doc => {
-                if (InkStrokeProperties.Instance._lock) {
-                    Doc.SetNativeHeight(doc, NumCast(doc._height));
-                    Doc.SetNativeWidth(doc, NumCast(doc._width));
-                }
-                return { doc, x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) };
-            });
-
         setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
         this.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
         this._resizeHdlId = e.currentTarget.className;
@@ -487,10 +481,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         if (!first) return false;
         let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY };
         var fixedAspect = Doc.NativeAspect(first.layoutDoc);
-        InkStrokeProperties.Instance._lock &&
-            SelectionManager.Views()
-                .filter(dv => dv.rootDoc.type === DocumentType.INK)
-                .forEach(dv => (fixedAspect = Doc.NativeAspect(dv.rootDoc)));
 
         const resizeHdl = this._resizeHdlId.split(' ')[0];
         if (fixedAspect && (resizeHdl === 'documentDecorations-bottomRightResizer' || resizeHdl === 'documentDecorations-topLeftResizer')) {
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index bb190e93b..164b6c57a 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -129,7 +129,7 @@ export class EditableView extends React.Component<EditableProps> {
                 this._editing = true;
                 this.props.isEditingCallback?.(true);
             }
-            e.stopPropagation();
+            // e.stopPropagation();
         }
     };
 
@@ -215,7 +215,7 @@ export class EditableView extends React.Component<EditableProps> {
                 className={`editableView-container-editing${this.props.oneLine ? '-oneLine' : ''}`}
                 ref={this._ref}
                 style={{ display: this.props.display, textOverflow: this.props.overflow, minHeight: '10px', whiteSpace: 'nowrap', height: this.props.height || 'auto', maxHeight: this.props.maxHeight }}
-                onPointerDown={e => e.stopPropagation()}
+                //onPointerDown={this.stopPropagation}
                 onClick={this.onClick}
                 placeholder={this.props.placeholder}>
                 <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 1d8d52425..e6df0801c 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -19,7 +19,6 @@ export class InkStrokeProperties {
         return this._Instance || new InkStrokeProperties();
     }
 
-    @observable _lock = false;
     @observable _controlButton = false;
     @observable _currentPoint = -1;
 
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index e3642fdaf..2fd6cc4d6 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -25,10 +25,11 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, HeightSym, WidthSym } from '../../fields/Doc';
 import { InkData, InkField } from '../../fields/InkField';
-import { BoolCast, Cast, DocCast, NumCast, RTFCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
-import { DashColor, OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { DashColor, returnFalse, setupMoveUpEvents } from '../../Utils';
 import { CognitiveServices } from '../cognitive_services/CognitiveServices';
+import { Docs } from '../documents/Documents';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
@@ -41,13 +42,12 @@ import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
 import './InkStroke.scss';
 import { InkStrokeProperties } from './InkStrokeProperties';
 import { InkTangentHandles } from './InkTangentHandles';
-import { DocComponentView, DocFocusOptions, DocumentView } from './nodes/DocumentView';
+import { DocComponentView } from './nodes/DocumentView';
 import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { PinProps, PresBox } from './nodes/trails';
 import { StyleProp } from './StyleProvider';
 import Color = require('color');
-import { Docs } from '../documents/Documents';
-import { PinProps, PresBox } from './nodes/trails';
 
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -464,7 +464,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                             //top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.NativeDimScaling?.() || 1)) / 2,
                         }}>
                         <FormattedTextBox
-                            {...OmitKeys(this.props, ['children']).omit}
+                            {...this.props}
+                            setHeight={undefined}
                             setContentView={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
                             yPadding={10}
                             xPadding={10}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f5adc17d0..befb0200d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -259,6 +259,7 @@ export class MainView extends React.Component {
                 fa.faConciergeBell,
                 fa.faWindowRestore,
                 fa.faFolder,
+                fa.faFolderOpen,
                 fa.faMapPin,
                 fa.faMapMarker,
                 fa.faFingerprint,
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index f3a5a5393..fbc7d7696 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -102,7 +102,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @observable inOptions: boolean = false;
     @observable _controlButton: boolean = false;
-    @observable _lock: boolean = false;
 
     componentDidMount() {
         this.selectedDocListenerDisposer?.();
@@ -586,16 +585,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         <FontAwesomeIcon icon="bezier-curve" color="white" size="lg" />
                     </div>
                 </Tooltip>
-                <Tooltip title={<div className="dash-tooltip">{InkStrokeProperties.Instance._lock ? 'Unlock ratio' : 'Lock ratio'}</div>}>
-                    <div className="inking-button-lock" onPointerDown={action(() => (InkStrokeProperties.Instance._lock = !InkStrokeProperties.Instance._lock))}>
-                        <FontAwesomeIcon icon={InkStrokeProperties.Instance._lock ? 'lock' : 'unlock'} color="white" size="lg" />
-                    </div>
-                </Tooltip>
-                <Tooltip title={<div className="dash-tooltip">{'Rotate 90˚'}</div>}>
-                    <div className="inking-button-rotate" onPointerDown={action(() => this.rotate(Math.PI / 2))}>
-                        <FontAwesomeIcon icon="undo" color="white" size="lg" />
-                    </div>
-                </Tooltip>
             </div>
         );
     }
@@ -644,9 +633,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @action
     upDownButtons = (dirs: string, field: string) => {
         switch (field) {
-            case 'rot':
-                this.rotate(dirs === 'up' ? 0.1 : -0.1);
-                break;
             case 'Xps':
                 this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10));
                 break;
@@ -662,7 +648,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 const oldX = NumCast(this.selectedDoc?.x);
                 const oldY = NumCast(this.selectedDoc?.y);
                 this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === 'up' ? 10 : -10));
-                InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth) * NumCast(this.selectedDoc?._height));
                 const doc = this.selectedDoc;
                 if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
                     const ink = Cast(doc.data, InkField)?.inkData;
@@ -684,7 +669,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 const oX = NumCast(this.selectedDoc?.x);
                 const oY = NumCast(this.selectedDoc?.y);
                 this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === 'up' ? 10 : -10));
-                InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight) * NumCast(this.selectedDoc?._width));
                 const docu = this.selectedDoc;
                 if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) {
                     const ink = Cast(docu.data, InkField)?.inkData;
@@ -704,11 +688,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     };
 
     getField(key: string) {
-        //if (this.selectedDoc) {
         return Field.toString(this.selectedDoc?.[key] as Field);
-        // } else {
-        //     return undefined as Opt<string>;
-        // }
     }
 
     @computed get shapeXps() {
@@ -717,9 +697,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @computed get shapeYps() {
         return this.getField('y');
     }
-    @computed get shapeRot() {
-        return this.getField('rotation');
-    }
     @computed get shapeHgt() {
         return this.getField('_height');
     }
@@ -732,18 +709,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     set shapeYps(value) {
         this.selectedDoc && (this.selectedDoc.y = Number(value));
     }
-    set shapeRot(value) {
-        this.selectedDoc && (this.selectedDoc.rotation = Number(value));
-    }
     set shapeWid(value) {
-        const oldWidth = NumCast(this.selectedDoc?._width);
         this.selectedDoc && (this.selectedDoc._width = Number(value));
-        InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth);
     }
     set shapeHgt(value) {
-        const oldHeight = NumCast(this.selectedDoc?._height);
         this.selectedDoc && (this.selectedDoc._height = Number(value));
-        InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight);
     }
 
     @computed get hgtInput() {
@@ -790,30 +760,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             'Y:'
         );
     }
-    @computed get rotInput() {
-        return this.inputBoxDuo(
-            'rot',
-            this.shapeRot,
-            (val: string) => {
-                if (!isNaN(Number(val))) {
-                    this.rotate(Number(val) - Number(this.shapeRot));
-                    this.shapeRot = val;
-                }
-                return true;
-            },
-            '∠:',
-            'rot',
-            this.shapeRot,
-            (val: string) => {
-                if (!isNaN(Number(val))) {
-                    this.rotate(Number(val) - Number(this.shapeRot));
-                    this.shapeRot = val;
-                }
-                return true;
-            },
-            ''
-        );
-    }
 
     @observable private _fillBtn = false;
     @observable private _lineBtn = false;
@@ -1080,10 +1026,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @computed get transformEditor() {
         return (
             <div className="transform-editor">
-                {this.controlPointsButton}
+                {this.isInk ? this.controlPointsButton : null}
                 {this.hgtInput}
                 {this.XpsInput}
-                {this.rotInput}
             </div>
         );
     }
@@ -1194,17 +1139,15 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     </div>
                 )}
 
-                {this.isInk ? (
-                    <div className="propertiesView-transform">
-                        <div className="propertiesView-transform-title" onPointerDown={action(() => (this.openTransform = !this.openTransform))} style={{ backgroundColor: this.openTransform ? 'black' : '' }}>
-                            Transform
-                            <div className="propertiesView-transform-title-icon">
-                                <FontAwesomeIcon icon={this.openTransform ? 'caret-down' : 'caret-right'} size="lg" color="white" />
-                            </div>
+                <div className="propertiesView-transform">
+                    <div className="propertiesView-transform-title" onPointerDown={action(() => (this.openTransform = !this.openTransform))} style={{ backgroundColor: this.openTransform ? 'black' : '' }}>
+                        Transform
+                        <div className="propertiesView-transform-title-icon">
+                            <FontAwesomeIcon icon={this.openTransform ? 'caret-down' : 'caret-right'} size="lg" color="white" />
                         </div>
-                        {this.openTransform ? <div className="propertiesView-transform-content">{this.transformEditor}</div> : null}
                     </div>
-                ) : null}
+                    {this.openTransform ? <div className="propertiesView-transform-content">{this.transformEditor}</div> : null}
+                </div>
             </>
         );
     }
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 02dd15960..48c0150e6 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -5,7 +5,7 @@ import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
 import { DocCast, NumCast, StrCast } from '../../fields/Types';
-import { emptyFunction, OmitKeys, returnAll, returnOne, returnTrue, returnZero } from '../../Utils';
+import { emptyFunction, returnAll, returnFalse, returnOne, returnTrue, returnZero } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { LinkManager } from '../util/LinkManager';
@@ -26,6 +26,7 @@ interface ExtraProps {
     // usePanelWidth: boolean;
     showSidebar: boolean;
     nativeWidth: number;
+    usePanelWidth?: boolean;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
     ScreenToLocalTransform: () => Transform;
     sidebarAddDocument: (doc: Doc | Doc[], suffix: string) => boolean;
@@ -158,13 +159,10 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
             .ScreenToLocalTransform()
             .translate(Doc.NativeWidth(this.props.dataDoc), 0)
             .scale(this.props.NativeDimScaling?.() || 1);
-    // panelWidth = () => !this.props.layoutDoc._showSidebar ? 0 :
-    //     this.props.usePanelWidth ? this.props.PanelWidth() :
-    //         (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth);
     panelWidth = () =>
         !this.props.showSidebar
             ? 0
-            : this.props.layoutDoc.type === DocumentType.RTF || this.props.layoutDoc.type === DocumentType.MAP
+            : this.props.usePanelWidth // [DocumentType.RTF, DocumentType.MAP].includes(this.props.layoutDoc.type as any)
             ? this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1)
             : ((NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth()) / NumCast(this.props.nativeWidth);
     panelHeight = () => this.props.PanelHeight() - this.filtersHeight();
@@ -224,20 +222,21 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                 </div>
                 <div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
                     <CollectionStackingView
-                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
-                        ref={this._stackRef}
+                        {...this.props}
+                        setContentView={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
+                        ref={this._stackRef}
                         PanelHeight={this.panelHeight}
                         PanelWidth={this.panelWidth}
                         docFilters={this.docFilters}
-                        scaleField={this.sidebarKey + '-scale'}
                         sortFunc={this.sortByLinkAnchorY}
                         setHeight={this.setHeightCallback}
                         isAnnotationOverlay={false}
                         select={emptyFunction}
                         NativeDimScaling={returnOne}
                         //childShowTitle={this.showTitle}
+                        isAnyChildContentActive={returnFalse}
                         childDocumentsActive={this.props.isContentActive}
                         whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
                         childHideDecorationTitle={returnTrue}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 50cf8eba7..f7b9420a7 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -170,7 +170,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
             if (docColor) return docColor;
             const docView = props?.DocumentView?.();
-            const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, 'backgroundColor');
+            const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, StyleProp.BackgroundColor);
             if (!backColor) return undefined;
             return lightOrDark(backColor);
         case StyleProp.Hidden:
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 681ff66e0..45db240a9 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -79,7 +79,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     };
     componentDidMount() {
         !this._addedKeys && (this._addedKeys = new ObservableSet());
-        Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document)))
+        [...Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document))), ...Array.from(Object.keys(this.props.docViews[0].props.Document))]
             .filter(key => key.startsWith('layout_'))
             .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', ''))));
     }
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 57ff1b292..a266c9207 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
 import { Doc } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { OmitKeys, returnFalse, Utils } from '../../../Utils';
+import { returnFalse, returnZero, Utils } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { DocumentView } from '../nodes/DocumentView';
 import { StyleProp } from '../StyleProvider';
@@ -43,7 +43,10 @@ export class CollectionCarousel3DView extends CollectionSubView() {
         const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
             return (
                 <DocumentView
-                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'childLayoutTemplate', 'childLayoutString']).omit}
+                    {...this.props}
+                    NativeWidth={returnZero}
+                    NativeHeight={returnZero}
+                    suppressSetHeight={true}
                     onDoubleClick={this.onChildDoubleClick}
                     renderDepth={this.props.renderDepth + 1}
                     LayoutTemplate={this.props.childLayoutTemplate}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 32f6207ed..c0220f804 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { OmitKeys, returnFalse } from '../../../Utils';
+import { emptyFunction, returnFalse, returnZero } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
@@ -47,7 +47,7 @@ export class CollectionCarouselView extends CollectionSubView() {
     @computed get content() {
         const index = NumCast(this.layoutDoc._itemIndex);
         const curDoc = this.childLayoutPairs?.[index];
-        const captionProps = { ...OmitKeys(this.props, ['setHeight']).omit, fieldKey: 'caption' };
+        const captionProps = { ...this.props, fieldKey: 'caption', setHeight: undefined };
         const marginX = NumCast(this.layoutDoc['caption-xMargin']);
         const marginY = NumCast(this.layoutDoc['caption-yMargin']);
         const showCaptions = StrCast(this.layoutDoc._showCaption);
@@ -55,7 +55,9 @@ export class CollectionCarouselView extends CollectionSubView() {
             <>
                 <div className="collectionCarouselView-image" key="image">
                     <DocumentView
-                        {...OmitKeys(this.props, ['setHeight', 'NativeWidth', 'NativeHeight', 'childLayoutTemplate', 'childLayoutString']).omit}
+                        {...this.props}
+                        NativeWidth={returnZero}
+                        NativeHeight={returnZero}
                         onDoubleClick={this.onContentDoubleClick}
                         onClick={this.onContentClick}
                         hideCaptions={showCaptions ? true : false}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 4941bc722..642d29d2a 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -9,7 +9,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { Cast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, OmitKeys, returnFalse, returnNone, returnOne, returnTrue, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
+import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -23,7 +23,6 @@ import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { AudioWaveform } from '../AudioWaveform';
 import { CollectionSubView } from '../collections/CollectionSubView';
-import { Colors } from '../global/globalEnums';
 import { LightboxView } from '../LightboxView';
 import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { LabelBox } from '../nodes/LabelBox';
@@ -512,37 +511,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     currentTimecode = () => this.currentTime;
 
-    @computed get renderDictation() {
-        const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null);
-        return !dictation ? null : (
-            <div
-                style={{
-                    position: 'absolute',
-                    height: '100%',
-                    top: this.timelineContentHeight,
-                    background: Colors.LIGHT_BLUE,
-                }}>
-                <DocumentView
-                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
-                    Document={dictation}
-                    PanelHeight={this.dictationHeight}
-                    isAnnotationOverlay={true}
-                    isDocumentActive={returnFalse}
-                    select={emptyFunction}
-                    NativeDimScaling={returnOne}
-                    xMargin={25}
-                    yMargin={10}
-                    ScreenToLocalTransform={this.dictationScreenToLocalTransform}
-                    whenChildContentsActiveChanged={emptyFunction}
-                    removeDocument={returnFalse}
-                    moveDocument={returnFalse}
-                    addDocument={returnFalse}
-                    CollectionView={undefined}
-                    renderDepth={this.props.renderDepth + 1}></DocumentView>
-            </div>
-        );
-    }
-
     // renders selection region on timeline
     @computed get selectionContainer() {
         const markerEnd = CollectionStackedTimeline.SelectingRegion === this ? this.currentTime : this._markerEnd;
@@ -638,7 +606,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                                 PanelWidth={this.timelineContentWidth}
                             />
                         )}
-                        {/* {this.renderDictation} */}
 
                         <div
                             className="collectionStackedTimeline-hover"
@@ -817,16 +784,22 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
     // renders anchor LabelBox
     renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
         const anchor = observable({ view: undefined as any });
-        const focusFunc = (doc: Doc, options: DocFocusOptions) => this.props.playLink(mark);
+        const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => {
+            this.props.playLink(mark);
+            return undefined;
+        };
         return {
             anchor,
             view: (
                 <DocumentView
                     key="view"
-                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+                    {...this.props}
+                    NativeWidth={returnZero}
+                    NativeHeight={returnZero}
                     ref={action((r: DocumentView | null) => (anchor.view = r))}
                     Document={mark}
                     DataDoc={undefined}
+                    docViewPath={returnEmptyDoclist}
                     pointerEvents={this.noEvents ? returnNone : undefined}
                     styleProvider={this.props.styleProvider}
                     renderDepth={this.props.renderDepth + 1}
@@ -837,7 +810,16 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                     PanelHeight={height}
                     fitWidth={returnTrue}
                     ScreenToLocalTransform={screenXf}
+                    addDocTab={returnFalse}
+                    pinToPres={emptyFunction}
+                    whenChildContentsActiveChanged={emptyFunction}
                     focus={focusFunc}
+                    isContentActive={returnFalse}
+                    ContainingCollectionView={undefined}
+                    ContainingCollectionDoc={undefined}
+                    searchFilterDocs={returnEmptyDoclist}
+                    docFilters={returnEmptyFilter}
+                    docRangeFilters={returnEmptyFilter}
                     rootSelected={returnFalse}
                     onClick={script}
                     onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 553967b95..4a11e8f0b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -7,7 +7,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, OmitKeys, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue } from '../../../Utils';
+import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
@@ -384,12 +384,12 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false;
 
     @observable _headerHeight = 0;
-    contentFunc = () => {
+    @computed get content() {
         const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
         const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined);
         const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? null : this.titleBar;
-        return [
-            <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
+        return (
+            <div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}>
                 {!this.buttonMenu && !this.noviceExplainer ? null : (
                     <div className="documentButtonMenu" ref={action((r: HTMLDivElement | null) => r && (this._headerHeight = Number(getComputedStyle(r).height.replace(/px/, ''))))}>
                         {this.buttonMenu}
@@ -428,9 +428,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                         </div>
                     </div>
                 </div>
-            </div>,
-        ];
-    };
+            </div>
+        );
+    }
     render() {
         TraceMobx();
 
@@ -439,7 +439,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
                 {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? (
                     <CollectionFreeFormView
-                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                        {...this.props}
+                        setContentView={emptyFunction}
+                        NativeWidth={returnZero}
+                        NativeHeight={returnZero}
+                        pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone}
                         isAnnotationOverlay={true}
                         isAnnotationOverlayScrollable={true}
                         childDocumentsActive={this.props.isDocumentActive}
@@ -451,10 +455,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                         moveDocument={this.moveAnnotationDocument}
                         bringToFront={emptyFunction}
                         renderDepth={this.props.renderDepth + 1}>
-                        {this.contentFunc}
+                        {this.content}
                     </CollectionFreeFormView>
                 ) : (
-                    this.contentFunc()
+                    this.content
                 )}
             </div>
         );
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 51624689e..bc25ad43a 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -45,7 +45,7 @@ interface CollectionViewProps_ extends FieldViewProps {
 
     // property overrides for child documents
     childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
-    childDocumentsActive?: () => boolean; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
+    childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
     childFitWidth?: (child: Doc) => boolean;
     childShowTitle?: () => string;
     childOpacity?: () => number;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index aed3683d4..d39668a5d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
 import { Bezier } from 'bezier-js';
 import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { DateField } from '../../../../fields/DateField';
@@ -53,10 +53,14 @@ import { MarqueeView } from './MarqueeView';
 import React = require('react');
 
 export type collectionFreeformViewProps = {
+    noPointerWheel?: () => boolean; // turn off pointerwheel interactions (see PDFViewer)
+    NativeWidth?: () => number;
+    NativeHeight?: () => number;
+    originTopLeft?: boolean;
     annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox)
     viewDefDivClick?: ScriptField;
     childPointerEvents?: string;
-    scaleField?: string;
+    viewField?: string;
     noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
     engineProps?: any;
     getScrollHeight?: () => number | undefined;
@@ -97,8 +101,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     private get isAnnotationOverlay() {
         return this.props.isAnnotationOverlay;
     }
-    private get scaleFieldKey() {
-        return this.props.scaleField || '_viewScale';
+    public get scaleFieldKey() {
+        return this.props.viewField ? this.props.viewField + '-viewScale' : '_viewScale';
+    }
+    private get panXFieldKey() {
+        return this.props.viewField ? this.props.viewField + '-panX' : '_panX';
+    }
+    private get panYFieldKey() {
+        return this.props.viewField ? this.props.viewField + '-panY' : '_panY';
     }
     private get borderWidth() {
         return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
@@ -149,21 +159,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                   );
     }
     @computed get nativeWidth() {
-        return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+        return this.props.NativeWidth?.() || (this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
     }
     @computed get nativeHeight() {
-        return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+        return this.props.NativeHeight?.() || (this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
     }
     @computed get cachedCenteringShiftX(): number {
         const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
-        return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+        return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedCenteringShiftY(): number {
         const dv = this.props.DocumentView?.();
         const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
         // if freeform has a native aspect, then the panel height needs to be adjusted to match it
         const aspect = dv?.nativeWidth && dv?.nativeHeight && !dv.layoutDoc.fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
-        return this.props.isAnnotationOverlay ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+        return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedGetLocalTransform(): Transform {
         return Transform.Identity()
@@ -236,13 +246,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     reverseNativeScaling = () => (this.fitContentsToBox ? true : false);
     // panx, pany, zoomscale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document.
     // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
-    panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1));
-    panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1));
+    panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1));
+    panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1));
     zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
     contentTransform = () =>
-        !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1
-            ? ''
-            : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
+        this.props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
     getTransform = () => this.cachedGetTransform.copy();
     getLocalTransform = () => this.cachedGetLocalTransform.copy();
     getContainerTransform = () => this.cachedGetContainerTransform.copy();
@@ -293,7 +301,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     groupFocus = (anchor: Doc, options: DocFocusOptions) => {
-        options.docTransform = new Transform(-NumCast(this.rootDoc.panX) + NumCast(anchor.x), -NumCast(this.rootDoc.panY) + NumCast(anchor.y), 1);
+        options.docTransform = new Transform(-NumCast(this.rootDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.rootDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
         const res = this.props.focus(this.rootDoc, options);
         options.docTransform = undefined;
         return res;
@@ -301,7 +309,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     focus = (anchor: Doc, options: DocFocusOptions) => {
         const xfToCollection = options?.docTransform ?? Transform.Identity();
-        const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
+        const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
         const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
         const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale || 0.75 : undefined);
 
@@ -772,7 +780,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         PresBox.Instance?.pauseAutoPres();
         const dx = e.deltaX;
         const dy = e.deltaY;
-        this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
+        this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true);
     };
 
     @action
@@ -780,7 +788,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         PresBox.Instance?.pauseAutoPres();
         this.props.DocumentView?.().clearViewTransition();
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
+        this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true);
         this._lastX = e.clientX;
         this._lastY = e.clientY;
     };
@@ -979,13 +987,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) {
             const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20);
             this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
-            this.setPan(-localTransform.TranslateX / safeScale, NumCast(this.props.Document.scrollTop) * safeScale || -localTransform.TranslateY / safeScale);
+            this.setPan(-localTransform.TranslateX / safeScale, (this.props.originTopLeft ? undefined : NumCast(this.props.Document.scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
         }
     };
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
+        if (this.props.noPointerWheel?.() || this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
         PresBox.Instance?.pauseAutoPres();
         if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
         e.stopPropagation();
@@ -995,7 +1003,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 // if ctrl is selected then zoom
                 if (e.ctrlKey) {
                     if (this.props.isContentActive(true)) {
-                        !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+                        this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
                     }
                 } // otherwise pan
                 else if (this.props.isContentActive(true)) {
@@ -1039,16 +1047,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) },
                     }),
                     {
-                        xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
-                        yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
+                        xrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
+                        yrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
                     }
                 );
 
-                const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
-                if (ranges.xrange.min >= panX + panelDim[0] / 2) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds
-                else if (ranges.xrange.max <= panX - panelDim[0] / 2) panX = ranges.xrange.min - panelDim[0] / 2;
-                if (ranges.yrange.min >= panY + panelDim[1] / 2) panY = ranges.yrange.max + panelDim[1] / 2;
-                else if (ranges.yrange.max <= panY - panelDim[1] / 2) panY = ranges.yrange.min - panelDim[1] / 2;
+                const panelWidMax = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+                const panelWidMin = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1);
+                const panelHgtMax = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+                const panelHgtMin = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1);
+                if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this.props.originTopLeft ? 0 : panelWidMax / 2);
+                else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this.props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2);
+                if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this.props.originTopLeft ? 0 : panelHgtMax / 2);
+                else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this.props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2);
             }
         }
         if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(Doc.MyOverlayDocs?.data).includes(this.Document)) {
@@ -1078,8 +1089,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 }, 10);
                 newPanY = minPanY;
             }
-            !this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX);
-            !this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY);
+            !this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX);
+            !this.Document._horizontalScroll && (this.Document[this.panYFieldKey] = this.isAnnotationOverlay ? newPanY : panY);
         }
     }
 
@@ -1087,8 +1098,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     nudge = (x: number, y: number, nudgeTime: number = 500) => {
         if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) {
             this.setPan(
-                NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
-                NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
+                NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
+                NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
                 nudgeTime,
                 true
             );
@@ -1106,12 +1117,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         } else {
             const docs = this.childLayoutPairs.map(pair => pair.layout).slice();
             docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
-            let zlast = docs.length ? Math.max(docs.length, NumCast(docs[docs.length - 1].zIndex)) : 1;
-            if (zlast - docs.length > 100) {
-                for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
-                zlast = docs.length + 1;
+            let zlast = docs.length ? Math.max(docs.length, NumCast(docs.lastElement().zIndex)) : 1;
+            if (docs.lastElement() !== doc) {
+                if (zlast - docs.length > 100) {
+                    for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
+                    zlast = docs.length + 1;
+                }
+                doc.zIndex = zlast + 1;
             }
-            doc.zIndex = zlast + 1;
         }
     };
 
@@ -1134,8 +1147,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
         const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
         const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
-        this.layoutDoc._panX = NumCast(this.layoutDoc._panX) - newpan[0];
-        this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
+        this.layoutDoc[this.panXFieldKey] = NumCast(this.layoutDoc[this.panXFieldKey]) - newpan[0];
+        this.layoutDoc[this.panYFieldKey] = NumCast(this.layoutDoc[this.panYFieldKey]) - newpan[1];
     }
 
     calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
@@ -1158,8 +1171,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const panelHeight = this.props.isAnnotationOverlay ? this.nativeHeight : this.props.PanelHeight();
         const pw = panelWidth / NumCast(this.layoutDoc._viewScale, 1);
         const ph = panelHeight / NumCast(this.layoutDoc._viewScale, 1);
-        const cx = NumCast(this.layoutDoc._panX) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
-        const cy = NumCast(this.layoutDoc._panY) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
+        const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
+        const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
         const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
         const maxYShift = Math.max(0, screen.bot - screen.top - (bounds.bot - bounds.top));
         const phborder = bounds.top < screen.top || bounds.bot > screen.bot ? Math.min(ph / 10, maxYShift / 2) : 0;
@@ -1171,8 +1184,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             };
         }
         return {
-            panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panX) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
-            panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panY) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
+            panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
+            panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
         };
     };
 
@@ -1459,7 +1472,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) {
             // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
-            if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
+            if (this.props.viewField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
             else this.props.Document[this.scaleFieldKey] = Math.max(1, this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey]));
         }
 
@@ -1501,7 +1514,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     cbounds => {
                         if (cbounds) {
                             const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2];
-                            const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)];
+                            const p = [NumCast(this.layoutDoc[this.panXFieldKey]), NumCast(this.layoutDoc[this.panYFieldKey])];
                             const pbounds = {
                                 x: cbounds.x - p[0] + c[0],
                                 y: cbounds.y - p[1] + c[1],
@@ -1511,8 +1524,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                             if (Number.isFinite(pbounds.r - pbounds.x) && Number.isFinite(pbounds.b - pbounds.y)) {
                                 this.layoutDoc._width = pbounds.r - pbounds.x;
                                 this.layoutDoc._height = pbounds.b - pbounds.y;
-                                this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2;
-                                this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2;
+                                this.layoutDoc[this.panXFieldKey] = (cbounds.r + cbounds.x) / 2;
+                                this.layoutDoc[this.panYFieldKey] = (cbounds.b + cbounds.y) / 2;
                                 this.layoutDoc.x = pbounds.x;
                                 this.layoutDoc.y = pbounds.y;
                             }
@@ -1634,8 +1647,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
             const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
             if (deltaX !== 0 || deltaY !== 0) {
-                this.Document._panY = NumCast(this.Document._panY) + deltaY / 2;
-                this.Document._panX = NumCast(this.Document._panX) + deltaX / 2;
+                this.Document[this.panYFieldKey] = NumCast(this.Document[this.panYFieldKey]) + deltaY / 2;
+                this.Document[this.panXFieldKey] = NumCast(this.Document[this.panXFieldKey]) + deltaX / 2;
             }
         }
         e.stopPropagation();
@@ -1659,8 +1672,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20;
         const dim = Math.ceil(Math.sqrt(docs.length));
         docs.forEach((doc, i) => {
-            doc.x = NumCast(this.Document._panX) + (i % dim) * width - (width * dim) / 2;
-            doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - (height * dim) / 2;
+            doc.x = NumCast(this.Document[this.panXFieldKey]) + (i % dim) * width - (width * dim) / 2;
+            doc.y = NumCast(this.Document[this.panYFieldKey]) + Math.floor(i / dim) * height - (height * dim) / 2;
         });
     };
 
@@ -1675,7 +1688,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         appearanceItems.push({
             description: 'Reset View',
             event: () => {
-                this.props.Document._panX = this.props.Document._panY = 0;
+                this.props.Document[this.panXFieldKey] = this.props.Document[this.panYFieldKey] = 0;
                 this.props.Document[this.scaleFieldKey] = 1;
             },
             icon: 'compress-arrows-alt',
@@ -1795,11 +1808,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
     });
 
-    children = () => {
+    get children() {
         this.incrementalRender();
-        const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : [];
+        const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : this.props.children ? [this.props.children] : [];
         return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />];
-    };
+    }
 
     @computed get placeholder() {
         return (
@@ -1843,6 +1856,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                                 PanelHeight={this.props.PanelHeight}
                                 panX={this.panX}
                                 panY={this.panY}
+                                nativeDimScaling={this.nativeDim}
                                 zoomScaling={this.zoomScaling}
                                 layoutDoc={this.layoutDoc}
                                 isAnnotationOverlay={this.isAnnotationOverlay}
@@ -1877,6 +1891,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale || this.layoutDoc.fitWidth ? wscale : hscale;
     }
+    nativeDim = () => this.nativeDimScaling;
 
     private groupDropDisposer?: DragManager.DragDropDisposer;
     protected createGroupEventsTarget = (ele: HTMLDivElement) => {
@@ -1912,7 +1927,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 ref={r => {
                     this.createDashEventsTarget(r);
                     // prevent wheel events from passivly propagating up through containers
-                    r?.addEventListener('wheel', (e: WheelEvent) => this.props.isSelected() && e.preventDefault(), { passive: false });
+                    !this.props.isAnnotationOverlay && r?.addEventListener('wheel', (e: WheelEvent) => this.props.isSelected() && e.preventDefault(), { passive: false });
                 }}
                 onWheel={this.onPointerWheel}
                 onClick={this.onClick}
@@ -1985,7 +2000,8 @@ interface CollectionFreeFormViewPannableContentsProps {
     transform: () => string;
     zoomScaling: () => number;
     viewDefDivClick?: ScriptField;
-    children: () => JSX.Element[];
+    children?: React.ReactNode | undefined;
+    //children: () => JSX.Element[];
     transition?: string;
     presPaths: () => JSX.Element | null;
     presPinView?: boolean;
@@ -2079,7 +2095,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
                     width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable.  This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
                     //willChange: "transform"
                 }}>
-                {this.props.children()}
+                {this.props.children}
                 {!this.props.brushView.width ? null : (
                     <div
                         className="collectionFreeFormView-brushView"
@@ -2107,6 +2123,7 @@ interface CollectionFreeFormViewBackgroundGridProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     isAnnotationOverlay?: boolean;
+    nativeDimScaling: () => number;
     zoomScaling: () => number;
     layoutDoc: Doc;
     cachedCenteringShiftX: number;
@@ -2124,10 +2141,10 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
         const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling();
         const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling();
         const renderGridSpace = gridSpace * this.props.zoomScaling();
-        const w = this.props.PanelWidth() + 2 * renderGridSpace;
-        const h = this.props.PanelHeight() + 2 * renderGridSpace;
+        const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace;
+        const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace;
         const strokeStyle = Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'rgba(255,255,255,0.5)' : 'rgba(0, 0,0,0.5)';
-        return (
+        return !this.props.nativeDimScaling() ? null : (
             <canvas
                 className="collectionFreeFormView-grid"
                 width={w}
@@ -2174,7 +2191,7 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
         const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
         let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
         while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
-        const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
+        const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
         ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime);
         Doc.linkFollowHighlight(dv?.props.Document, false);
     } else {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index f16371592..043fe0bcf 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -237,6 +237,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
+        // if (this.props.pointerEvents?.() === 'none') return;
         this._downX = this._lastX = e.clientX;
         this._downY = this._lastY = e.clientY;
         if (!(e.nativeEvent as any).marqueeHit) {
@@ -345,6 +346,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @action
     onClick = (e: React.MouseEvent): void => {
+        if (this.props.pointerEvents?.() === 'none') return;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
             if (Doc.ActiveTool === InkTool.None) {
                 if (!(e.nativeEvent as any).marqueeHit) {
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 9468c5f06..e8ae88ae5 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
@@ -186,7 +186,10 @@ export class CollectionGridView extends CollectionSubView() {
     getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
         return (
             <DocumentView
-                {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+                {...this.props}
+                NativeWidth={returnZero}
+                NativeHeight={returnZero}
+                setContentView={emptyFunction}
                 Document={layout}
                 DataDoc={layout.resolvedDataDoc as Doc}
                 isContentActive={this.isChildContentActive}
@@ -196,7 +199,7 @@ export class CollectionGridView extends CollectionSubView() {
                 whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
                 onClick={this.onChildClickHandler}
                 renderDepth={this.props.renderDepth + 1}
-                dontCenter={this.props.Document.centerY ? '' : 'y'}
+                dontCenter={this.props.Document.centerY ? undefined : 'y'}
             />
         );
     }
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index d47c9762c..fd9bcf681 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,23 +1,20 @@
 import React = require('react');
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, ObservableMap, trace, untracked } from 'mobx';
+import { action, computed, observable, ObservableMap, untracked } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, DocListCast, Field, StrListCast } from '../../../../fields/Doc';
+import { computedFn } from 'mobx-utils';
+import { Doc, Field, StrListCast } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
-import { RichTextField } from '../../../../fields/RichTextField';
 import { listSpec } from '../../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { ImageField } from '../../../../fields/URLField';
 import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { SelectionManager } from '../../../util/SelectionManager';
-import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
-import { ContextMenuProps } from '../../ContextMenuItem';
 import { EditableView } from '../../EditableView';
 import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
@@ -215,9 +212,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    addNewKey = (key: string, defaultVal: any) => {
-        this.childDocs.forEach(doc => (doc[key] = defaultVal));
-    };
+    addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[key] = defaultVal));
 
     @undoBatch
     @action
@@ -303,9 +298,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    addRowRef = (doc: Doc, ref: HTMLDivElement) => {
-        this._rowEles.set(doc, ref);
-    };
+    addRowRef = (doc: Doc, ref: HTMLDivElement) => this._rowEles.set(doc, ref);
 
     @action
     setColRef = (index: number, ref: HTMLDivElement) => {
@@ -405,68 +398,12 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     menuCallback = (x: number, y: number) => {
         ContextMenu.Instance.clearItems();
-        const layoutItems: ContextMenuProps[] = [];
-        const docItems: ContextMenuProps[] = [];
-        const dataDoc = this.props.DataDoc || this.props.Document;
-
-        DocUtils.addDocumentCreatorMenuItems(
-            doc => {
-                FormattedTextBox.SelectOnLoad = StrCast(doc[Id]);
-                return this.addRow(doc);
-            },
-            this.addRow,
-            x,
-            y,
-            true
-        );
 
-        Array.from(Object.keys(Doc.GetProto(dataDoc)))
-            .filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof dataDoc[fieldKey] === 'string')
-            .map(fieldKey =>
-                docItems.push({
-                    description: ':' + fieldKey,
-                    event: () => {
-                        const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
-                        if (created) {
-                            if (this.props.Document.isTemplateDoc) {
-                                Doc.MakeMetadataFieldTemplate(created, this.props.Document);
-                            }
-                            return this.addRow(created);
-                        }
-                    },
-                    icon: 'compress-arrows-alt',
-                })
-            );
-        Array.from(Object.keys(Doc.GetProto(dataDoc)))
-            .filter(fieldKey => DocListCast(dataDoc[fieldKey]).length)
-            .map(fieldKey =>
-                docItems.push({
-                    description: ':' + fieldKey,
-                    event: () => {
-                        const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
-                        if (created) {
-                            const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
-                            if (container.isTemplateDoc) {
-                                Doc.MakeMetadataFieldTemplate(created, container);
-                                return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
-                            }
-                            return this.addRow(created) || false;
-                        }
-                    },
-                    icon: 'compress-arrows-alt',
-                })
-            );
-        !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
-        !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
+        DocUtils.addDocumentCreatorMenuItems(doc => this.addRow(doc), this.addRow, x, y, true);
+
         ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
             Doc.GetProto(this.props.Document)[name] = '';
-            const created = Docs.Create.TextDocument('', { title: name, _autoHeight: true });
-            if (created) {
-                if (this.props.Document.isTemplateDoc) {
-                    Doc.MakeMetadataFieldTemplate(created, this.props.Document);
-                }
-                this.addRow(created);
-            }
+            this.addRow(Docs.Create.TextDocument('', { title: name, _autoHeight: true }));
         });
         ContextMenu.Instance.displayMenu(x, y, undefined, true);
     };
@@ -543,9 +480,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    closeColumnMenu = () => {
-        this._columnMenuIndex = undefined;
-    };
+    closeColumnMenu = () => (this._columnMenuIndex = undefined);
 
     @action
     openFilterMenu = (index: number) => {
@@ -596,19 +531,14 @@ export class CollectionSchemaView extends CollectionSubView() {
     getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
 
     removeFieldFilters = (field: string) => {
-        this.getFieldFilters(field).forEach(filter => {
-            Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove');
-        });
+        this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove'));
     };
 
     onFilterKeyDown = (e: React.KeyboardEvent) => {
+        //prettier-ignore
         switch (e.key) {
-            case 'Enter':
-                this.closeFilterMenu(true);
-                break;
-            case 'Escape':
-                this.closeFilterMenu(false);
-                break;
+            case 'Enter' : this.closeFilterMenu(true); break;
+            case 'Escape': this.closeFilterMenu(false);break;
         }
     };
 
@@ -903,6 +833,7 @@ interface CollectionSchemaViewDocsProps {
 class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsProps> {
     tableWidthFunc = () => this.props.schema.tableWidth;
     rowHeightFunc = () => CollectionSchemaView._rowHeight;
+    childScreenToLocal = computedFn((index: number) => () => this.props.schema.props.ScreenToLocalTransform().translate(0, -CollectionSchemaView._rowHeight - index * this.rowHeightFunc()));
     render() {
         return (
             <div className="schema-table-content">
@@ -932,7 +863,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
                                 docRangeFilters={this.props.schema.childDocRangeFilters}
                                 searchFilterDocs={this.props.schema.searchFilterDocs}
                                 rootSelected={this.props.schema.rootSelected}
-                                ScreenToLocalTransform={Transform.Identity}
+                                ScreenToLocalTransform={this.childScreenToLocal(index)}
                                 bringToFront={emptyFunction}
                                 isDocumentActive={this.props.schema.props.childDocumentsActive?.() ? this.props.schema.props.isDocumentActive : this.props.schema.isContentActive}
                                 isContentActive={emptyFunction}
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index cbdaeb933..d88d67c94 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -39,18 +39,12 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
 
     @action
     onPointerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction);
+        setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction, false);
     };
 
     render() {
         return (
-            <div
-                className="schema-column-header"
-                style={{ width: this.props.columnWidths[this.props.columnIndex] }}
-                onPointerDown={this.onPointerDown}
-                ref={(col: HTMLDivElement | null) => {
-                    col && this.props.setColRef(this.props.columnIndex, col);
-                }}>
+            <div className="schema-column-header" style={{ width: this.props.columnWidths[this.props.columnIndex] }} onPointerDown={this.onPointerDown} ref={col => col && this.props.setColRef(this.props.columnIndex, col)}>
                 <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)}></div>
                 <div className="schema-column-title">{this.fieldKey}</div>
 
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 384975b45..a6acf882c 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -7,7 +7,7 @@ import { Doc, DocListCast } from '../../../fields/Doc';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DateCast, NumCast } from '../../../fields/Types';
 import { AudioField, nullAudio } from '../../../fields/URLField';
-import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { Networking } from '../../Network';
 import { DragManager } from '../../util/DragManager';
@@ -19,7 +19,6 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import './AudioBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
-import { SelectionManager } from '../../util/SelectionManager';
 import { PinProps, PresBox } from './trails';
 
 /**
@@ -655,7 +654,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return (
             <CollectionStackedTimeline
                 ref={action((r: any) => (this._stackedTimeline = r))}
-                {...OmitKeys(this.props, ['CollectionFreeFormDocumentView']).omit}
+                {...this.props}
+                CollectionFreeFormDocumentView={undefined}
                 fieldKey={this.annotationKey}
                 dictationKey={this.fieldKey + '-dictation'}
                 mediaPath={this.path}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index ecffe6c4f..ace388c57 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -3,7 +3,7 @@ import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, OmitKeys, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -122,7 +122,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                         ref={r => {
                             //whichDoc !== targetDoc && r?.focus(whichDoc, { instant: true });
                         }}
-                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+                        {...this.props}
+                        NativeWidth={returnZero}
+                        NativeHeight={returnZero}
                         isContentActive={returnFalse}
                         isDocumentActive={returnFalse}
                         styleProvider={this.docStyleProvider}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 091a6b050..c7bf37a45 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -12,7 +12,7 @@ import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
-import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { DocServer } from '../../DocServer';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -512,7 +512,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 this.props.isDocumentActive?.() &&
                 !this.props.onBrowseClick?.() &&
                 !this.Document.ignoreClick &&
-                !e.ctrlKey &&
                 e.button === 0 &&
                 this.pointerEvents !== 'none' &&
                 !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)
@@ -705,22 +704,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const appearance = cm.findByDescription('UI Controls...');
             const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
             !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
-            !Doc.noviceMode &&
-                appearanceItems.push({
-                    description: 'Add a Field',
-                    event: () => {
-                        const alias = Doc.MakeAlias(this.rootDoc);
-                        alias.layout = FormattedTextBox.LayoutString('newfield');
-                        alias.title = 'newfield';
-                        alias._height = 35;
-                        alias._width = 100;
-                        alias.syncLayoutFieldWithTitle = true;
-                        alias.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc.width);
-                        alias.y = NumCast(this.rootDoc.y);
-                        this.props.addDocument?.(alias);
-                    },
-                    icon: 'eye',
-                });
             LinkManager.Links(this.Document).length &&
                 appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? 'Show' : 'Hide'} Link Button`, event: action(() => (this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton)), icon: 'eye' });
             !appearance && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
@@ -863,7 +846,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
     setHeight = (height: number) => (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
-    isContentActive = (outsideReaction?: boolean) => {
+    isContentActive = (outsideReaction?: boolean): boolean | undefined => {
         return this.props.isContentActive() === false
             ? false
             : Doc.ActiveTool !== InkTool.None ||
@@ -1064,7 +1047,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     };
 
-    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
+    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
     @computed get innards() {
         TraceMobx();
         const ffscale = () => this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1;
@@ -1079,7 +1062,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     maxHeight: `max(100%, ${20 * ffscale()}px)`,
                 }}>
                 <FormattedTextBox
-                    {...OmitKeys(this.props, ['children']).omit}
+                    {...this.props}
                     yPadding={10}
                     xPadding={10}
                     fieldKey={this.showCaption}
@@ -1090,7 +1073,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     dontScale={true}
                     renderDepth={this.props.renderDepth}
                     isContentActive={this.isContentActive}
-                    onClick={this.onClickFunc}
                 />
             </div>
         );
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index e53422ab7..8d3534a5c 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -20,7 +20,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
 
     select: (isCtrlPressed: boolean) => void;
     isContentActive: (outsideReaction?: boolean) => boolean | undefined;
-    isDocumentActive?: () => boolean;
+    isDocumentActive?: () => boolean | undefined;
     isSelected: (outsideReaction?: boolean) => boolean;
     setHeight?: (height: number) => void;
     NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 48e54b722..b5193cd20 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -11,7 +11,7 @@ import { ComputedField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { DashColor, emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
+import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -409,8 +409,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         );
     }
 
-    contentFunc = () => [this.content];
-
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     @observable _marqueeing: number[] | undefined;
@@ -473,7 +471,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 }}>
                 <CollectionFreeFormView
                     ref={this._ffref}
-                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                    {...this.props}
+                    setContentView={emptyFunction}
+                    NativeWidth={returnZero}
+                    NativeHeight={returnZero}
                     renderDepth={this.props.renderDepth + 1}
                     fieldKey={this.annotationKey}
                     styleProvider={this.props.styleProvider}
@@ -487,11 +488,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     focus={this.focus}
                     getScrollHeight={this.getScrollHeight}
                     NativeDimScaling={returnOne}
+                    isAnyChildContentActive={returnFalse}
                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     removeDocument={this.removeDocument}
                     moveDocument={this.moveDocument}
                     addDocument={this.addDocument}>
-                    {this.contentFunc}
+                    {this.content}
                 </CollectionFreeFormView>
                 {this.annotationLayer}
                 {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index ea8f47b39..36be7d257 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -8,7 +8,7 @@ import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
@@ -635,7 +635,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                 .map(marker => (
                                     <MapBoxInfoWindow
                                         key={marker[Id]}
-                                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                                        {...this.props}
+                                        setContentView={emptyFunction}
                                         place={marker}
                                         markerMap={this.markerMap}
                                         PanelWidth={this.infoWidth}
@@ -674,6 +675,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         rootDoc={this.rootDoc}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
+                        usePanelWidth={true}
                         showSidebar={this.SidebarShown}
                         nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 00bedafbe..d65ef9c4c 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
-import { emptyFunction, OmitKeys, returnAll, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction, returnAll, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView';
@@ -52,7 +52,8 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
                     <div style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }}>
                         <CollectionStackingView
                             ref={r => (this._stack = r)}
-                            {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                            {...this.props}
+                            setContentView={emptyFunction}
                             Document={this.props.place}
                             DataDoc={undefined}
                             fieldKey="data"
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 5f207cc0d..a254edd87 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -5,16 +5,20 @@ import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
-import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { InkTool } from '../../../fields/InkField';
+import { ComputedField } from '../../../fields/ScriptField';
+import { Cast, FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField, PdfField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { KeyCodes } from '../../util/KeyCodes';
+import { SelectionManager } from '../../util/SelectionManager';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm';
+import { CollectionStackingView } from '../collections/CollectionStackingView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
@@ -41,7 +45,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _initialScrollTarget: Opt<Doc>;
     private _pdfViewer: PDFViewer | undefined;
     private _searchRef = React.createRef<HTMLInputElement>();
-    private _selectReactionDisposer: IReactionDisposer | undefined;
+    private _disposers: { [name: string]: IReactionDisposer } = {};
     private _sidebarRef = React.createRef<SidebarAnnos>();
 
     @observable private _searching: boolean = false;
@@ -184,11 +188,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     componentWillUnmount() {
-        this._selectReactionDisposer?.();
+        Object.values(this._disposers).forEach(disposer => disposer?.());
     }
     componentDidMount() {
         this.props.setContentView?.(this);
-        this._selectReactionDisposer = reaction(
+        this._disposers.select = reaction(
             () => this.props.isSelected(),
             () => {
                 document.removeEventListener('keydown', this.onKeyDown);
@@ -196,6 +200,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             },
             { fireImmediately: true }
         );
+        this._disposers.scroll = reaction(
+            () => this.rootDoc.scrollTop,
+            () => {
+                if (!(ComputedField.WithoutComputed(() => FieldValue(this.props.Document[this.SidebarKey + '-panY'])) instanceof ComputedField)) {
+                    this.props.Document[this.SidebarKey + '-panY'] = ComputedField.MakeFunction('this.scrollTop');
+                }
+                this.props.Document[this.SidebarKey + '-viewScale'] = 1;
+                this.props.Document[this.SidebarKey + '-panX'] = 0;
+            }
+        );
     }
 
     brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._pdfViewer?.brushView(view);
@@ -303,7 +317,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     // adding external documents; to sidebar key
     // if (doc.Geolocation) this.addDocument(doc, this.fieldkey+"-annotation")
-    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
         if (!this.layoutDoc._showSidebar) this.toggleSidebar();
         return this.addDocument(doc, sidebarKey);
     };
@@ -428,6 +442,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
+        funcs.push({
+            description: 'Toggle Sidebar Type',
+            event: () => (this.rootDoc.sidebarViewType = this.rootDoc.sidebarViewType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform),
+            icon: 'expand-arrows-alt',
+        });
         funcs.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
         funcs.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' });
         //funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
@@ -467,12 +486,87 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
     }
 
+    public get SidebarKey() {
+        return this.fieldKey + '-sidebar';
+    }
+    @computed get pdfScale() {
+        const pdfNativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']);
+        const nativeWidth = NumCast(this.layoutDoc.nativeWidth, pdfNativeWidth);
+        const pdfRatio = pdfNativeWidth / nativeWidth;
+        return (pdfRatio * this.props.PanelWidth()) / pdfNativeWidth;
+    }
+    @computed get sidebarNativeWidth() {
+        return this.sidebarWidth() / this.pdfScale;
+    }
+    @computed get sidebarNativeHeight() {
+        return this.props.PanelHeight() / this.pdfScale;
+    }
+    sidebarNativeWidthFunc = () => this.sidebarNativeWidth;
+    sidebarNativeHeightFunc = () => this.sidebarNativeHeight;
+    sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
+    sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
+    sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate((this.sidebarWidth() - this.props.PanelWidth()) / this.pdfScale, 0);
+    @computed get sidebarCollection() {
+        const renderComponent = (tag: string) => {
+            const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : CollectionStackingView;
+            return ComponentTag === CollectionStackingView ? (
+                <SidebarAnnos
+                    ref={this._sidebarRef}
+                    {...this.props}
+                    rootDoc={this.rootDoc}
+                    layoutDoc={this.layoutDoc}
+                    dataDoc={this.dataDoc}
+                    setHeight={emptyFunction}
+                    nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
+                    showSidebar={this.SidebarShown}
+                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                    sidebarAddDocument={this.sidebarAddDocument}
+                    moveDocument={this.moveDocument}
+                    removeDocument={this.removeDocument}
+                    //PanelWidth={this.sidebarWidth}
+                    //ScreenToLocalTransform={this.sidebarScreenToLocal}
+                />
+            ) : (
+                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
+                    <ComponentTag
+                        {...this.props}
+                        setContentView={emptyFunction} // override setContentView to do nothing
+                        NativeWidth={this.sidebarNativeWidthFunc}
+                        NativeHeight={this.sidebarNativeHeightFunc}
+                        PanelHeight={this.props.PanelHeight}
+                        PanelWidth={this.sidebarWidth}
+                        xPadding={0}
+                        yPadding={0}
+                        viewField={this.SidebarKey}
+                        isAnnotationOverlay={false}
+                        originTopLeft={true}
+                        isAnyChildContentActive={this.isAnyChildContentActive}
+                        select={emptyFunction}
+                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                        removeDocument={this.sidebarRemDocument}
+                        moveDocument={this.sidebarMoveDocument}
+                        addDocument={this.sidebarAddDocument}
+                        CollectionView={undefined}
+                        ScreenToLocalTransform={this.sidebarScreenToLocal}
+                        renderDepth={this.props.renderDepth + 1}
+                        noSidebar={true}
+                        fieldKey={this.SidebarKey}
+                    />
+                </div>
+            );
+        };
+        return (
+            <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: '100%', right: 0, backgroundColor: `white` }}>
+                {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
+            </div>
+        );
+    }
     isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected() || (this.props.renderDepth === 0 && LightboxView.IsLightboxDocView(this.props.docViewPath()));
     @computed get renderPdfView() {
         TraceMobx();
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
-        return (
+        return !this._pdf ? null : (
             <div
                 className={'pdfBox'}
                 onContextMenu={this.specificContextMenu}
@@ -496,7 +590,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         addDocTab={this.sidebarAddDocTab}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
-                        pdf={this._pdf!}
+                        pdf={this._pdf}
                         focus={this.focus}
                         url={this.pdfUrl!.url.pathname}
                         isContentActive={this.isPdfContentActive}
@@ -510,22 +604,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         crop={this.crop}
                     />
                 </div>
-                <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.props.PanelWidth()}%` }}>
-                    <SidebarAnnos
-                        ref={this._sidebarRef}
-                        {...this.props}
-                        rootDoc={this.rootDoc}
-                        layoutDoc={this.layoutDoc}
-                        dataDoc={this.dataDoc}
-                        setHeight={emptyFunction}
-                        nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
-                        showSidebar={this.SidebarShown}
-                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                        sidebarAddDocument={this.sidebarAddDocument}
-                        moveDocument={this.moveDocument}
-                        removeDocument={this.removeDocument}
-                    />
-                </div>
+                <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.props.PanelWidth()}%` }}>{this.sidebarCollection}</div>
                 {this.settingsPanel()}
             </div>
         );
@@ -535,21 +614,17 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     static pdfpromise = new Map<string, Promise<Pdfjs.PDFDocumentProxy>>();
     render() {
         TraceMobx();
-        if (this._pdf) {
-            if (!this.props.thumbShown?.()) {
-                return this.renderPdfView;
-            }
-            return null;
-        }
+        if (this.props.thumbShown?.()) return null;
+        const pdfView = this.renderPdfView;
 
         const href = this.pdfUrl?.url.href;
-        if (href) {
+        if (!pdfView && href) {
             if (PDFBox.pdfcache.get(href)) setTimeout(action(() => (this._pdf = PDFBox.pdfcache.get(href))));
             else {
                 if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise);
                 PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, (this._pdf = pdf))));
             }
         }
-        return this.renderTitleBox;
+        return pdfView ?? this.renderTitleBox;
     }
 }
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 61e4894f0..db11a7776 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -11,7 +11,7 @@ import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, NumCast } from '../../../fields/Types';
 import { AudioField, VideoField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, OmitKeys, returnFalse, returnOne } from '../../../Utils';
+import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
@@ -277,7 +277,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         dictationTextProto.mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState');
         this.dataDoc[this.fieldKey + '-dictation'] = dictationText;
     };
-    contentFunc = () => [this.threed, this.content];
     videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '-nativeHeight'], this.layoutDoc[HeightSym]()) / NumCast(this.dataDoc[this.fieldKey + '-nativeWidth'], this.layoutDoc[WidthSym]())) * this.props.PanelWidth();
     formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
     render() {
@@ -287,7 +286,10 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                 <div className="videoBox-viewer">
                     <div style={{ position: 'relative', height: this.videoPanelHeight() }}>
                         <CollectionFreeFormView
-                            {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+                            {...this.props}
+                            setContentView={emptyFunction}
+                            NativeWidth={returnZero}
+                            NativeHeight={returnZero}
                             PanelHeight={this.videoPanelHeight}
                             PanelWidth={this.props.PanelWidth}
                             focus={this.props.focus}
@@ -296,6 +298,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                             select={emptyFunction}
                             isContentActive={returnFalse}
                             NativeDimScaling={returnOne}
+                            isAnyChildContentActive={returnFalse}
                             whenChildContentsActiveChanged={emptyFunction}
                             removeDocument={returnFalse}
                             moveDocument={returnFalse}
@@ -304,17 +307,19 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform}
                             renderDepth={this.props.renderDepth + 1}
                             ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
-                            {this.contentFunc}
+                            <>
+                                {this.threed}
+                                {this.content}
+                            </>
                         </CollectionFreeFormView>
                     </div>
                     <div style={{ position: 'relative', height: this.formattedPanelHeight() }}>
                         {!(this.dataDoc[this.fieldKey + '-dictation'] instanceof Doc) ? null : (
                             <FormattedTextBox
-                                {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+                                {...this.props}
                                 Document={this.dataDoc[this.fieldKey + '-dictation']}
                                 fieldKey={'text'}
                                 PanelHeight={this.formattedPanelHeight}
-                                isAnnotationOverlay={true}
                                 select={emptyFunction}
                                 isContentActive={emptyFunction}
                                 NativeDimScaling={returnOne}
@@ -324,7 +329,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                                 removeDocument={returnFalse}
                                 moveDocument={returnFalse}
                                 addDocument={returnFalse}
-                                CollectionView={undefined}
                                 renderDepth={this.props.renderDepth + 1}
                                 ContainingCollectionDoc={this.props.ContainingCollectionDoc}></FormattedTextBox>
                         )}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 0b88f5fe3..0570c7fcb 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -9,7 +9,7 @@ import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
@@ -894,8 +894,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     playing = () => this._playing;
 
-    contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
-
     scaling = () => this.props.NativeDimScaling?.() || 1;
 
     panelWidth = () => (this.props.PanelWidth() * this.heightPercent) / 100;
@@ -1068,7 +1066,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             left: (this.props.PanelWidth() - this.panelWidth()) / 2,
                         }}>
                         <CollectionFreeFormView
-                            {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                            {...this.props}
+                            setContentView={emptyFunction}
+                            NativeWidth={returnZero}
+                            NativeHeight={returnZero}
                             renderDepth={this.props.renderDepth + 1}
                             fieldKey={this.annotationKey}
                             CollectionView={undefined}
@@ -1076,6 +1077,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             annotationLayerHostsContent={true}
                             PanelWidth={this.panelWidth}
                             PanelHeight={this.panelHeight}
+                            isAnyChildContentActive={returnFalse}
                             ScreenToLocalTransform={this.screenToLocalTransform}
                             docFilters={this.timelineDocFilter}
                             select={emptyFunction}
@@ -1084,7 +1086,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             removeDocument={this.removeDocument}
                             moveDocument={this.moveDocument}
                             addDocument={this.addDocWithTimecode}>
-                            {this.contentFunc}
+                            {this.youtubeVideoId ? this.youtubeContent : this.content}
                         </CollectionFreeFormView>
                     </div>
                     {this.annotationLayer}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index d57518a8d..66d0fd385 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema';
 import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField, WebField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils';
+import { emptyFunction, getWordAtPoint, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
@@ -305,7 +305,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), anchor[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[HeightSym](), this._scrollHeight));
             if (scrollTo !== undefined) {
                 if (this._initialScroll === undefined) {
-                    this.goTo(scrollTo, options.zoomTime ?? 500, options.easeFunc);
+                    const focusTime = options.zoomTime ?? 500;
+                    this.goTo(scrollTo, focusTime, options.easeFunc);
+                    return focusTime;
                 } else {
                     this._initialScroll = scrollTo;
                 }
@@ -904,9 +906,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
         const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
-        const renderAnnotations = (docFilters?: () => string[]) => (
+        const renderAnnotations = (docFilters: () => string[]) => (
             <CollectionFreeFormView
-                {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                {...this.props}
+                setContentView={emptyFunction}
+                NativeWidth={returnZero}
+                NativeHeight={returnZero}
                 renderDepth={this.props.renderDepth + 1}
                 isAnnotationOverlay={true}
                 fieldKey={this.annotationKey}
@@ -921,6 +926,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 dropAction={'alias'}
                 docFilters={docFilters}
                 select={emptyFunction}
+                isAnyChildContentActive={returnFalse}
                 bringToFront={emptyFunction}
                 styleProvider={this.childStyleProvider}
                 whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ee4249b02..d8afc3227 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -20,7 +20,7 @@ import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { ComputedField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
@@ -1739,12 +1739,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             }
         }
     };
-    fitContentsToBox = () => this.props.Document._fitContentsToBox;
+    fitContentsToBox = () => BoolCast(this.props.Document._fitContentsToBox);
     sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
     sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
         if (!this.layoutDoc._showSidebar) this.toggleSidebar();
-        // console.log("printting allSideBarDocs");
-        // console.log(this.allSidebarDocs);
         return this.addDocument(doc, sidebarKey);
     };
     sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
@@ -1785,8 +1783,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 className="formattedTextBox-sidebar-handle"
                 onPointerDown={this.sidebarDown}
                 style={{
-                    backgroundColor: backgroundColor,
-                    color: color,
+                    backgroundColor,
+                    color,
                     opacity: annotated ? 1 : undefined,
                 }}>
                 <FontAwesomeIcon icon={'comment-alt'} />
@@ -1800,33 +1798,36 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 <SidebarAnnos
                     ref={this._sidebarRef}
                     {...this.props}
-                    fieldKey={this.fieldKey}
                     rootDoc={this.rootDoc}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
-                    ScreenToLocalTransform={this.sidebarScreenToLocal}
+                    usePanelWidth={true}
                     nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
-                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     showSidebar={this.SidebarShown}
-                    PanelWidth={this.sidebarWidth}
-                    setHeight={this.setSidebarHeight}
+                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     sidebarAddDocument={this.sidebarAddDocument}
                     moveDocument={this.moveDocument}
                     removeDocument={this.removeDocument}
+                    ScreenToLocalTransform={this.sidebarScreenToLocal}
+                    fieldKey={this.fieldKey}
+                    PanelWidth={this.sidebarWidth}
+                    setHeight={this.setSidebarHeight}
                 />
             ) : (
                 <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
                     <ComponentTag
-                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                        {...this.props}
+                        setContentView={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
                         PanelHeight={this.props.PanelHeight}
                         PanelWidth={this.sidebarWidth}
                         xPadding={0}
                         yPadding={0}
-                        scaleField={this.SidebarKey + '-scale'}
+                        viewField={this.SidebarKey}
                         isAnnotationOverlay={false}
                         select={emptyFunction}
+                        isAnyChildContentActive={returnFalse}
                         NativeDimScaling={this.sidebarContentScaling}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                         removeDocument={this.sidebarRemDocument}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index c5060a2c2..6ab541207 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -7,7 +7,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, returnNone, smoothScroll, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
@@ -16,7 +16,7 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
-import { DocFocusOptions, DocumentViewProps, OpenWhere } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
 import { StyleProp } from '../StyleProvider';
@@ -512,7 +512,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return this.props.styleProvider?.(doc, props, property);
     };
 
-    renderAnnotations = (docFilters?: () => string[], mixBlendMode?: any, display?: string) => (
+    renderAnnotations = (docFilters: () => string[], mixBlendMode?: any, display?: string) => (
         <div
             className="pdfViewerDash-overlay"
             style={{
@@ -522,9 +522,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined,
             }}>
             <CollectionFreeFormView
-                {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                {...this.props}
+                NativeWidth={returnZero}
+                NativeHeight={returnZero}
+                setContentView={emptyFunction} // override setContentView to do nothing
+                pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone}
                 renderDepth={this.props.renderDepth + 1}
-                pointerEvents={returnNone}
                 isAnnotationOverlay={true}
                 fieldKey={this.props.fieldKey + '-annotations'}
                 CollectionView={undefined}
@@ -534,6 +537,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 PanelHeight={this.panelHeight}
                 PanelWidth={this.panelWidth}
                 ScreenToLocalTransform={this.overlayTransform}
+                isAnyChildContentActive={returnFalse}
+                isAnnotationOverlayScrollable={true}
                 dropAction={'alias'}
                 docFilters={docFilters}
                 select={emptyFunction}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 553c4525c..cc024d83a 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1213,7 +1213,7 @@ export namespace Doc {
         return doc[StrCast(doc.layoutKey, 'layout')];
     }
     export function LayoutFieldKey(doc: Doc): string {
-        return StrCast(Doc.Layout(doc).layout).split("'")[1];
+        return StrCast(Doc.Layout(doc)[StrCast(doc.layoutKey, 'layout')]).split("'")[1]; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layoutKey
     }
     export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
@@ -1222,9 +1222,10 @@ export namespace Doc {
         return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeWidth'], useWidth ? doc[WidthSym]() : 0));
     }
     export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
-        const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeHeight'], useHeight ? doc[HeightSym]() : 0) : 0;
-        const nheight = doc ? (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]()) / doc[WidthSym]() : 0;
-        return !doc ? 0 : NumCast(doc._nativeHeight, nheight || dheight);
+        if (!doc) return 0;
+        const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]()) / doc[WidthSym]();
+        const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeHeight'], useHeight ? doc[HeightSym]() : 0);
+        return NumCast(doc._nativeHeight, nheight || dheight);
     }
     export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
         doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeWidth'] = width;
-- 
cgit v1.2.3-70-g09d2

