From d5f60e34e8ac9f99c0b442346db5386068af5de2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 23 Mar 2023 10:06:39 -0400
Subject: changed showing keyvalue panes to not create a document, but to use
 the LayoutTemplateString.

---
 src/client/views/collections/TabDocView.tsx | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 0ab94e2e3..631b9add5 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -35,10 +35,12 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionView } from './CollectionView';
 import './TabDocView.scss';
 import React = require('react');
+import { KeyValueBox } from '../nodes/KeyValueBox';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface TabDocViewProps {
     documentId: FieldId;
+    keyValue?: boolean;
     glContainer: any;
 }
 @observer
@@ -348,7 +350,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     addDocTab = (doc: Doc, location: OpenWhere) => {
         SelectionManager.DeselectAll();
         const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':');
-        const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
+        const keyValue = whereFields[1]?.includes('KeyValue');
+        const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none;
         if (doc.dockingConfig) return DashboardView.openDashboard(doc);
         // prettier-ignore
         switch (whereFields[0]) {
@@ -365,9 +368,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             case OpenWhere.dashboard:  return DashboardView.openDashboard(doc);
             case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc);
             case OpenWhere.close:      return CollectionDockingView.CloseSplit(doc, whereMods);
-            case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack);
-            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack);
-            case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack);
+            case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, undefined, keyValue);
+            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, undefined, keyValue);
+            case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue);
         }
     };
     remDocTab = (doc: Doc | Doc[]) => {
@@ -419,6 +422,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                         this._lastView = this._view;
                     })}
                     renderDepth={0}
+                    LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString('data') : undefined}
                     Document={this._document}
                     DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
                     ContainingCollectionView={undefined}
-- 
cgit v1.2.3-70-g09d2


From 8ab9236664c561d54d6a41ecb1eb2eaf6064fc0c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 27 Mar 2023 18:21:13 -0400
Subject: changed longPress to always select and to show decorations.  fixed
 single/double-click code and cleaned up behavior timeouts.  fixed pointer
 events for tree view editing titles and using as powerpoint.

---
 src/client/documents/Documents.ts                  |  11 +-
 src/client/util/DragManager.ts                     |  34 ++--
 src/client/views/DocumentDecorations.tsx           |   3 +-
 src/client/views/InkingStroke.tsx                  |   1 -
 src/client/views/MainView.tsx                      |  17 ++
 .../collections/CollectionNoteTakingViewColumn.tsx |   4 +-
 src/client/views/collections/TabDocView.tsx        |   2 +-
 src/client/views/collections/TreeView.tsx          |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   3 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   6 +-
 src/client/views/nodes/DocumentView.tsx            | 197 ++++++++++-----------
 src/client/views/nodes/KeyValueBox.tsx             |  24 ++-
 src/client/views/nodes/LinkBox.tsx                 |  54 +++---
 src/client/views/nodes/ScriptingBox.tsx            |   4 +-
 src/client/views/nodes/WebBox.tsx                  |   3 +-
 src/client/views/nodes/button/FontIconBox.tsx      |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  11 +-
 src/client/views/pdf/AnchorMenu.tsx                |   2 +-
 18 files changed, 201 insertions(+), 183 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0b6d4380d..2bc1b5b1d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -228,7 +228,8 @@ 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
+    defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen
+    waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire.  'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait
     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
@@ -408,6 +409,7 @@ export namespace Docs {
                         nativeDimModifiable: true,
                         nativeHeightUnfrozen: true,
                         forceReflow: true,
+                        defaultDoubleClick: 'ignore',
                     },
                 },
             ],
@@ -436,7 +438,7 @@ export namespace Docs {
                 DocumentType.WEB,
                 {
                     layout: { view: WebBox, dataField: defaultDataKey },
-                    options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true },
+                    options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, waitForDoubleClickToClick: 'always' },
                 },
             ],
             [
@@ -578,14 +580,14 @@ export namespace Docs {
                 DocumentType.PRES,
                 {
                     layout: { view: PresBox, dataField: defaultDataKey },
-                    options: {},
+                    options: { defaultDoubleClick: 'ignore' },
                 },
             ],
             [
                 DocumentType.FONTICON,
                 {
                     layout: { view: FontIconBox, dataField: 'icon' },
-                    options: { allowClickBeforeDoubleClick: true, hideLinkButton: true, _width: 40, _height: 40 },
+                    options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', hideLinkButton: true, _width: 40, _height: 40 },
                 },
             ],
             [
@@ -983,6 +985,7 @@ export namespace Docs {
             I.author = Doc.CurrentUserEmail;
             I.rotation = 0;
             I.data = new InkField(points);
+            I.defaultDoubleClick = 'click';
             I.creationDate = new DateField();
             I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
             //I['acl-Override'] = SharingPermissions.Unset;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index c35941ee5..cc116fb46 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,6 +1,6 @@
 import { action, observable, runInAction } from 'mobx';
 import { DateField } from '../../fields/DateField';
-import { Doc, Field, Opt } from '../../fields/Doc';
+import { Doc, Field, Opt, StrListCast } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
 import { listSpec } from '../../fields/Schema';
@@ -207,24 +207,26 @@ export namespace DragManager {
             dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails
             if (docDragData && !docDragData.droppedDocuments.length) {
                 docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
-                docDragData.droppedDocuments = await Promise.all(
-                    dragData.draggedDocuments.map(async d =>
-                        !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
-                            ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result)
-                            : docDragData.dropAction === 'alias'
-                            ? Doc.BestAlias(d)
-                            : docDragData.dropAction === 'proto'
-                            ? Doc.GetProto(d)
-                            : docDragData.dropAction === 'copy'
-                            ? (
-                                  await Doc.MakeClone(d)
-                              ).clone
-                            : d
+                docDragData.droppedDocuments = (
+                    await Promise.all(
+                        dragData.draggedDocuments.map(async d =>
+                            !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
+                                ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result)
+                                : docDragData.dropAction === 'alias'
+                                ? Doc.BestAlias(d)
+                                : docDragData.dropAction === 'proto'
+                                ? Doc.GetProto(d)
+                                : docDragData.dropAction === 'copy'
+                                ? (
+                                      await Doc.MakeClone(d)
+                                  ).clone
+                                : d
+                        )
                     )
-                );
+                ).filter(d => d);
                 !['same', 'proto'].includes(docDragData.dropAction as any) &&
                     docDragData.droppedDocuments.forEach((drop: Doc, i: number) => {
-                        const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec('string'), []);
+                        const dragProps = StrListCast(dragData.draggedDocuments[i].removeDropProperties);
                         const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps));
                         remProps.map(prop => (drop[prop] = undefined));
                     });
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2982f8a99..2d2d3c2f6 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -67,8 +67,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         reaction(
             () => SelectionManager.Views().slice(),
             action(docs => {
-                this._showNothing = true;
-                docs.length > 1 && (this._showNothing = false); // show decorations if multiple docs are selected
+                this._showNothing = !DocumentView.LongPress && docs.length === 1; // show decorations if multiple docs are selected or we're long pressing
                 this._editingTitle = false;
             })
         );
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 3861331b5..a085b69a5 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -179,7 +179,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                       UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
                   }),
             action((e: PointerEvent, doubleTap: boolean | undefined) => {
-                doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
                 if (doubleTap) {
                     InkStrokeProperties.Instance._controlButton = true;
                     InkStrokeProperties.Instance._currentPoint = -1;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a30d139be..c84d204d5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -214,6 +214,8 @@ export class MainView extends React.Component {
         window.removeEventListener('keyup', KeyManager.Instance.unhandle);
         window.removeEventListener('keydown', KeyManager.Instance.handle);
         window.removeEventListener('pointerdown', this.globalPointerDown, true);
+        window.removeEventListener('pointermove', this.globalPointerMove, true);
+        window.removeEventListener('mouseclick', this.globalPointerClick, true);
         window.removeEventListener('paste', KeyManager.Instance.paste as any);
         document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener);
     }
@@ -485,7 +487,20 @@ export class MainView extends React.Component {
         );
     }
 
+    private longPressTimer: NodeJS.Timeout | undefined;
+    globalPointerClick = action((e: any) => {
+        this.longPressTimer && clearTimeout(this.longPressTimer);
+        DocumentView.LongPress = false;
+    });
+    globalPointerMove = action((e: PointerEvent) => {
+        if (e.movementX > 3 || e.movementY > 3) this.longPressTimer && clearTimeout(this.longPressTimer);
+    });
     globalPointerDown = action((e: PointerEvent) => {
+        DocumentView.LongPress = false;
+        this.longPressTimer = setTimeout(
+            action(() => (DocumentView.LongPress = true)),
+            1000
+        );
         DocumentManager.removeOverlayViews();
         Doc.linkFollowUnhighlight();
         AudioBox.Enabled = true;
@@ -506,6 +521,8 @@ export class MainView extends React.Component {
         window.addEventListener('dragover', e => e.preventDefault(), false);
         // document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined));
         document.addEventListener('pointerdown', this.globalPointerDown, true);
+        document.addEventListener('pointermove', this.globalPointerMove, true);
+        document.addEventListener('mouseclick', this.globalPointerClick, true);
         document.addEventListener(
             'click',
             (e: MouseEvent) => {
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 621e3d93b..28bdd0cb9 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -268,7 +268,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
                             {this.props.renderChildren(this.props.docList)}
                         </div>
 
-                        {!this.props.chromeHidden && type !== DocumentType.PRES ? (
+                        {!this.props.chromeHidden ? (
                             <div className="collectionNoteTakingView-DocumentButtons" style={{ marginBottom: 10 }}>
                                 <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
                                     <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
@@ -289,7 +289,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
         const heading = this._heading;
         return (
             <div
-                className={'collectionNoteTakingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')}
+                className="collectionNoteTakingViewFieldColumn"
                 key={heading}
                 style={{
                     width: this.columnWidth,
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 631b9add5..5e6849e7c 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -422,7 +422,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                         this._lastView = this._view;
                     })}
                     renderDepth={0}
-                    LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString('data') : undefined}
+                    LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined}
                     Document={this._document}
                     DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
                     ContainingCollectionView={undefined}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index af2d148e0..257428d56 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -305,7 +305,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
 
     public static makeTextBullet() {
-        const bullet = Docs.Create.TextDocument('-text-', {
+        const bullet = Docs.Create.TextDocument('', {
             layout: CollectionView.LayoutString('data'),
             title: '-title-',
             treeViewExpandedViewLock: true,
@@ -326,7 +326,8 @@ export class TreeView extends React.Component<TreeViewProps> {
         });
         Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
         Doc.GetProto(bullet).data = new List<Doc>([]);
-        FormattedTextBox.SelectOnLoad = bullet[Id];
+        DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
+
         return bullet;
     }
 
@@ -902,6 +903,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 hideDecorationTitle={this.props.treeView.outlineMode}
                 hideResizeHandles={this.props.treeView.outlineMode}
                 styleProvider={this.titleStyleProvider}
+                onClickScriptDisable="never"
                 docViewPath={returnEmptyDoclist}
                 treeViewDoc={this.props.treeView.props.Document}
                 addDocument={undefined}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index be4c2a60e..8104ab1a7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -822,7 +822,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
             Doc.ActiveTool = InkTool.None;
             if (this.props.isContentActive(true)) e.stopPropagation();
-        } else if (!e.cancelBubble) {
+        } else {
+            this.props.DocumentView?.().docView?.cancelMoveEvents();
             if (this.tryDragCluster(e, this._hitCluster)) {
                 return true;
             }
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index c21bc9754..9e56de8c2 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -127,9 +127,9 @@ export class DocumentContentsView extends React.Component<
         TraceMobx();
         if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString;
         if (!this.layoutDoc) return '<p>awaiting layout</p>';
-        if (this.props.layoutKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString('data'));
+        if (this.props.layoutKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString());
         const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, 'layout')], 'string');
-        if (layout === undefined) return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString(this.layoutDoc.proto ? 'proto' : '');
+        if (layout === undefined) return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
         if (typeof layout === 'string') return layout;
         return '<p>Loading layout</p>';
     }
@@ -273,7 +273,7 @@ export class DocumentContentsView extends React.Component<
                 jsx={layoutFrame}
                 showWarnings={true}
                 onError={(test: any) => {
-                    console.log('DocumentContentsView:' + test);
+                    console.log('DocumentContentsView:' + test, bindings, layoutFrame);
                 }}
             />
         );
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 42c2b28ba..c8f677c5f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -124,6 +124,7 @@ export interface DocComponentView {
     select?: (ctrlKey: boolean, shiftKey: boolean) => void;
     menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
     isAnyChildContentActive?: () => boolean; // is any child content of the document active
+    onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
     getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
     setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
     playFrom?: (time: number, endTime?: number) => void;
@@ -133,6 +134,7 @@ export interface DocComponentView {
     setFocus?: () => void; // sets input focus to the componentView
     componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
     incrementalRendering?: () => void;
+    fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox)
     fieldKey?: string;
     annotationKey?: string;
     getTitle?: () => string;
@@ -192,6 +194,7 @@ export interface DocumentViewSharedProps {
     ignoreAutoHeight?: boolean;
     forceAutoHeight?: boolean;
     disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
+    onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
     pointerEvents?: () => Opt<string>;
     scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
     createNewFilterDoc?: () => void;
@@ -245,9 +248,10 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
 @observer
 export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() {
     public static SelectAfterContextMenu = true; //  whether a document should be selected after it's contextmenu is triggered.
-    private _cursorTimer: NodeJS.Timeout | undefined;
-    private _longPress = false;
     private _disposers: { [name: string]: IReactionDisposer } = {};
+    private _doubleClickTimeout: NodeJS.Timeout | undefined;
+    private _singleClickFunc: undefined | (() => any);
+    private _longPressSelector: NodeJS.Timeout | undefined;
     private _downX: number = 0;
     private _downY: number = 0;
     private _downTime: number = 0;
@@ -257,7 +261,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _doubleTap = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _titleRef = React.createRef<EditableView>();
-    private _timeout: NodeJS.Timeout | undefined;
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
     protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
@@ -265,8 +268,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
     @observable _animateScaleTime: Opt<number>; // milliseconds for animating between views.  defaults to 300 if not uset
     @observable _animateScalingTo = 0;
-    @observable _pendingDoubleClick = false;
-    @observable _cursorPress = false;
 
     private get topMost() {
         return this.props.renderDepth === 0 && !LightboxView.LightboxDoc;
@@ -331,9 +332,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get nativeHeight() {
         return this.props.NativeHeight();
     }
+    @computed get disableClickScriptFunc() {
+        return (
+            DocumentView.LongPress ||
+            this.props.onClickScriptDisable === 'always' ||
+            this._componentView?.onClickScriptDisable?.() ||
+            this._componentView?.isAnyChildContentActive?.()
+        );
+    }
     @computed get onClickHandler() {
-        const noClick = this.props.LayoutTemplateString?.includes(KeyValueBox.name) || this.rootSelected() || this.props.isSelected() || this._componentView?.isAnyChildContentActive?.();
-        return noClick ? undefined : this.props.onClick?.() ?? this.props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
+        return this.props.onClick?.() ?? this.props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
     }
     @computed get onDoubleClickHandler() {
         return this.props.onDoubleClick?.() ?? Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick;
@@ -446,7 +454,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         } else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
             const touch = me.touchEvent.changedTouches.item(0);
             if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) {
-                if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) {
+                if (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) {
                     this.cleanUpInteractions();
                     this.startDragging(this._downX, this._downY, this.Document.dropAction ? (this.Document.dropAction as any) : e.ctrlKey || e.altKey ? 'alias' : undefined);
                 }
@@ -585,91 +593,76 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
             let stopPropagate = true;
             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 && (![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);
-                    this._pendingDoubleClick = false;
-                    this._timeout = undefined;
+            if (this._doubleTap) {
+                if (this.onDoubleClickHandler?.script) {
+                    const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; // or we could call e.persist() to capture variables
+                    // prettier-ignore
+                    const func = () => this.onDoubleClickHandler.script.run( {
+                            this: this.layoutDoc,
+                            self: this.rootDoc,
+                            scriptContext: this.props.scriptContext,
+                            thisContainer: this.props.ContainingCollectionDoc,
+                            documentView: this.props.DocumentView(),
+                            clientX, clientY,  altKey,  shiftKey,  ctrlKey,
+                            value: undefined,
+                        }, console.log  );
+                    UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
+                } else if (!Doc.IsSystem(this.rootDoc) && (this.Document.defaultDoubleClick === undefined || this.Document.defaultDoubleClick === 'default')) {
+                    UndoManager.RunInBatch(() => this.props.addDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap');
+                    SelectionManager.DeselectAll();
+                    Doc.UnBrushDoc(this.props.Document);
+                } else {
+                    this._singleClickFunc?.();
                 }
-                if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) {
-                    // bcz: hack? don't execute script if you're clicking on a scripting box itself
-                    const { clientX, clientY, shiftKey, altKey, ctrlKey } = e;
+                this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
+                this._doubleClickTimeout = undefined;
+                this._singleClickFunc = undefined;
+            } else {
+                let clickFunc: undefined | (() => any);
+                if (!this.disableClickScriptFunc && this.onClickHandler?.script) {
+                    const { clientX, clientY, shiftKey, altKey, metaKey } = e;
                     const func = () =>
-                        this.onDoubleClickHandler.script.run(
+                        this.onClickHandler?.script.run(
                             {
                                 this: this.layoutDoc,
                                 self: this.rootDoc,
+                                _readOnly_: false,
                                 scriptContext: this.props.scriptContext,
                                 thisContainer: this.props.ContainingCollectionDoc,
                                 documentView: this.props.DocumentView(),
                                 clientX,
                                 clientY,
-                                altKey,
                                 shiftKey,
-                                ctrlKey,
-                                value: undefined,
+                                altKey,
+                                metaKey,
                             },
                             console.log
-                        );
-                    UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
-                } else if (!Doc.IsSystem(this.rootDoc) && (![DocumentType.INK].includes(this.rootDoc.type as any) || Doc.UserDoc().openInkInLightbox) && !this.rootDoc.isLinkButton) {
-                    //UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.()), 'double tap');
-                    UndoManager.RunInBatch(() => this.props.addDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap');
-                    SelectionManager.DeselectAll();
-                    Doc.UnBrushDoc(this.props.Document);
+                        ).result?.select === true
+                            ? this.props.select(false)
+                            : '';
+                    clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click'));
+                } else if (!this.disableClickScriptFunc && this.allLinks.length && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
+                    clickFunc = () => {
+                        SelectionManager.DeselectAll();
+                        LinkFollower.FollowLink(undefined, this.Document, e.altKey);
+                    };
+                } else {
+                    if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
+                        // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
+                        stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
+                    }
+                    preventDefault = false;
                 }
-            } else if (!this._longPress && this.onClickHandler?.script && !isScriptBox()) {
-                // bcz: hack? don't execute script if you're clicking on a scripting box itself
-                const { clientX, clientY, shiftKey, altKey, metaKey } = e;
-                const func = () =>
-                    this.onClickHandler?.script.run(
-                        {
-                            this: this.layoutDoc,
-                            self: this.rootDoc,
-                            _readOnly_: false,
-                            scriptContext: this.props.scriptContext,
-                            thisContainer: this.props.ContainingCollectionDoc,
-                            documentView: this.props.DocumentView(),
-                            clientX,
-                            clientY,
-                            shiftKey,
-                            altKey,
-                            metaKey,
-                        },
-                        console.log
-                    ).result?.select === true
-                        ? this.props.select(false)
-                        : '';
-                const clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click'));
-                if (this.onDoubleClickHandler && !this.props.Document.allowClickBeforeDoubleClick) {
-                    runInAction(() => (this._pendingDoubleClick = true));
-                    this._timeout = setTimeout(() => {
-                        this._timeout = undefined;
-                        clickFunc();
-                    }, 150);
-                } else clickFunc();
-            } else if (!this._longPress && this.allLinks.length && this.Document.type !== DocumentType.LINK && !isScriptBox() && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
-                SelectionManager.DeselectAll();
-                this.allLinks.length && LinkFollower.FollowLink(undefined, this.props.Document, e.altKey);
-            } else {
-                if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
-                    // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
-                    stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
+
+                this._singleClickFunc = clickFunc ?? (() => (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey));
+                if ((clickFunc && this.Document.waitForDoubleClickToClick !== 'never') || this.Document.waitForDoubleClickToClick === 'always') {
+                    this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
+                    this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300);
                 } else {
-                    runInAction(() => (this._pendingDoubleClick = true));
-                    this._timeout = setTimeout(
-                        action(() => {
-                            this._pendingDoubleClick = false;
-                            this._timeout = undefined;
-                        }),
-                        350
-                    );
-                    (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey);
+                    this._singleClickFunc();
+                    this._singleClickFunc = undefined;
                 }
-                preventDefault = false;
             }
             stopPropagate && e.stopPropagation();
             preventDefault && e.preventDefault();
@@ -678,6 +671,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
+        this._longPressSelector = setTimeout(() => DocumentView.LongPress && this.props.select(false), 1000);
         if (!(e.nativeEvent as any).DownDocView) (e.nativeEvent as any).DownDocView = GestureOverlay.DownDocView = this.props.DocumentView();
         if (this.rootDoc.type === DocumentType.INK && Doc.ActiveTool === InkTool.Eraser) return;
         // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document)
@@ -689,13 +683,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             }
             return;
         }
-        this._cursorTimer = setTimeout(
-            action(() => {
-                this._cursorPress = true;
-                this.props.select(false);
-            }),
-            1000 // long press required duration
-        );
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._downTime = Date.now();
@@ -719,44 +706,36 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             }
             document.removeEventListener('pointerup', this.onPointerUp);
             document.addEventListener('pointerup', this.onPointerUp);
-        } else {
-            this._cursorTimer && clearTimeout(this._cursorTimer);
-            this._cursorPress = false;
         }
     };
 
     @action
     onPointerMove = (e: PointerEvent): void => {
-        if (e.cancelBubble) return;
+        if (this.layoutDoc._lockedPosition || DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) return;
         if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
 
-        if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
+        if (((!this.topMost && this.props.isDocumentActive?.()) || this.layoutDoc.onDragStart) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
-                if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
-                    document.removeEventListener('pointermove', this.onPointerMove);
-                    document.removeEventListener('pointerup', this.onPointerUp);
-                    this._cursorTimer && clearTimeout(this._cursorTimer);
-                    this._cursorPress = false;
-                    this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType));
-                }
+                this.cleanupPointerEvents();
+                this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType));
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
             e.preventDefault();
         }
     };
 
+    cancelMoveEvents = () => document.removeEventListener('pointermove', this.onPointerMove);
+
     cleanupPointerEvents = () => {
         this.cleanUpInteractions();
-        document.removeEventListener('pointermove', this.onPointerMove);
+        this.cancelMoveEvents();
         document.removeEventListener('pointerup', this.onPointerUp);
     };
 
     @action
     onPointerUp = (e: PointerEvent): void => {
         this.cleanupPointerEvents();
-        this._longPress = this._cursorPress;
-        this._cursorTimer && clearTimeout(this._cursorTimer);
-        this._cursorPress = false;
+        this._longPressSelector && clearTimeout(this._longPressSelector);
 
         const now = Date.now();
         if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
@@ -766,6 +745,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             // 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
         }
+        if (DocumentView.LongPress) e.preventDefault();
     };
 
     @undoBatch
@@ -1051,7 +1031,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         // Add link to help documentation
         if (documentationDescription && documentationLink) {
-            console.log('add documentation item');
             helpItems.push({
                 description: documentationDescription,
                 event: () => {
@@ -1078,7 +1057,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
-    onClickFunc = () => this.onClickHandler as any as ScriptField; // bcz: typing HACK.  check and fix.
+    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) => {
@@ -1101,7 +1080,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 .concat(Array.from(Doc.brushManager.BrushedDoc.keys()))
                 .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc));
         const childOverlayed = () => Array.from(DocumentManager._overlayViews).some(view => Doc.AreProtosEqual(view.rootDoc, this.rootDoc));
-        return !this.props.isSelected() &&
+        return !this.props.LayoutTemplateString &&
+            !this.props.isSelected() &&
             LightboxView.LightboxDoc !== this.rootDoc &&
             this.thumb &&
             !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
@@ -1129,7 +1109,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </Tooltip>
         );
     }
-    contentPointerEvents = () => (this.onClickHandler ? 'none' : this.pointerEvents);
+    contentPointerEvents = () => (!this.disableClickScriptFunc && this.onClickHandler ? 'none' : this.pointerEvents);
     @computed get contents() {
         TraceMobx();
         return (
@@ -1326,7 +1306,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             <div
                 className="documentView-captionWrapper"
                 style={{
-                    pointerEvents: this.onClickHandler || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+                    pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
                     minWidth: 50 * ffscale(),
                     maxHeight: `max(100%, ${20 * ffscale()}px)`,
                 }}>
@@ -1360,7 +1340,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     width: !this.headerMargin ? `calc(100% - 18px)` : '100%', // leave room for annotation button
                     color: lightOrDark(background),
                     background,
-                    pointerEvents: this.onClickHandler || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+                    pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
                 }}>
                 <EditableView
                     ref={this._titleRef}
@@ -1420,7 +1400,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         ...style,
                         background: isButton || thumb ? undefined : this.backgroundColor,
                         opacity: this.opacity,
-                        cursor: this._cursorPress ? 'wait' : Doc.ActiveTool === InkTool.None ? 'grab' : 'crosshair',
+                        cursor: Doc.ActiveTool === InkTool.None ? 'grab' : 'crosshair',
                         color: StrCast(this.layoutDoc.color, 'inherit'),
                         fontFamily: StrCast(this.Document._fontFamily, 'inherit'),
                         fontSize: Cast(this.Document._fontSize, 'string', null),
@@ -1428,7 +1408,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
                     }}>
                     {this.innards}
-                    {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : null}
+                    {!this.disableClickScriptFunc && this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : null}
                     {this.widgetDecorations ?? null}
                 </div>
             )
@@ -1530,6 +1510,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 @observer
 export class DocumentView extends React.Component<DocumentViewProps> {
     public static ROOT_DIV = 'documentView-effectsWrapper';
+    @observable public static LongPress = false;
     public get displayName() {
         return 'DocumentView(' + this.props.Document?.title + ')';
     } // this makes mobx trace() statements more descriptive
@@ -1628,7 +1609,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.docView?.LayoutFieldKey || 'layout';
     }
     get fitWidth() {
-        return this.props.fitWidth?.(this.rootDoc) ?? this.layoutDoc?.fitWidth;
+        return this.docView?._componentView?.fitWidth?.() ?? this.props.fitWidth?.(this.rootDoc) ?? this.layoutDoc?.fitWidth;
     }
 
     @computed get hideLinkButton() {
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 0b1de0ff6..9c31ed3e4 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -3,24 +3,24 @@ import { observer } from 'mobx-react';
 import { Doc, Field, FieldResult } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
-import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { Cast, DocCast, FieldValue, NumCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { Docs } from '../../documents/Documents';
 import { SetupDrag } from '../../util/DragManager';
 import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting';
 import { undoBatch } from '../../util/UndoManager';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { ImageBox } from './ImageBox';
 import './KeyValueBox.scss';
 import { KeyValuePair } from './KeyValuePair';
 import React = require('react');
-import { ContextMenu } from '../ContextMenu';
-import { ContextMenuProps } from '../ContextMenuItem';
 import e = require('express');
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
-import { ImageBox } from './ImageBox';
-import { OpenWhere } from './DocumentView';
+import { returnTrue } from '../../../Utils';
 
 export type KVPScript = {
     script: CompiledScript;
@@ -30,8 +30,8 @@ export type KVPScript = {
 
 @observer
 export class KeyValueBox extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string) {
-        return FieldView.LayoutString(KeyValueBox, fieldStr);
+    public static LayoutString() {
+        return FieldView.LayoutString(KeyValueBox, 'data');
     }
 
     private _mainCont = React.createRef<HTMLDivElement>();
@@ -39,6 +39,12 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     private _keyInput = React.createRef<HTMLInputElement>();
     private _valInput = React.createRef<HTMLInputElement>();
 
+    componentDidMount() {
+        this.props.setContentView?.(this);
+    }
+    onClickScriptDisable: () => 'always' = () => 'always';
+    fitWidth = returnTrue;
+
     @observable private rows: KeyValuePair[] = [];
 
     @computed get splitPercentage() {
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 43f4b43fb..470f7e803 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,29 +1,39 @@
-import React = require("react");
-import { observer } from "mobx-react";
-import { emptyFunction, returnFalse } from "../../../Utils";
-import { ViewBoxBaseComponent } from "../DocComponent";
-import { StyleProp } from "../StyleProvider";
-import { ComparisonBox } from "./ComparisonBox";
+import React = require('react');
+import { observer } from 'mobx-react';
+import { emptyFunction, returnFalse, returnTrue } from '../../../Utils';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import { StyleProp } from '../StyleProvider';
+import { ComparisonBox } from './ComparisonBox';
 import { FieldView, FieldViewProps } from './FieldView';
-import "./LinkBox.scss";
+import './LinkBox.scss';
 
 @observer
 export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
-    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); }
+    public static LayoutString(fieldKey: string) {
+        return FieldView.LayoutString(LinkBox, fieldKey);
+    }
     isContentActiveFunc = () => this.isContentActive();
+
+    onClickScriptDisable: () => 'always' = () => 'always';
+    componentDidMount() {
+        this.props.setContentView?.(this);
+    }
     render() {
-        if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => this.dataDoc.treeViewOpen = true);
-        return <div className={`linkBox-container${this.isContentActive() ? "-interactive" : ""}`}
-            style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }} >
-            <ComparisonBox {...this.props}
-                fieldKey="anchor"
-                setHeight={emptyFunction}
-                dontRegisterView={true}
-                renderDepth={this.props.renderDepth + 1}
-                isContentActive={this.isContentActiveFunc}
-                addDocument={returnFalse}
-                removeDocument={returnFalse}
-                moveDocument={returnFalse} />
-        </div>;
+        if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => (this.dataDoc.treeViewOpen = true));
+        return (
+            <div className={`linkBox-container${this.isContentActive() ? '-interactive' : ''}`} style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }}>
+                <ComparisonBox
+                    {...this.props}
+                    fieldKey="anchor"
+                    setHeight={emptyFunction}
+                    dontRegisterView={true}
+                    renderDepth={this.props.renderDepth + 1}
+                    isContentActive={this.isContentActiveFunc}
+                    addDocument={returnFalse}
+                    removeDocument={returnFalse}
+                    moveDocument={returnFalse}
+                />
+            </div>
+        );
     }
-} 
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index a6e2c3e90..5683b0fe7 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -8,7 +8,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { returnEmptyString } from '../../../Utils';
+import { returnEmptyString, returnTrue } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { CompileScript, ScriptParam } from '../../util/Scripting';
@@ -114,6 +114,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
         }
     }
 
+    onClickScriptDisable: () => 'always' = () => 'always';
+
     @action
     componentDidMount() {
         this.props.setContentView?.(this);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f7425b26a..73283263f 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -205,7 +205,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     this._webPageHasBeenRendered = true;
                 } else if (
                     (!this.props.isContentActive(true) || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail)
-                    !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && // don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty
                     LightboxView.LightboxDoc !== this.rootDoc
                 ) {
                     // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty.
@@ -877,7 +876,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;
+        const interactive = this.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}
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index d9364e5b5..468bcc4d8 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -817,7 +817,7 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | '
             setMode: () => SetActiveInkWidth(value.toString()),
         }],
         ['strokeColor', {
-            checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.color) : ActiveInkColor()),
+            checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()),
             setInk: (doc: Doc) => (doc.color = String(value)),
             setMode: () => SetActiveInkColor(StrCast(value)),
         }],
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ddec86606..3ce2366f8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1477,7 +1477,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             e.preventDefault();
         }
     };
-    onSelectMove = (e: PointerEvent) => e.stopPropagation();
+    onSelectMove = (e: PointerEvent) => {
+        this.props.DocumentView?.().docView?.cancelMoveEvents();
+        e.stopPropagation();
+    };
     onSelectEnd = (e: PointerEvent) => {
         document.removeEventListener('pointerup', this.onSelectEnd);
         document.removeEventListener('pointermove', this.onSelectMove);
@@ -1497,10 +1500,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 return;
             }
         }
-
-        if (e.button === 0 && this.props.isSelected(true) && !e.altKey) {
-            e.stopPropagation();
-        }
     };
     @action
     onDoubleClick = (e: React.MouseEvent): void => {
@@ -1566,8 +1565,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         if (this.props.isSelected(true)) {
             // if text box is selected, then it consumes all click events
             (e.nativeEvent as any).handledByInnerReactInstance = true;
-            if (this.ProseRef?.children[0] !== e.nativeEvent.target) e.stopPropagation(); // if you double click on text, then it will be selected instead of sending a double click to DocumentView & opening a lightbox.  Also,if a text box has isLinkButton, this will prevent link following if you've selected the document to edit it.
-            // e.stopPropagation();  // bcz: not sure why this was here.  We need to allow the DocumentView to get clicks to process doubleClicks (see above comment)
             this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
         }
         this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed;
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index fc8f1da49..7392d2706 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -80,7 +80,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             { fireImmediately: true }
         );
         this._disposer = reaction(
-            () => SelectionManager.Views(),
+            () => SelectionManager.Views().slice(),
             selected => {
                 this._showLinkPopup = false;
                 AnchorMenu.Instance.fadeOut(true);
-- 
cgit v1.2.3-70-g09d2


From dabac69d26c8e8e69bd55466ce221d9e33e36638 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 28 Mar 2023 14:23:17 -0400
Subject: numerous changes to try to simplify event handling in DocumentView -
 got rid of isContentActive in DocComponent since it's in DocumentView.  
 Including adding 'enableDragWhenActive' , 'onClickScriptDisable',

---
 src/Utils.ts                                       |  17 +-
 src/client/documents/Documents.ts                  |   3 +-
 src/client/util/CurrentUserUtils.ts                |  12 +-
 src/client/views/DocComponent.tsx                  |  13 --
 src/client/views/DocumentDecorations.tsx           |   8 +-
 src/client/views/InkingStroke.tsx                  |   2 +-
 src/client/views/MainView.tsx                      |   8 +-
 src/client/views/StyleProvider.tsx                 |   4 +-
 .../views/collections/CollectionStackingView.tsx   |   2 +
 src/client/views/collections/TabDocView.tsx        |   4 +-
 src/client/views/collections/TreeView.tsx          |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 100 +--------
 .../collectionLinear/CollectionLinearView.scss     |  10 +-
 .../collectionLinear/CollectionLinearView.tsx      |  18 +-
 .../collectionSchema/CollectionSchemaView.scss     |   3 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  46 ++--
 .../collections/collectionSchema/SchemaRowBox.tsx  |   8 +-
 src/client/views/global/globalCssVariables.scss    |   3 +-
 .../views/global/globalCssVariables.scss.d.ts      |   2 +-
 src/client/views/nodes/ColorBox.tsx                |   2 +-
 src/client/views/nodes/DocumentView.tsx            | 238 ++++-----------------
 src/client/views/nodes/FunctionPlotBox.tsx         |   2 +-
 src/client/views/nodes/KeyValueBox.tsx             |  15 +-
 src/client/views/nodes/KeyValuePair.tsx            |   9 +-
 src/client/views/nodes/LinkBox.tsx                 |   9 +-
 src/client/views/nodes/ScriptingBox.tsx            |   4 +-
 src/client/views/nodes/WebBox.tsx                  |   2 +-
 src/client/views/nodes/button/FontIconBox.scss     |  10 +-
 src/client/views/nodes/button/FontIconBox.tsx      |   6 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   9 +-
 src/client/views/nodes/trails/PresBox.tsx          |   4 +-
 src/client/views/pdf/PDFViewer.tsx                 |   9 -
 src/fields/Doc.ts                                  |   2 +-
 src/fields/ScriptField.ts                          |   2 +-
 34 files changed, 171 insertions(+), 421 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index ae1478943..0c7deaf5d 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -8,8 +8,12 @@ import { Message } from './server/Message';
 import Color = require('color');
 
 export namespace Utils {
+    export let CLICK_TIME = 300;
     export let DRAG_THRESHOLD = 4;
     export let SNAP_THRESHOLD = 10;
+    export function isClick(x: number, y: number, downX: number, downY: number, downTime: number) {
+        return Date.now() - downTime < Utils.CLICK_TIME && Math.abs(x - downX) < Utils.DRAG_THRESHOLD && Math.abs(y - downY) < Utils.DRAG_THRESHOLD;
+    }
 
     export function readUploadedFileAsText(inputFile: File) {
         const temporaryFileReader = new FileReader();
@@ -509,11 +513,22 @@ export function returnTrue() {
     return true;
 }
 
+export function returnAlways(): 'always' {
+    return 'always';
+}
+export function returnNever(): 'never' {
+    return 'never';
+}
+
+export function returnDefault(): 'default' {
+    return 'default';
+}
+
 export function returnFalse() {
     return false;
 }
 
-export function returnAll() {
+export function returnAll(): 'all' {
     return 'all';
 }
 
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2bc1b5b1d..c484db0db 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -230,6 +230,7 @@ export class DocumentOptions {
     contextMenuIcons?: List<string>;
     defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen
     waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire.  'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait
+    enableDragWhenActive?: boolean;
     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
@@ -587,7 +588,7 @@ export namespace Docs {
                 DocumentType.FONTICON,
                 {
                     layout: { view: FontIconBox, dataField: 'icon' },
-                    options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', hideLinkButton: true, _width: 40, _height: 40 },
+                    options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', enableDragWhenActive: true, hideLinkButton: true, _width: 40, _height: 40 },
                 },
             ],
             [
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 814b7b072..b21f53221 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -62,7 +62,7 @@ export class CurrentUserUtils {
         const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [
             {
                 btnOpts: { title: "slide", icon: "address-card" }, 
-                templateOpts: { _width: 400, _height: 300, title: "slideView", childDocumentsActive: true, _xMargin: 3, _yMargin: 3, system: true }, 
+                templateOpts: { _width: 400, _height: 300, title: "slideView",  _xMargin: 3, _yMargin: 3, system: true }, 
                 template: (opts:DocumentOptions) => Docs.Create.MultirowDocument(
                     [
                         Docs.Create.MulticolumnDocument([], { title: "data", _height: 200,  system: true }),
@@ -95,7 +95,7 @@ export class CurrentUserUtils {
 
         const reqdOpts:DocumentOptions = {
             title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true,
-            _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, childDocumentsActive: true,
+            _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true,
             _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
         };
         const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
@@ -184,10 +184,10 @@ export class CurrentUserUtils {
                 case DocumentType.IMG : creator = imageBox; break;
                 case DocumentType.FONTICON: creator = fontBox; break;
             }
-            const allopts = {system: true, ...opts};
+            const allopts = {system: true, onClickScriptDisable: "never",  ...opts};
             return DocUtils.AssignScripts(  (curIcon?.iconTemplate === opts.iconTemplate ? 
                       DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))),
-                {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)"});
+                {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", });
         };
         const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({
             textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px",  _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
@@ -314,7 +314,7 @@ export class CurrentUserUtils {
         const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
             const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
             const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, 
-                _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, 
+                _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, 
                 btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true,
                 _removeDropProperties: new List<string>(["_stayInCollection"]),
                 };
@@ -324,7 +324,7 @@ export class CurrentUserUtils {
         const reqdOpts:DocumentOptions = {
             title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, system: true,
             _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
-            childDocumentsActive: true, childDropAction: 'alias' 
+             childDropAction: 'alias' 
         };
         const reqdScripts = { dropConverter: "convertToButtons(dragData)" };
         return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts);
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 0b92fd864..9fc1487a0 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -82,12 +82,6 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
             return this.props.fieldKey;
         }
 
-        isContentActive = (outsideReaction?: boolean) =>
-            this.props.isContentActive?.() === false
-                ? false
-                : Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction)
-                ? true
-                : undefined;
         protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
@@ -135,13 +129,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
 
         isAnyChildContentActive = () => this._isAnyChildContentActive;
 
-        isContentActive = (outsideReaction?: boolean) =>
-            this.props.isContentActive?.() === false
-                ? false
-                : Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) || this.isAnyChildContentActive()
-                ? true
-                : undefined;
-
         lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
 
         protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2d2d3c2f6..985e6f88f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -916,9 +916,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                         pointerEvents: 'none',
                                     }}>
                                     {this._isRotating ? null : (
-                                        <div className="documentDecorations-rotation" style={{ pointerEvents: 'all' }} onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}>
-                                            <IconButton icon={<FaUndo />} isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} />
-                                        </div>
+                                        <Tooltip enterDelay={750} title={<div className="dash-tooltip">tap to set rotate center, drag to rotate</div>}>
+                                            <div className="documentDecorations-rotation" style={{ pointerEvents: 'all' }} onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}>
+                                                <IconButton icon={<FaUndo />} isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} />
+                                            </div>
+                                        </Tooltip>
                                     )}
                                 </div>
                                 {!this._showRotCenter ? null : (
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index a085b69a5..e3642fdaf 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -473,7 +473,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                             dontRegisterView={true}
                             noSidebar={true}
                             dontScale={true}
-                            isContentActive={this.isContentActive}
+                            isContentActive={this.props.isContentActive}
                         />
                     </div>
                 )}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index c84d204d5..f5adc17d0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -40,7 +40,7 @@ import { DashboardView } from './DashboardView';
 import { DictationOverlay } from './DictationOverlay';
 import { DocumentDecorations } from './DocumentDecorations';
 import { GestureOverlay } from './GestureOverlay';
-import { DASHBOARD_SELECTOR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss';
+import { TOPBAR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss';
 import { Colors } from './global/globalEnums';
 import { KeyManager } from './GlobalKeyHandler';
 import { InkTranscription } from './InkTranscription';
@@ -86,7 +86,7 @@ export class MainView extends React.Component {
         return this._hideUI ? 0 : 27;
     } //  27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js
     @computed private get topOfDashUI() {
-        return this._hideUI || LightboxView.LightboxDoc ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', ''));
+        return this._hideUI || LightboxView.LightboxDoc ? 0 : Number(TOPBAR_HEIGHT.replace('px', ''));
     }
     @computed private get topOfHeaderBarDoc() {
         return this.topOfDashUI;
@@ -872,7 +872,7 @@ export class MainView extends React.Component {
 
     @computed get docButtons() {
         return !Doc.MyDockedBtns ? null : (
-            <div className="mainView-docButtons" ref={this._docBtnRef} style={{ height: !Doc.MyDockedBtns.linearViewIsExpanded ? '42px' : undefined }}>
+            <div className="mainView-docButtons" ref={this._docBtnRef}>
                 <CollectionLinearView
                     Document={Doc.MyDockedBtns}
                     DataDoc={undefined}
@@ -910,7 +910,7 @@ export class MainView extends React.Component {
         );
     }
     @computed get snapLines() {
-        return !SnappingManager.GetShowSnapLines() ? null : (
+        return !SelectionManager.Views().some(dv => dv.rootDoc.showSnapLines) ? null : (
             <div className="mainView-snapLines">
                 <svg style={{ width: '100%', height: '100%' }}>
                     {SnappingManager.horizSnapLines().map(l => (
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 1b5eb3342..faaa4e1f9 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -89,6 +89,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
     const isAnchor = property.includes(':anchor');
     const isAnnotated = property.includes(':annotated');
     const isOpen = property.includes(':open');
+    const boxBackground = property.includes(':box');
     const fieldKey = props?.fieldKey ? props.fieldKey + '-' : isCaption ? 'caption-' : '';
     const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
     const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
@@ -200,7 +201,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                     docColor = docColor || (darkScheme() ? 'transparent' : 'transparent');
                     break;
                 case DocumentType.FONTICON:
-                    docColor = docColor || Colors.DARK_GRAY;
+                    docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY;
                     break;
                 case DocumentType.RTF:
                     docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY);
@@ -289,6 +290,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         }
         case StyleProp.PointerEvents:
             const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name);
+            if (docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.() !== undefined) return docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.();
             if (MainView.Instance._exploreMode || doc?.unrendered) return isInk ? 'visiblePainted' : 'all';
             if (doc?.pointerEvents) return StrCast(doc.pointerEvents);
             if (props?.contentPointerEvents) return StrCast(props.contentPointerEvents);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 33d468950..64ec419ec 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -325,6 +325,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 fitWidth={this.props.childFitWidth}
                 isContentActive={doc.isLinkButton ? this.isChildButtonContentActive : this.isChildContentActive}
                 onKey={this.onKeyDown}
+                onBrowseClick={this.props.onBrowseClick}
                 isDocumentActive={this.isContentActive}
                 LayoutTemplate={this.props.childLayoutTemplate}
                 LayoutTemplateString={this.props.childLayoutString}
@@ -659,6 +660,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         addDocument={this.props.addDocument}
                         moveDocument={this.props.moveDocument}
                         addDocTab={this.props.addDocTab}
+                        onBrowseClick={this.props.onBrowseClick}
                         pinToPres={emptyFunction}
                         rootSelected={this.props.isSelected}
                         removeDocument={this.props.removeDocument}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 5e6849e7c..93b816581 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -10,7 +10,6 @@ import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { FieldId } from '../../../fields/RefField';
 import { listSpec } from '../../../fields/Schema';
-import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
 import { DocServer } from '../../DocServer';
@@ -28,6 +27,7 @@ import { LightboxView } from '../LightboxView';
 import { MainView } from '../MainView';
 import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
 import { DashFieldView } from '../nodes/formattedText/DashFieldView';
+import { KeyValueBox } from '../nodes/KeyValueBox';
 import { PinProps, PresBox, PresMovement } from '../nodes/trails';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
 import { CollectionDockingView } from './CollectionDockingView';
@@ -35,7 +35,6 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionView } from './CollectionView';
 import './TabDocView.scss';
 import React = require('react');
-import { KeyValueBox } from '../nodes/KeyValueBox';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface TabDocViewProps {
@@ -429,6 +428,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     ContainingCollectionDoc={undefined}
                     onBrowseClick={MainView.Instance.exploreMode}
                     isContentActive={returnTrue}
+                    isDocumentActive={returnFalse}
                     PanelWidth={this.PanelWidth}
                     PanelHeight={this.PanelHeight}
                     styleProvider={DefaultStyleProvider}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 257428d56..99b7549c0 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -573,6 +573,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         </div>
                     )}
                     <ul
+                        style={{ cursor: 'inherit' }}
                         key={expandKey + 'more'}
                         title="click to change sort order"
                         className={''} //this.doc.treeViewHideTitle ? 'no-indent' : ''}
@@ -629,7 +630,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             );
         } else if (this.treeViewExpandedView === 'fields') {
             return (
-                <ul key={this.doc[Id] + this.doc.title}>
+                <ul key={this.doc[Id] + this.doc.title} style={{ cursor: 'inherit' }}>
                     <div>{this.expandedField}</div>
                 </ul>
             );
@@ -903,7 +904,8 @@ export class TreeView extends React.Component<TreeViewProps> {
                 hideDecorationTitle={this.props.treeView.outlineMode}
                 hideResizeHandles={this.props.treeView.outlineMode}
                 styleProvider={this.titleStyleProvider}
-                onClickScriptDisable="never"
+                enableDragWhenActive={true}
+                onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
                 docViewPath={returnEmptyDoclist}
                 treeViewDoc={this.props.treeView.props.Document}
                 addDocument={undefined}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8104ab1a7..aed3683d4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -821,9 +821,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return false;
         if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
             Doc.ActiveTool = InkTool.None;
-            if (this.props.isContentActive(true)) e.stopPropagation();
         } else {
-            this.props.DocumentView?.().docView?.cancelMoveEvents();
             if (this.tryDragCluster(e, this._hitCluster)) {
                 return true;
             }
@@ -955,96 +953,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return tVals;
     };
 
-    handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
-        if (!e.cancelBubble) {
-            const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-            if (myTouches[0]) {
-                if (Doc.ActiveTool === InkTool.None) {
-                    if (this.tryDragCluster(e, this._hitCluster)) {
-                        e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
-                        e.preventDefault();
-                        document.removeEventListener('pointermove', this.onPointerMove);
-                        return;
-                    }
-                    // TODO: nda - this allows us to pan collections with finger -> only want to do this when collection is selected'
-                    this.pan(myTouches[0]);
-                }
-            }
-            // e.stopPropagation();
-            e.preventDefault();
-        }
-    };
-
-    handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
-        // pinch zooming
-        if (!e.cancelBubble) {
-            const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-            const pt1 = myTouches[0];
-            const pt2 = myTouches[1];
-
-            if (this.prevPoints.size === 2) {
-                const oldPoint1 = this.prevPoints.get(pt1.identifier);
-                const oldPoint2 = this.prevPoints.get(pt2.identifier);
-                if (oldPoint1 && oldPoint2) {
-                    const dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2);
-
-                    // if zooming, zoom
-                    if (dir !== 0) {
-                        const d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2));
-                        const d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2));
-                        const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
-                        const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-
-                        // calculate the raw delta value
-                        const rawDelta = dir * (d1 + d2);
-
-                        // this floors and ceils the delta value to prevent jitteriness
-                        const delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8);
-                        this.zoom(centerX, centerY, delta * window.devicePixelRatio);
-                        this.prevPoints.set(pt1.identifier, pt1);
-                        this.prevPoints.set(pt2.identifier, pt2);
-                    }
-                    // this is not zooming. derive some form of panning from it.
-                    else {
-                        // use the centerx and centery as the "new mouse position"
-                        const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
-                        const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-                        // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
-
-                        this._lastX = centerX;
-                        this._lastY = centerY;
-                    }
-                }
-            }
-            // e.stopPropagation();
-            e.preventDefault();
-        }
-    };
-
-    @action
-    handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
-        if (this.props.isContentActive(true)) {
-            // const pt1: React.Touch | null = e.targetTouches.item(0);
-            // const pt2: React.Touch | null = e.targetTouches.item(1);
-            // // if (!pt1 || !pt2) return;
-            const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-            const pt1 = myTouches[0];
-            const pt2 = myTouches[1];
-            if (pt1 && pt2) {
-                const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
-                const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-                this._lastX = centerX;
-                this._lastY = centerY;
-
-                this.removeMoveListeners();
-                this.addMoveListeners();
-                this.removeEndListeners();
-                this.addEndListeners();
-                e.stopPropagation();
-            }
-        }
-    };
-
     cleanUpInteractions = () => {
         this.removeMoveListeners();
         this.removeEndListeners();
@@ -1637,7 +1545,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             } else {
                 const canvas = oldDiv;
                 const img = document.createElement('img'); // create a Image Element
-                img.src = canvas.toDataURL(); //image source
+                try {
+                    img.src = canvas.toDataURL(); //image source
+                } catch (e) {
+                    console.log(e);
+                }
                 img.style.width = canvas.style.width;
                 img.style.height = canvas.style.height;
                 const newCan = newDiv as HTMLCanvasElement;
@@ -2265,6 +2177,8 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
         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
         ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime);
         Doc.linkFollowHighlight(dv?.props.Document, false);
+    } else {
+        DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true });
     }
 }
 ScriptingGlobals.add(CollectionBrowseClick);
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
index 521bcda1e..3e3709827 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
@@ -1,9 +1,13 @@
 @import '../../global/globalCssVariables';
 @import '../../_nodeModuleOverrides';
 
+.collectionLinearView {
+    width: 100%;
+}
 .collectionLinearView-label {
     color: black;
     background-color: $light-gray;
+    width: 100%;
 }
 .collectionLinearView-outer {
     overflow: visible;
@@ -15,8 +19,6 @@
     }
 
     &.true {
-        padding-left: 5px;
-        padding-right: 5px;
         border-left: $standard-border;
         background-color: $medium-blue-alt;
     }
@@ -29,7 +31,6 @@
         display: flex;
         height: 100%;
         align-items: center;
-        gap: 5px;
 
         .collectionView {
             overflow: visible !important;
@@ -101,13 +102,12 @@
             background-color: $medium-blue;
             padding: 5;
             border-radius: 2px;
-            height: 25;
+            height: 100%;
             min-width: 25;
             margin: 0;
             color: $white;
             display: flex;
             font-weight: 100;
-            width: fit-content;
             transition: transform 0.2s;
             align-items: center;
             justify-content: center;
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index d54e8ce98..a062c65fc 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { StylesProvider, Tooltip } from '@material-ui/core';
+import { Tooltip } from '@material-ui/core';
 import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -11,8 +11,6 @@ import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
-import { Colors, Shadows } from '../../global/globalEnums';
-import { media_state } from '../../nodes/AudioBox';
 import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
 import { DocumentView } from '../../nodes/DocumentView';
 import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
@@ -46,7 +44,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
     componentDidMount() {
         this._widthDisposer = reaction(
-            () => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * this.rootDoc[HeightSym]() : 10),
+            () => 5 + this.dimension() + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[WidthSym]() || this.dimension()) + tot + 4, 0) : 0),
             width => this.childDocs.length && (this.layoutDoc._width = width),
             { fireImmediately: true }
         );
@@ -180,10 +178,10 @@ export class CollectionLinearView extends CollectionSubView() {
                 ref={r => (dref = r || undefined)}
                 style={{
                     pointerEvents: 'all',
-                    width: nested ? undefined : NumCast(doc._width),
-                    height: nested ? undefined : NumCast(doc._height),
-                    marginLeft: !nested ? 2.5 : 0,
-                    marginRight: !nested ? 2.5 : 0,
+                    width: NumCast(doc._width),
+                    height: NumCast(doc._height),
+                    marginLeft: 2,
+                    marginRight: 2,
                     // width: NumCast(pair.layout._width),
                     // height: NumCast(pair.layout._height),
                 }}>
@@ -199,7 +197,7 @@ export class CollectionLinearView extends CollectionSubView() {
                     rootSelected={this.props.isSelected}
                     removeDocument={this.props.removeDocument}
                     ScreenToLocalTransform={docXf}
-                    PanelWidth={nested ? doc[WidthSym] : this.dimension}
+                    PanelWidth={doc[WidthSym]}
                     PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension}
                     renderDepth={this.props.renderDepth + 1}
                     dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)}
@@ -236,7 +234,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
         return (
             <div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: this.layoutDoc.linearViewIsExpanded ? undefined : 'transparent' }}>
-                <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu}>
+                <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension() }}>
                     {!this.props.Document.linearViewExpandable ? null : (
                         <Tooltip title={<div className="dash-tooltip">{isExpanded ? 'Close' : 'Open'}</div>} placement="top">
                             {menuOpener}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 34e591195..1ef2fb4ef 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -8,7 +8,8 @@
 
     .schema-table {
         background-color: $white;
-        cursor: default;
+        cursor: grab;
+        overflow: scroll;
 
         .schema-column-menu,
         .schema-filter-menu {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 60202a19e..d47c9762c 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -9,7 +9,7 @@ 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, returnEmptyDoclist, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
+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';
@@ -46,7 +46,7 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     public static _rowHeight: number = 40;
     public static _minColWidth: number = 25;
-    public static _rowMenuWidth: number = 100;
+    public static _rowMenuWidth: number = 60;
     public static _previewDividerWidth: number = 4;
 
     @computed get _selectedDocs() {
@@ -364,7 +364,11 @@ export class CollectionSchemaView extends CollectionSubView() {
             this.dataDoc[this.fieldKey ?? 'data'] = new List<Doc>([...removed, ...draggedDocs, ...pushedDocs]);
             this.setSort(undefined);
             SelectionManager.DeselectAll();
-            setTimeout(() => draggedDocs.forEach(doc => DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true))), 100);
+            draggedDocs.forEach(doc => {
+                const draggedView = DocumentManager.Instance.getFirstDocumentView(doc);
+                if (draggedView) DocumentManager.Instance.RemoveView(draggedView);
+                DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true));
+            });
             e.stopPropagation();
             return true;
         }
@@ -812,26 +816,17 @@ export class CollectionSchemaView extends CollectionSubView() {
     previewWidthFunc = () => this.previewWidth;
     render() {
         return (
-            <div
-                className="collectionSchemaView"
-                ref={(ele: HTMLDivElement | null) => {
-                    this.createDashEventsTarget(ele);
-                }}
-                onPointerDown={e => {
-                    // this is analogous to the panning code for a freeform view.
-                    // however, schema views don't pan so it does nothing.  but it does eat the pointerDown event
-                    // if the content is active to prevent the schema from being dragged
-                    this.isContentActive() && setupMoveUpEvents(this, e, returnFalse, emptyFunction, emptyFunction, false);
-                }}
-                onDrop={this.onExternalDrop.bind(this)}>
-                <div className="schema-table">
+            <div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}>
+                <div
+                    className="schema-table"
+                    onWheel={e => this.props.isContentActive() && e.stopPropagation()}
+                    ref={r => {
+                        // prevent wheel events from passively propagating up through containers
+                        r?.addEventListener('wheel', (e: WheelEvent) => {}, { passive: false });
+                    }}>
                     <div className="schema-header-row" style={{ height: CollectionSchemaView._rowHeight }}>
                         <div className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth }}>
-                            <div
-                                className="schema-header-button"
-                                onPointerDown={e => {
-                                    this._columnMenuIndex && this._columnMenuIndex === -1 ? this.closeColumnMenu() : this.openColumnMenu(-1, true);
-                                }}>
+                            <div className="schema-header-button" onPointerDown={e => (this._columnMenuIndex === -1 ? this.closeColumnMenu() : this.openColumnMenu(-1, true))}>
                                 <FontAwesomeIcon icon="plus" />
                             </div>
                         </div>
@@ -867,8 +862,9 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 DataDoc={undefined}
                                 fitContentsToBox={returnTrue}
                                 dontCenter={'y'}
+                                onClickScriptDisable="always"
                                 focus={DocUtils.DefaultFocus}
-                                renderDepth={this.props.renderDepth}
+                                renderDepth={this.props.renderDepth + 1}
                                 rootSelected={this.rootSelected}
                                 PanelWidth={this.previewWidthFunc}
                                 PanelHeight={this.props.PanelHeight}
@@ -915,16 +911,22 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
                     return (
                         <div className="schema-row-wrapper" style={{ maxHeight: CollectionSchemaView._rowHeight }}>
                             <DocumentView
+                                key={doc[Id]}
                                 {...this.props.schema.props}
                                 LayoutTemplate={this.props.schema.props.childLayoutTemplate}
                                 LayoutTemplateString={SchemaRowBox.LayoutString(this.props.schema.props.fieldKey)}
                                 Document={doc}
                                 DataDoc={dataDoc}
+                                renderDepth={this.props.schema.props.renderDepth + 1}
                                 ContainingCollectionView={this.props.schema.props.CollectionView}
                                 ContainingCollectionDoc={this.props.schema.Document}
                                 PanelWidth={this.tableWidthFunc}
                                 PanelHeight={this.rowHeightFunc}
                                 styleProvider={DefaultStyleProvider}
+                                waitForDoubleClickToClick={returnNever}
+                                defaultDoubleClick={returnDefault}
+                                enableDragWhenActive={true}
+                                onClickScriptDisable="always"
                                 focus={this.props.schema.focusDocument}
                                 docFilters={this.props.schema.childDocFilters}
                                 docRangeFilters={this.props.schema.childDocRangeFilters}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 10f307068..2defaae00 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -88,11 +88,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return (
             <div
                 className="schema-row"
-                style={
-                    this.props.isSelected()
-                        ? { height: CollectionSchemaView._rowHeight, backgroundColor: Colors.LIGHT_BLUE, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined /*, opacity: this.props.dragging ? 0.5 : 1 */ }
-                        : { height: CollectionSchemaView._rowHeight, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined }
-                }
+                style={{ height: CollectionSchemaView._rowHeight, backgroundColor: this.props.isSelected() ? Colors.LIGHT_BLUE : undefined, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined }}
                 onPointerEnter={this.onPointerEnter}
                 onPointerLeave={this.onPointerLeave}
                 ref={(row: HTMLDivElement | null) => {
@@ -103,7 +99,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     className="row-menu"
                     style={{
                         width: CollectionSchemaView._rowMenuWidth,
-                        pointerEvents: !this.isContentActive() ? 'none' : undefined,
+                        pointerEvents: !this.props.isContentActive() ? 'none' : undefined,
                     }}>
                     <div
                         className="schema-row-button"
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index 430a36dce..3496bb835 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -59,7 +59,6 @@ $standard-border-radius: 3px;
 // shadow
 $standard-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
 
-$dashboardselector-height: 32px;
 $mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
 $docDecorations-zindex: 998; // then doc decorations appear over everything else
 $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
@@ -79,7 +78,7 @@ $TREE_BULLET_WIDTH: 20px;
     MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
     SEARCH_THUMBNAIL_SIZE: $search-thumnail-size;
     ANTIMODEMENU_HEIGHT: $antimodemenu-height;
-    DASHBOARD_SELECTOR_HEIGHT: $dashboardselector-height;
+    TOPBAR_HEIGHT: $topbar-height;
     DFLT_IMAGE_NATIVE_DIM: $DFLT_IMAGE_NATIVE_DIM;
     LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH;
     TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts
index 3375579d6..537ea1e5d 100644
--- a/src/client/views/global/globalCssVariables.scss.d.ts
+++ b/src/client/views/global/globalCssVariables.scss.d.ts
@@ -6,7 +6,7 @@ interface IGlobalScss {
     MAX_ROW_HEIGHT: string;
     SEARCH_THUMBNAIL_SIZE: string;
     ANTIMODEMENU_HEIGHT: string;
-    DASHBOARD_SELECTOR_HEIGHT: string;
+    TOPBAR_HEIGHT: string;
     DFLT_IMAGE_NATIVE_DIM: string;
     LEFT_MENU_WIDTH: string;
     TREE_BULLET_WIDTH: string;
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index c229a966a..70ba7e182 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -51,7 +51,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const scaling = Math.min(this.layoutDoc.fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[HeightSym](), this.props.PanelWidth() / this.rootDoc[WidthSym]());
         return (
             <div
-                className={`colorBox-container${this.isContentActive() ? '-interactive' : ''}`}
+                className={`colorBox-container${this.props.isContentActive() ? '-interactive' : ''}`}
                 onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
                 onClick={e => e.stopPropagation()}
                 style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }}>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 541dddd83..9a3e77e6e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -5,7 +5,6 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
 import { observer } from 'mobx-react';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
 import { AclAdmin, AclEdit, AclPrivate, AnimationSym, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
-import { Document } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -15,7 +14,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, returnNone, 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';
@@ -50,11 +49,9 @@ import { FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { LinkAnchorBox } from './LinkAnchorBox';
 import { RadialMenu } from './RadialMenu';
-import { ScriptingBox } from './ScriptingBox';
 import { PresEffect, PresEffectDirection } from './trails';
 import { PinProps, PresBox } from './trails/PresBox';
 import React = require('react');
-import { KeyValueBox } from './KeyValueBox';
 const { Howl } = require('howler');
 
 interface Window {
@@ -135,6 +132,7 @@ export interface DocComponentView {
     componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
     incrementalRendering?: () => void;
     fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox)
+    overridePointerEvents?: () => 'all' | 'none' | undefined; // if the conmponent overrides the pointer events for the document
     fieldKey?: string;
     annotationKey?: string;
     getTitle?: () => string;
@@ -195,6 +193,9 @@ export interface DocumentViewSharedProps {
     forceAutoHeight?: boolean;
     disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
     onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
+    enableDragWhenActive?: boolean;
+    waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
+    defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
     pointerEvents?: () => Opt<string>;
     scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
     createNewFilterDoc?: () => void;
@@ -255,8 +256,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _downX: number = 0;
     private _downY: number = 0;
     private _downTime: number = 0;
-    private _firstX: number = -1;
-    private _firstY: number = -1;
     private _lastTap: number = 0;
     private _doubleTap = false;
     private _mainCont = React.createRef<HTMLDivElement>();
@@ -333,11 +332,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return this.props.NativeHeight();
     }
     @computed get disableClickScriptFunc() {
+        const onScriptDisable = this.props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
+        // prettier-ignore
         return (
             DocumentView.LongPress ||
-            this.props.onClickScriptDisable === 'always' ||
-            this._componentView?.onClickScriptDisable?.() ||
-            (this.props.onClickScriptDisable !== 'never' && (this.rootSelected() || this.props.isSelected())) ||
+            onScriptDisable === 'always' ||
+            (onScriptDisable !== 'never' && (this.rootSelected() || this.props.isSelected())) ||
             this._componentView?.isAnyChildContentActive?.()
         );
     }
@@ -360,7 +360,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     componentDidMount() {
         this.setupHandlers();
     }
-    //componentDidUpdate() { this.setupHandlers(); }
+
     setupHandlers() {
         this.cleanupHandlers(false);
         if (this._mainCont.current) {
@@ -378,151 +378,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
-    handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
-        this.removeMoveListeners();
-        this.removeEndListeners();
-        document.removeEventListener('pointermove', this.onPointerMove);
-        document.removeEventListener('pointerup', this.onPointerUp);
-        if (RadialMenu.Instance._display === false) {
-            this.addHoldMoveListeners();
-            this.addHoldEndListeners();
-            this.onRadialMenu(e, me);
-            const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
-            this._firstX = pt.pageX;
-            this._firstY = pt.pageY;
-        }
-    };
-
-    handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
-        const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
-
-        if (this._firstX === -1 || this._firstY === -1) {
-            return;
-        }
-        if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
-            this.handle1PointerHoldEnd(e, me);
-        }
-    };
-
-    handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
-        this.removeHoldMoveListeners();
-        this.removeHoldEndListeners();
-        RadialMenu.Instance.closeMenu();
-        this._firstX = -1;
-        this._firstY = -1;
-        SelectionManager.DeselectAll();
-        me.touchEvent.stopPropagation();
-        me.touchEvent.preventDefault();
-        e.stopPropagation();
-        if (RadialMenu.Instance.used) {
-            this.onContextMenu(undefined, me.touches[0].pageX, me.touches[0].pageY);
-        }
-    };
-
-    handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
-        if (!this.props.isSelected()) {
-            e.stopPropagation();
-            e.preventDefault();
-
-            this.removeMoveListeners();
-            this.addMoveListeners();
-            this.removeEndListeners();
-            this.addEndListeners();
-        }
-    };
-
-    handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
-        SelectionManager.DeselectAll();
-        if (this.Document.onPointerDown) return;
-        const touch = me.touchEvent.changedTouches.item(0);
-        if (touch) {
-            this._downX = touch.clientX;
-            this._downY = touch.clientY;
-            if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
-                e.stopPropagation();
-            }
-            this.removeMoveListeners();
-            this.addMoveListeners();
-            this.removeEndListeners();
-            this.addEndListeners();
-            e.stopPropagation();
-        }
-    };
-
-    handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
-        if (e.cancelBubble && this.props.isDocumentActive?.()) {
-            this.removeMoveListeners();
-        } else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
-            const touch = me.touchEvent.changedTouches.item(0);
-            if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) {
-                if (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) {
-                    this.cleanUpInteractions();
-                    this.startDragging(this._downX, this._downY, this.Document.dropAction ? (this.Document.dropAction as any) : e.ctrlKey || e.altKey ? 'alias' : undefined);
-                }
-            }
-            e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
-            e.preventDefault();
-        }
-    };
-
-    @action
-    handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
-        const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-        const pt1 = myTouches[0];
-        const pt2 = myTouches[1];
-        const oldPoint1 = this.prevPoints.get(pt1.identifier);
-        const oldPoint2 = this.prevPoints.get(pt2.identifier);
-        const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
-        if (pinching !== 0 && oldPoint1 && oldPoint2) {
-            const dW = Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX);
-            const dH = Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY);
-            const dX = -1 * Math.sign(dW);
-            const dY = -1 * Math.sign(dH);
-
-            if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
-                const doc = Document(this.props.Document);
-                const layoutDoc = Document(Doc.Layout(this.props.Document));
-                let nwidth = Doc.NativeWidth(layoutDoc);
-                let nheight = Doc.NativeHeight(layoutDoc);
-                const width = layoutDoc._width || 0;
-                const height = layoutDoc._height || (nheight / nwidth) * width;
-                const scale = this.props.ScreenToLocalTransform().Scale * this.NativeDimScaling;
-                const actualdW = Math.max(width + dW * scale, 20);
-                const actualdH = Math.max(height + dH * scale, 20);
-                doc.x = (doc.x || 0) + dX * (actualdW - width);
-                doc.y = (doc.y || 0) + dY * (actualdH - height);
-                const fixedAspect = e.ctrlKey || (nwidth && nheight);
-                if (fixedAspect && (!nwidth || !nheight)) {
-                    Doc.SetNativeWidth(layoutDoc, (nwidth = layoutDoc._width || 0));
-                    Doc.SetNativeHeight(layoutDoc, (nheight = layoutDoc._height || 0));
-                }
-                if (nwidth > 0 && nheight > 0) {
-                    if (Math.abs(dW) > Math.abs(dH)) {
-                        if (!fixedAspect) {
-                            Doc.SetNativeWidth(layoutDoc, (actualdW / (layoutDoc._width || 1)) * Doc.NativeWidth(layoutDoc));
-                        }
-                        layoutDoc._width = actualdW;
-                        if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._height = (nheight / nwidth) * layoutDoc._width;
-                        else layoutDoc._height = actualdH;
-                    } else {
-                        if (!fixedAspect) {
-                            Doc.SetNativeHeight(layoutDoc, (actualdH / (layoutDoc._height || 1)) * Doc.NativeHeight(doc));
-                        }
-                        layoutDoc._height = actualdH;
-                        if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._width = (nwidth / nheight) * layoutDoc._height;
-                        else layoutDoc._width = actualdW;
-                    }
-                } else {
-                    dW && (layoutDoc._width = actualdW);
-                    dH && (layoutDoc._height = actualdH);
-                    dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false);
-                }
-            }
-            e.stopPropagation();
-            e.preventDefault();
-        }
-    };
-
     @action
     onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
         const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
@@ -591,11 +446,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
 
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
-        if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+        if (!this.Document.ignoreClick && this.pointerEvents !== 'none' && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
             let stopPropagate = true;
             let preventDefault = true;
             (this.rootDoc._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
             if (this._doubleTap) {
+                const defaultDblclick = this.props.defaultDoubleClick?.() || this.Document.defaultDoubleClick;
                 if (this.onDoubleClickHandler?.script) {
                     const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; // or we could call e.persist() to capture variables
                     // prettier-ignore
@@ -609,7 +465,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             value: undefined,
                         }, console.log  );
                     UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
-                } else if (!Doc.IsSystem(this.rootDoc) && (this.Document.defaultDoubleClick === undefined || this.Document.defaultDoubleClick === 'default')) {
+                } else if (!Doc.IsSystem(this.rootDoc) && (defaultDblclick === undefined || defaultDblclick === 'default')) {
                     UndoManager.RunInBatch(() => this.props.addDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap');
                     SelectionManager.DeselectAll();
                     Doc.UnBrushDoc(this.props.Document);
@@ -649,15 +505,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         LinkFollower.FollowLink(undefined, this.Document, e.altKey);
                     };
                 } else {
+                    // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
-                        // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                         stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                     }
                     preventDefault = false;
                 }
 
                 this._singleClickFunc = clickFunc ?? (() => (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey));
-                if ((clickFunc && this.Document.waitForDoubleClickToClick !== 'never') || this.Document.waitForDoubleClickToClick === 'always') {
+                const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
+                if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
                     this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
                     this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300);
                 } else {
@@ -673,55 +530,46 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         this._longPressSelector = setTimeout(() => DocumentView.LongPress && this.props.select(false), 1000);
-        if (!(e.nativeEvent as any).DownDocView) (e.nativeEvent as any).DownDocView = GestureOverlay.DownDocView = this.props.DocumentView();
-        if (this.rootDoc.type === DocumentType.INK && Doc.ActiveTool === InkTool.Eraser) return;
-        // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document)
-        if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool))) {
-            if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
-                e.stopPropagation();
-                if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
-                // TODO: check here for panning/inking
-            }
-            return;
-        }
+        if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.props.DocumentView();
+
         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
+            // click events stop here if the document is active and no modes are overriding it
+            // if this is part of a template, let the event go up to the template root unless right/ctrl clicking
             if (
-                (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) &&
+                // prettier-ignore
+                this.props.isDocumentActive?.() &&
                 !this.props.onBrowseClick?.() &&
                 !this.Document.ignoreClick &&
                 !e.ctrlKey &&
-                (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
+                e.button === 0 &&
+                this.pointerEvents !== 'none' &&
                 !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)
             ) {
                 e.stopPropagation();
                 // don't preventDefault anymore.  Goldenlayout, PDF text selection and RTF text selection all need it to go though
                 //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault();
+
+                // listen to move events if document content isn't active or document is draggable
+                if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || this.props.enableDragWhenActive || this.rootDoc.enableDragWhenActive)) {
+                    document.addEventListener('pointermove', this.onPointerMove);
+                }
             }
-            if (this.props.isDocumentActive?.()) {
-                document.removeEventListener('pointermove', this.onPointerMove);
-                document.addEventListener('pointermove', this.onPointerMove);
-            }
-            document.removeEventListener('pointerup', this.onPointerUp);
             document.addEventListener('pointerup', this.onPointerUp);
         }
     };
 
     @action
     onPointerMove = (e: PointerEvent): void => {
-        if (this.layoutDoc._lockedPosition || DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) return;
         if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
 
-        if (((!this.topMost && this.props.isDocumentActive?.()) || this.layoutDoc.onDragStart) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
+        if (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
                 this.cleanupPointerEvents();
                 this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType));
             }
-            e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
-            e.preventDefault();
         }
     };
 
@@ -738,13 +586,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this.cleanupPointerEvents();
         this._longPressSelector && clearTimeout(this._longPressSelector);
 
-        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 if (now - this._downTime < 300) {
-            this._doubleTap = now - this._lastTap < 300 && 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
+        } else if (e.button === 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+            this._doubleTap = Date.now() - this._lastTap < Utils.CLICK_TIME;
+            if (!this.props.isSelected(true)) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
         }
         if (DocumentView.LongPress) e.preventDefault();
     };
@@ -1386,11 +1232,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>
         );
     }
-    @observable _: string = '';
+
     renderDoc = (style: object) => {
         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;
+        const background = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
         if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null;
         return (
             this.docContents ?? (
@@ -1399,7 +1245,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     id={this.props.Document[Id]}
                     style={{
                         ...style,
-                        background: isButton || thumb ? undefined : this.backgroundColor,
+                        background: thumb ? undefined : background,
                         opacity: this.opacity,
                         cursor: Doc.ActiveTool === InkTool.None ? 'grab' : 'crosshair',
                         color: StrCast(this.layoutDoc.color, 'inherit'),
@@ -1809,7 +1655,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         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 isButton = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
 
         return this.hidden ? null : (
             <div
@@ -1831,12 +1676,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         ref={this.ContentRef}
                         style={{
                             transition: this.props.dataTransition,
-                            transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
-                            width: isButton ? '100%' : xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
-                            height:
-                                isButton || this.props.LayoutTemplateString?.includes(KeyValueBox.name) || this.props.forceAutoHeight
-                                    ? undefined
-                                    : yshift ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`),
+                            transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
+                            width: xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
+                            height: this.props.forceAutoHeight
+                                ? undefined
+                                : 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/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 75b4907cd..b43e359ff 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -101,7 +101,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
             <div
                 ref={this.createDropTarget}
                 style={{
-                    pointerEvents: !this.isContentActive() ? 'all' : undefined,
+                    pointerEvents: !this.props.isContentActive() ? 'all' : undefined,
                     width: this.props.PanelWidth(),
                     height: this.props.PanelHeight(),
                 }}>
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 9c31ed3e4..57018fb93 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -4,8 +4,9 @@ import { Doc, Field, FieldResult } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, DocCast, NumCast } from '../../../fields/Types';
+import { DocCast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
+import { returnAll, returnAlways, returnTrue } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { SetupDrag } from '../../util/DragManager';
 import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting';
@@ -20,7 +21,6 @@ import './KeyValueBox.scss';
 import { KeyValuePair } from './KeyValuePair';
 import React = require('react');
 import e = require('express');
-import { returnTrue } from '../../../Utils';
 
 export type KVPScript = {
     script: CompiledScript;
@@ -42,8 +42,11 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     componentDidMount() {
         this.props.setContentView?.(this);
     }
-    onClickScriptDisable: () => 'always' = () => 'always';
+    reverseNativeScaling = returnTrue;
+    able = returnAlways;
     fitWidth = returnTrue;
+    overridePointerEvents = returnAll;
+    onClickScriptDisable = returnAlways;
 
     @observable private rows: KeyValuePair[] = [];
 
@@ -69,9 +72,9 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     };
     public static CompileKVPScript(value: string): KVPScript | undefined {
         const eq = value.startsWith('=');
-        value = eq ? value.substr(1) : value;
-        const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith(';=') ? 'script' : false;
-        value = dubEq ? value.substr(2) : value;
+        value = eq ? value.substring(1) : value;
+        const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith('$=') ? 'script' : false;
+        value = dubEq ? value.substring(2) : value;
         const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: false };
         if (dubEq) options.typecheck = false;
         const script = CompileScript(value, options);
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index c4adc7f1a..94434dce7 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,19 +1,18 @@
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, Field, Opt } from '../../../fields/Doc';
-import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist, emptyPath } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
+import { Doc, Field } from '../../../fields/Doc';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { EditableView } from '../EditableView';
+import { DefaultStyleProvider } from '../StyleProvider';
+import { OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { KeyValueBox } from './KeyValueBox';
 import './KeyValueBox.scss';
 import './KeyValuePair.scss';
 import React = require('react');
-import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere } from './DocumentView';
 
 // Represents one row in a key value plane
 
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 470f7e803..46ccdecae 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,6 +1,6 @@
 import React = require('react');
 import { observer } from 'mobx-react';
-import { emptyFunction, returnFalse, returnTrue } from '../../../Utils';
+import { emptyFunction, returnAlways, returnFalse, returnTrue } from '../../../Utils';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { ComparisonBox } from './ComparisonBox';
@@ -12,23 +12,22 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LinkBox, fieldKey);
     }
-    isContentActiveFunc = () => this.isContentActive();
 
-    onClickScriptDisable: () => 'always' = () => 'always';
+    onClickScriptDisable = returnAlways;
     componentDidMount() {
         this.props.setContentView?.(this);
     }
     render() {
         if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => (this.dataDoc.treeViewOpen = true));
         return (
-            <div className={`linkBox-container${this.isContentActive() ? '-interactive' : ''}`} style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }}>
+            <div className={`linkBox-container${this.props.isContentActive() ? '-interactive' : ''}`} style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }}>
                 <ComparisonBox
                     {...this.props}
                     fieldKey="anchor"
                     setHeight={emptyFunction}
                     dontRegisterView={true}
                     renderDepth={this.props.renderDepth + 1}
-                    isContentActive={this.isContentActiveFunc}
+                    isContentActive={this.props.isContentActive}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     moveDocument={returnFalse}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 5683b0fe7..f09962b22 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -8,7 +8,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { returnEmptyString, returnTrue } from '../../../Utils';
+import { returnAlways, returnEmptyString, returnTrue } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { CompileScript, ScriptParam } from '../../util/Scripting';
@@ -114,7 +114,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
         }
     }
 
-    onClickScriptDisable: () => 'always' = () => 'always';
+    onClickScriptDisable = returnAlways;
 
     @action
     componentDidMount() {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 73283263f..d57518a8d 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -876,7 +876,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return WebBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
     };
     @computed get content() {
-        const interactive = this.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
+        const interactive = 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}
diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss
index 7fe1436c7..f3b43501b 100644
--- a/src/client/views/nodes/button/FontIconBox.scss
+++ b/src/client/views/nodes/button/FontIconBox.scss
@@ -158,7 +158,7 @@
         width: 100%;
         border-radius: 100%;
         flex-direction: column;
-        margin-top: -4px;
+        // margin-top: -4px;
 
         svg {
             width: 60% !important;
@@ -427,11 +427,11 @@
     }
 
     .dropbox-background {
-        width: 100vw;
-        height: 100vh;
-        top: 0;
+        width: 200vw;
+        height: 200vh;
+        top: -100vh;
         z-index: 20;
-        left: 0;
+        left: -100vw;
         background: transparent;
         position: fixed;
     }
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 468bcc4d8..28e6eaf1c 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -618,11 +618,10 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     const selected = SelectionManager.Docs().lastElement();
     // prettier-ignore
-    const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void; setMode?: () => void }> = new Map([
+    const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void;}> = new Map([
         ['font', {
             checkResult: () => RichTextMenu.Instance?.fontFamily,
             setDoc: () => value && RichTextMenu.Instance.setFontFamily(value),
-            setMode: () => Doc.UserDoc().textAlign = value,
         }],
         ['highlight', {
             checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight,
@@ -645,8 +644,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
     if (checkResult) {
         return map.get(attr)?.checkResult();
     }
-    if (editorView?.state) map.get(attr)?.setDoc();
-    else map.get(attr)?.setMode?.();
+    map.get(attr)?.setDoc?.();
 });
 
 type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal';
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3ce2366f8..ee4249b02 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1470,20 +1470,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 //      but that's changed, so this shouldn't be needed.
                 //e.stopPropagation();  // if the text box is selected, then it consumes all down events
                 document.addEventListener('pointerup', this.onSelectEnd);
-                document.addEventListener('pointermove', this.onSelectMove);
             }
         }
         if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
             e.preventDefault();
         }
     };
-    onSelectMove = (e: PointerEvent) => {
-        this.props.DocumentView?.().docView?.cancelMoveEvents();
-        e.stopPropagation();
-    };
     onSelectEnd = (e: PointerEvent) => {
         document.removeEventListener('pointerup', this.onSelectEnd);
-        document.removeEventListener('pointermove', this.onSelectMove);
     };
     onPointerUp = (e: React.PointerEvent): void => {
         const editor = this._editorView!;
@@ -1534,6 +1528,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     @observable public static Focused: FormattedTextBox | undefined;
 
     onClick = (e: React.MouseEvent): void => {
+        if (!this.props.isContentActive()) return;
         if ((e.nativeEvent as any).handledByInnerReactInstance) {
             e.stopPropagation();
             return;
@@ -1876,7 +1871,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     r?.addEventListener(
                         'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
                         (e: WheelEvent) => {
-                            if (this.isContentActive()) {
+                            if (this.props.isContentActive()) {
                                 if (!NumCast(this.layoutDoc._scrollTop) && e.deltaY <= 0) e.preventDefault();
                                 e.stopPropagation();
                             }
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e79e7472a..3376c29a9 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1027,8 +1027,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc);
     getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
     panelHeight = () => this.props.PanelHeight() - 40;
-    isContentActive = (outsideReaction?: boolean) =>
-        Doc.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false;
+    isContentActive = (outsideReaction?: boolean) => this.props.isContentActive(outsideReaction);
+    //.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false;
 
     /**
      * For sorting the array so that the order is maintained when it is dropped.
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index eb3087399..c5060a2c2 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -381,7 +381,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
             const target = e.target as any;
             if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
                 this._textSelecting = false;
-                document.addEventListener('pointermove', this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called
             } else {
                 // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
                 setTimeout(
@@ -391,7 +390,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
                 this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, 'htmlAnnotation', { 'pointer-events': 'none' });
                 document.addEventListener('pointerup', this.onSelectEnd);
-                document.addEventListener('pointermove', this.onSelectMove);
             }
         }
     };
@@ -401,12 +399,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
         this.isAnnotating = false;
         this._marqueeing = undefined;
         this._textSelecting = true;
-        document.removeEventListener('pointermove', this.onSelectMove);
-    };
-
-    onSelectMove = (e: PointerEvent) => {
-        this.props.DocumentView?.().docView?.cancelMoveEvents();
-        e.stopPropagation();
     };
 
     @action
@@ -414,7 +406,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
         this.isAnnotating = false;
         clearStyleSheetRules(PDFViewer._annotationStyle);
         this.props.select(false);
-        document.removeEventListener('pointermove', this.onSelectMove);
         document.removeEventListener('pointerup', this.onSelectEnd);
 
         const sel = window.getSelection();
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 4d82551db..553c4525c 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -31,7 +31,7 @@ export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
         const onDelegate = Object.keys(doc).includes(key);
         const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-        return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field));
+        return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field));
     }
     export function toScriptString(field: Field): string {
         switch (typeof field) {
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index feb419597..2b8750714 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -114,7 +114,7 @@ export class ScriptField extends ObjectField {
     }
 
     [ToScriptString]() {
-        return 'script field';
+        return this.script.originalScript;
     }
     [ToString]() {
         return this.script.originalScript;
-- 
cgit v1.2.3-70-g09d2


From 0d899471be676f6619244350982630b151bb4b41 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 28 Mar 2023 19:53:49 -0400
Subject: mostly code cleaning.  small bugs

---
 src/client/documents/Documents.ts                  |  4 +-
 src/client/util/CurrentUserUtils.ts                |  2 +-
 src/client/views/OverlayView.tsx                   |  6 +-
 src/client/views/StyleProvider.tsx                 | 12 +--
 .../views/collections/CollectionTimeView.tsx       |  2 +-
 src/client/views/collections/TabDocView.tsx        |  1 +
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/nodes/DocumentView.scss           |  6 +-
 src/client/views/nodes/DocumentView.tsx            | 93 ++++++++++------------
 src/client/views/nodes/LinkAnchorBox.tsx           | 32 +++-----
 src/client/views/nodes/trails/PresBox.tsx          |  5 +-
 11 files changed, 76 insertions(+), 89 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c484db0db..a110782b2 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -263,6 +263,7 @@ export class DocumentOptions {
     defaultBackgroundColor?: string;
     _isLinkButton?: boolean; // marks a document as a button that will follow its primary link when clicked
     _linkAutoMove?: boolean; // whether link endpoint should move around the edges of a document to make shortest path to other link endpoint
+    hideLinkAnchors?: boolean; // suppresses link anchor dots from being displayed
     isFolder?: boolean;
     lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide)
     activeFrame?: number; // the active frame of a document in a frame base collection
@@ -505,6 +506,7 @@ export namespace Docs {
                     options: {
                         childDontRegisterViews: true,
                         _isLinkButton: true,
+                        hideLinkAnchors: true,
                         _height: 150,
                         description: '',
                         showCaption: 'description',
@@ -581,7 +583,7 @@ export namespace Docs {
                 DocumentType.PRES,
                 {
                     layout: { view: PresBox, dataField: defaultDataKey },
-                    options: { defaultDoubleClick: 'ignore' },
+                    options: { defaultDoubleClick: 'ignore', hideLinkAnchors: true },
                 },
             ],
             [
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b21f53221..753cd1cb7 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -301,7 +301,7 @@ export class CurrentUserUtils {
             { toolTip: "Tap or drag to create a data viz node",   title: "DataViz", icon: "file",       dragFactory: doc.emptyDataViz as Doc,   clickFactory: DocCast(doc.emptyDataViz)},
             { toolTip: "Tap or drag to create a bullet slide",    title: "PPT Slide", icon: "file",     dragFactory: doc.emptySlide as Doc,     clickFactory: DocCast(doc.emptySlide),      openFactoryLocation: OpenWhere.overlay},
             { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc,    clickFactory: DocCast(doc.emptyHeader),     openFactoryAsDelegate: true },
-            { toolTip: "Toggle a Calculator REPL",                title: "repl", icon: "calculator",                                            clickFactory: "repl" as any,                openFactoryLocation: OpenWhere.overlay},
+            { toolTip: "Toggle a Calculator REPL",                title: "repl", icon: "calculator",                                            clickFactory: 'repl' as any,                openFactoryLocation: OpenWhere.overlay},
           ].map(tuple => (
               { openFactoryLocation: OpenWhere.addRight, 
                 scripts: { onClick:     'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)', 
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 34e8cd6dd..ec22128d4 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -219,6 +219,7 @@ export class OverlayView extends React.Component {
                             PanelHeight={d[HeightSym]}
                             ScreenToLocalTransform={this.docScreenToLocalXf(d)}
                             renderDepth={1}
+                            hideDecorations={true}
                             isDocumentActive={returnTrue}
                             isContentActive={returnTrue}
                             whenChildContentsActiveChanged={emptyFunction}
@@ -252,8 +253,3 @@ export class OverlayView extends React.Component {
         );
     }
 }
-// bcz: ugh ... want to be able to pass ScriptingRepl as tag argument, but that doesn't seem to work.. runtime error
-ScriptingGlobals.add(function addOverlayWindow(type: string, options: OverlayElementOptions) {
-    OverlayView.Instance.addWindow(<ScriptingRepl />, options);
-    addOverlayWindow('ScriptingRepl', { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
-});
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index faaa4e1f9..bd0539069 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -36,10 +36,10 @@ export enum StyleProp {
     FillColor = 'fillColor', // fill color of an ink stroke or shape
     WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
     HideLinkButton = 'hideLinkButton', // hides the blue-dot link button.  used when a document acts like a button
-    LinkSource = 'linkSource', // source document of a link -- used by LinkAnchorBox
     PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified
     Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background
     HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that
+    ShowCaption = 'showCaption',
     TitleHeight = 'titleHeight', // Height of Title area
     ShowTitle = 'showTitle', // whether to display a title on a Document (optional :hover suffix)
     JitterRotation = 'jitterRotation', // whether documents should be randomly rotated
@@ -182,6 +182,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 : { path: undefined, width: 0 };
         case StyleProp.JitterRotation:
             return Doc.IsComicStyle(doc) ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
+        case StyleProp.ShowCaption:
+            return doc?._viewType === CollectionViewType.Carousel || props?.hideCaptions ? undefined : StrCast(doc?._showCaption);
         case StyleProp.HeaderMargin:
             return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) ||
                 (doc?.type === DocumentType.RTF && !showTitle()?.includes('noMargin')) ||
@@ -299,12 +301,12 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             if (props?.isDocumentActive?.()) return isInk ? 'visiblePainted' : 'all';
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations:
-            if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform || doc?.x !== undefined || doc?.y !== undefined) {
-                return doc && doc.pointerEvents === 'none' && lockedPosition() && !Doc.IsSystem(doc) && (props?.renderDepth || 0) > 0 ? (
+            if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform) {
+                return doc?.pointerEvents !== 'none' ? null : (
                     <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
-                        <FontAwesomeIcon icon={lockedPosition() ? 'lock' : 'unlock'} style={{ color: lockedPosition() ? 'red' : undefined }} size="lg" />
+                        <FontAwesomeIcon icon={'lock'} style={{ color: 'red' }} size="lg" />
                     </div>
-                ) : null;
+                );
             }
     }
 }
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 89b2fbfe3..4d5978548 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -150,7 +150,7 @@ export class CollectionTimeView extends CollectionSubView() {
                     engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }}
                     fitContentsToBox={returnTrue}
                     childClickScript={this._childClickedScript}
-                    viewDefDivClick={this._viewDefDivClick}
+                    viewDefDivClick={this.layoutEngine() === computeTimelineLayout.name ? undefined : this._viewDefDivClick}
                     //dontScaleFilter={this.dontScaleFilter}
                     layoutEngine={this.layoutEngine}
                 />
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 93b816581..4ad09628f 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -422,6 +422,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     })}
                     renderDepth={0}
                     LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined}
+                    hideTitle={this.props.keyValue}
                     Document={this._document}
                     DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
                     ContainingCollectionView={undefined}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 0714bffbc..f16371592 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -609,7 +609,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         return false;
     }
 
-    marqueeSelect(selectBackgrounds: boolean = true) {
+    marqueeSelect(selectBackgrounds: boolean = false) {
         const selection: Doc[] = [];
         const selectFunc = (doc: Doc) => {
             const layoutDoc = Doc.Layout(doc);
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index e5913d997..1265651ad 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -17,8 +17,7 @@
     top: 0;
 }
 
-.documentView-node,
-.documentView-node-topmost {
+.documentView-node {
     position: inherit;
     top: 0;
     left: 0;
@@ -209,8 +208,7 @@
     }
 }
 
-.documentView-node:hover,
-.documentView-node-topmost:hover {
+.documentView-node:hover {
     > .documentView-styleWrapper {
         > .documentView-titleWrapper-hover {
             display: inline-block;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9a3e77e6e..ab93ce87b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -268,9 +268,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @observable _animateScaleTime: Opt<number>; // milliseconds for animating between views.  defaults to 300 if not uset
     @observable _animateScalingTo = 0;
 
-    private get topMost() {
-        return this.props.renderDepth === 0 && !LightboxView.LightboxDoc;
-    }
     public get animateScaleTime() {
         return this._animateScaleTime ?? 300;
     }
@@ -307,8 +304,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get widgetDecorations() {
         return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations + (this.props.isSelected() ? ':selected' : ''));
     }
-    @computed get backgroundColor() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor);
+    @computed get backgroundBoxColor() {
+        const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb));
+        return thumb ? undefined : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
     }
     @computed get docContents() {
         return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents);
@@ -316,6 +314,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get headerMargin() {
         return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0;
     }
+    @computed get showCaption() {
+        return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.ShowCaption) || 0;
+    }
     @computed get titleHeight() {
         return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0;
     }
@@ -389,7 +390,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             RadialMenu.Instance.addItem({
                 description: 'Delete',
                 event: () => {
-                    this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu();
+                    this.props.ContainingCollectionView?.removeDocument(this.props.Document);
+                    RadialMenu.Instance.closeMenu();
                 },
                 icon: 'external-link-square-alt',
                 selected: -1,
@@ -563,13 +565,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @action
     onPointerMove = (e: PointerEvent): void => {
-        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
+        if (e.buttons !== 1 || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
 
-        if (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
-            if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
-                this.cleanupPointerEvents();
-                this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType));
-            }
+        if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
+            this.cleanupPointerEvents();
+            this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType));
         }
     };
 
@@ -888,7 +888,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         cm.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'question' });
 
-        if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
+        e?.stopPropagation(); // DocumentViews should stop propagation of this event
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
     };
 
@@ -1021,7 +1021,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         switch (property.split(':')[0]) {
             case StyleProp.ShowTitle:     return '';
             case StyleProp.PointerEvents: return 'none';
-            case StyleProp.LinkSource:    return this.props.Document; // pass the LinkSource to the LinkAnchorBox
             case StyleProp.Highlighting:  return undefined;
         }
         return this.props.styleProvider?.(doc, props, property);
@@ -1049,9 +1048,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get allLinkEndpoints() {
         // the small blue dots that mark the endpoints of links
         TraceMobx();
-        if (this.props.hideLinkAnchors) return null;
-        if (this.layoutDoc.unrendered || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
-        if (this.rootDoc.type === DocumentType.PRES || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return null;
+        if (this.props.hideLinkAnchors || this.layoutDoc.hideLinkAnchors || this.props.dontRegisterView || this.layoutDoc.unrendered) return null;
         const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => d.linkDisplay);
         return filtered.map((link, i) => (
             <div className="documentView-anchorCont" key={link[Id]}>
@@ -1059,11 +1056,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     {...this.props}
                     isContentActive={returnFalse}
                     Document={link}
+                    docViewPath={this.props.viewPath}
                     PanelWidth={this.anchorPanelWidth}
                     PanelHeight={this.anchorPanelHeight}
                     dontRegisterView={false}
                     showTitle={returnEmptyString}
                     hideCaptions={true}
+                    hideLinkAnchors={true}
                     fitWidth={returnTrue}
                     styleProvider={this.anchorStyleProvider}
                     removeDocument={this.hideLinkAnchor}
@@ -1148,12 +1147,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const ffscale = () => this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1;
         const showTitle = this.ShowTitle?.split(':')[0];
         const showTitleHover = this.ShowTitle?.includes(':hover');
-        const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined;
-        const captionView = !showCaption ? null : (
+        const captionView = !this.showCaption ? null : (
             <div
                 className="documentView-captionWrapper"
                 style={{
-                    pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+                    pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
                     minWidth: 50 * ffscale(),
                     maxHeight: `max(100%, ${20 * ffscale()}px)`,
                 }}>
@@ -1161,12 +1159,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     {...OmitKeys(this.props, ['children']).omit}
                     yPadding={10}
                     xPadding={10}
-                    fieldKey={showCaption}
+                    fieldKey={this.showCaption}
                     fontSize={12 * Math.max(1, (2 * ffscale()) / 3)}
                     styleProvider={this.captionStyleProvider}
                     dontRegisterView={true}
                     noSidebar={true}
                     dontScale={true}
+                    renderDepth={this.props.renderDepth}
                     isContentActive={this.isContentActive}
                     onClick={this.onClickFunc}
                 />
@@ -1220,7 +1219,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 />
             </div>
         );
-        return this.props.hideTitle || (!showTitle && !showCaption) ? (
+        return this.props.hideTitle || (!showTitle && !this.showCaption) ? (
             this.contents
         ) : (
             <div className="documentView-styleWrapper">
@@ -1235,31 +1234,27 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     renderDoc = (style: object) => {
         TraceMobx();
-        const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
-        const background = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
-        if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null;
-        return (
-            this.docContents ?? (
-                <div
-                    className={`documentView-node${this.topMost ? '-topmost' : ''}`}
-                    id={this.props.Document[Id]}
-                    style={{
-                        ...style,
-                        background: thumb ? undefined : background,
-                        opacity: this.opacity,
-                        cursor: Doc.ActiveTool === InkTool.None ? 'grab' : 'crosshair',
-                        color: StrCast(this.layoutDoc.color, 'inherit'),
-                        fontFamily: StrCast(this.Document._fontFamily, 'inherit'),
-                        fontSize: Cast(this.Document._fontSize, 'string', null),
-                        transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
-                        transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
-                    }}>
-                    {this.innards}
-                    {!this.disableClickScriptFunc && this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : null}
-                    {this.widgetDecorations ?? null}
-                </div>
-            )
-        );
+        return !DocCast(this.Document) || GetEffectiveAcl(this.Document[DataSym]) === AclPrivate || this.hidden
+            ? null
+            : this.docContents ?? (
+                  <div
+                      className="documentView-node"
+                      id={this.Document[Id]}
+                      style={{
+                          ...style,
+                          background: this.backgroundBoxColor,
+                          opacity: this.opacity,
+                          cursor: Doc.ActiveTool === InkTool.None ? 'grab' : 'crosshair',
+                          color: StrCast(this.layoutDoc.color, 'inherit'),
+                          fontFamily: StrCast(this.Document._fontFamily, 'inherit'),
+                          fontSize: Cast(this.Document._fontSize, 'string', null),
+                          transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
+                          transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
+                      }}>
+                      {this.innards}
+                      {this.widgetDecorations ?? null}
+                  </div>
+              );
     };
 
     /**
@@ -1506,7 +1501,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth();
     }
     @computed get panelHeight() {
-        if (this.effectiveNativeHeight && !this.layoutDoc.nativeHeightUnfrozen) {
+        if (this.effectiveNativeHeight && (!this.fitWidth || !this.layoutDoc.nativeHeightUnfrozen)) {
             return Math.min(this.props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling);
         }
         return this.props.PanelHeight();
@@ -1528,7 +1523,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight());
     getBounds = () => {
-        if (!this.docView || !this.docView.ContentDiv || this.props.Document.type === DocumentType.PRES || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
+        if (!this.docView || !this.docView.ContentDiv || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
             return undefined;
         }
         const xf = this.docView?.props
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index e12548f18..3feb95ce9 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,22 +1,19 @@
-import { action, observable } from 'mobx';
+import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../../fields/Doc';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
-import { ContextMenu } from '../ContextMenu';
-import { ContextMenuProps } from '../ContextMenuItem';
+import { SelectionManager } from '../../util/SelectionManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LinkAnchorBox.scss';
 import { LinkDocPreview } from './LinkDocPreview';
 import React = require('react');
-import { LinkManager } from '../../util/LinkManager';
 import globalCssVariables = require('../global/globalCssVariables.scss');
-import { SelectionManager } from '../../util/SelectionManager';
 
 @observer
 export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -31,19 +28,15 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable _x = 0;
     @observable _y = 0;
 
+    @computed get linkSource() {
+        return this.props.docViewPath()[this.props.docViewPath().length - 2].rootDoc; // this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
+    }
+
     onPointerDown = (e: React.PointerEvent) => {
-        const anchorContainerDoc = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
-        setupMoveUpEvents(
-            this,
-            e,
-            this.onPointerMove,
-            emptyFunction,
-            (e, doubleTap) => {
-                if (doubleTap) LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, false);
-                else this.props.select(false);
-            },
-            false
-        );
+        setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
+            if (doubleTap) LinkFollower.FollowLink(this.rootDoc, this.linkSource, false);
+            else this.props.select(false);
+        });
     };
     onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
         const cdiv = this._ref?.current?.parentElement;
@@ -73,7 +66,6 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const small = this.props.PanelWidth() <= 1; // this happens when rendered in a treeView
         const x = NumCast(this.rootDoc[this.fieldKey + '_x'], 100);
         const y = NumCast(this.rootDoc[this.fieldKey + '_y'], 100);
-        const linkSource = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
         const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor + ':anchor');
         const anchor = this.fieldKey === 'anchor1' ? 'anchor2' : 'anchor1';
         const anchorScale = !this.dataDoc[this.fieldKey + '-useLinkSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25;
@@ -87,7 +79,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 onPointerEnter={e =>
                     LinkDocPreview.SetLinkInfo({
                         docProps: this.props,
-                        linkSrc: linkSource,
+                        linkSrc: this.linkSource,
                         linkDoc: this.rootDoc,
                         showHeader: true,
                         location: [e.clientX, e.clientY + 20],
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 3376c29a9..0afb36214 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -919,6 +919,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     @action
     enterMinimize = () => {
+        this.updateCurrentPresentation(this.rootDoc);
         clearTimeout(this._presTimer);
         const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
         this.props.removeDocument?.(this.layoutDoc);
@@ -939,7 +940,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         doc._height = 30;
         doc._width = PresBox.minimizedWidth;
         Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
-        PresBox.Instance.initializePresState(PresBox.Instance.itemIndex);
+        PresBox.Instance?.initializePresState(PresBox.Instance.itemIndex);
         return (doc.presStatus = PresStatus.Manual);
     }
 
@@ -2435,7 +2436,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     </Tooltip>
                     <div className="presPanel-button-text">
                         Slide {this.itemIndex + 1}
-                        {this.activeItem.presIndexed !== undefined ? `(${this.activeItem.presIndexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
+                        {this.activeItem?.presIndexed !== undefined ? `(${this.activeItem.presIndexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
                     </div>
                     <div className="presPanel-divider" />
                     <div className="presPanel-button-text" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}>
-- 
cgit v1.2.3-70-g09d2


From 34d63367302d8158c71361dbcefca9e528c03333 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 11 Apr 2023 09:58:21 -0400
Subject: fixed explore mode to not wait for double clicks

---
 src/client/views/MainView.tsx                                            | 1 +
 src/client/views/collections/TabDocView.tsx                              | 1 +
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx      | 1 +
 3 files changed, 3 insertions(+)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b0888df68..60459cf30 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -568,6 +568,7 @@ export class MainView extends React.Component {
     @computed get exploreMode() {
         return () => (this._exploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
     }
+    waitForDoubleClick = () => (this._exploreMode ? 'never' : undefined);
     headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
     mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
     @computed get headerBarDocView() {
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 4ad09628f..458712999 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -428,6 +428,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     ContainingCollectionView={undefined}
                     ContainingCollectionDoc={undefined}
                     onBrowseClick={MainView.Instance.exploreMode}
+                    waitForDoubleClickToClick={MainView.Instance.waitForDoubleClick}
                     isContentActive={returnTrue}
                     isDocumentActive={returnFalse}
                     PanelWidth={this.PanelWidth}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9d4a788db..1ecc69174 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1237,6 +1237,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
                 LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
                 rootSelected={childData ? this.rootSelected : returnFalse}
+                waitForDoubleClickToClick={this.props.waitForDoubleClickToClick}
                 onClick={this.onChildClickHandler}
                 onKey={this.onKeyDown}
                 onDoubleClick={this.onChildDoubleClickHandler}
-- 
cgit v1.2.3-70-g09d2


From 5cd64622f14ede408d3baca4a10d155b60392e46 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 13 Apr 2023 22:22:07 -0400
Subject: lots of changes to get rid of ContainingCollectionDoc and
 ContainingCollectionView props.

---
 src/client/documents/Documents.ts                  |  8 +--
 src/client/util/CurrentUserUtils.ts                |  2 +-
 src/client/util/DocumentManager.ts                 | 83 ++++++++++------------
 src/client/util/DragManager.ts                     |  4 +-
 .../util/Import & Export/DirectoryImportBox.tsx    |  6 +-
 src/client/util/ReplayMovements.ts                 | 64 ++++++-----------
 src/client/util/SelectionManager.ts                |  6 +-
 src/client/util/SharingManager.tsx                 |  5 +-
 src/client/util/TrackMovements.ts                  | 36 +++++-----
 src/client/views/DashboardView.tsx                 | 17 ++---
 src/client/views/DocComponent.tsx                  |  1 -
 src/client/views/DocumentDecorations.tsx           |  8 +--
 src/client/views/GestureOverlay.tsx                |  4 +-
 src/client/views/InkStrokeProperties.ts            |  3 +-
 src/client/views/LightboxView.tsx                  |  4 +-
 src/client/views/MainView.tsx                      | 20 ++----
 src/client/views/OverlayView.tsx                   | 10 +--
 src/client/views/Palette.tsx                       | 31 ++++----
 src/client/views/PropertiesDocContextSelector.tsx  | 22 ++++--
 src/client/views/PropertiesView.tsx                |  2 -
 src/client/views/StyleProvider.tsx                 | 11 ++-
 src/client/views/TemplateMenu.tsx                  |  2 -
 .../views/collections/CollectionCarouselView.tsx   |  1 -
 src/client/views/collections/CollectionMenu.tsx    |  6 +-
 .../views/collections/CollectionNoteTakingView.tsx |  4 --
 .../views/collections/CollectionPileView.tsx       |  2 +-
 .../collections/CollectionStackedTimeline.tsx      |  2 -
 .../views/collections/CollectionStackingView.tsx   |  4 --
 .../views/collections/CollectionTreeView.tsx       | 10 +--
 src/client/views/collections/CollectionView.tsx    |  5 +-
 src/client/views/collections/TabDocView.tsx        |  6 +-
 src/client/views/collections/TreeView.tsx          | 54 ++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 31 ++++----
 .../collectionLinear/CollectionLinearView.tsx      |  5 +-
 .../CollectionMulticolumnView.tsx                  |  2 -
 .../CollectionMultirowView.tsx                     |  2 -
 .../collectionSchema/CollectionSchemaView.tsx      |  6 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |  2 +-
 .../collectionSchema/SchemaTableCell.tsx           |  2 -
 src/client/views/linking/LinkMenuItem.tsx          | 21 +-----
 src/client/views/linking/LinkPopup.tsx             |  4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  7 +-
 src/client/views/nodes/DocumentContentsView.tsx    |  1 -
 src/client/views/nodes/DocumentLinksButton.tsx     |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 12 ++--
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/KeyValuePair.tsx            |  2 -
 src/client/views/nodes/LinkDocPreview.tsx          |  4 +-
 src/client/views/nodes/ScreenshotBox.tsx           |  5 +-
 src/client/views/nodes/button/FontIconBox.tsx      |  4 +-
 .../views/nodes/formattedText/DashDocView.tsx      |  7 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |  5 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 20 ++++--
 src/client/views/nodes/trails/PresBox.tsx          |  1 -
 src/client/views/nodes/trails/PresElementBox.tsx   | 27 ++++---
 src/client/views/topbar/TopBar.tsx                 | 51 ++-----------
 src/mobile/AudioUpload.tsx                         | 76 ++++++++++----------
 src/mobile/MobileInterface.tsx                     |  4 +-
 58 files changed, 291 insertions(+), 456 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8d97ce5af..09b45a481 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1285,10 +1285,6 @@ export namespace DocUtils {
         return rangeFilteredDocs;
     }
 
-    export function DefaultFocus(doc: Doc, options: DocFocusOptions) {
-        return undefined;
-    }
-
     export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = [];
 
     export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) {
@@ -1299,10 +1295,9 @@ export namespace DocUtils {
         });
     }
 
-    export function MakeLink(source: Doc, target: Doc, linkSettings: { linkRelationship?: string; description?: string; linkDisplay?: boolean }, id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
+    export function MakeLink(source: Doc, target: Doc, linkSettings: { linkRelationship?: string; description?: string; linkDisplay?: boolean }, id?: string, showPopup?: number[]) {
         if (!linkSettings.linkRelationship) linkSettings.linkRelationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
         const sv = DocumentManager.Instance.getDocumentView(source);
-        if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target) return;
         if (target.doc === Doc.UserDoc()) return undefined;
 
         const makeLink = action((linkDoc: Doc, showPopup?: number[]) => {
@@ -1371,7 +1366,6 @@ export namespace DocUtils {
                         value: 'any',
                         _readOnly_: 'boolean',
                         scriptContext: 'any',
-                        thisContainer: Doc.name,
                         documentView: Doc.name,
                         heading: Doc.name,
                         checked: 'boolean',
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 76ea3e3ea..abf7313a4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -110,7 +110,7 @@ export class CurrentUserUtils {
         const tempClicks = DocCast(doc[field]);
         const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, system: true};
         const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
-            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self])))"},
+            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCast(documentView?.props.docViewPath().lastElement()?.rootDoc.target).then((target) => target && (target.proto.data = new List([self])))"},
             { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: `openDoc(self.doubleClickView.${OpenWhere.addRight})`}];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ccf370662..ad89e8653 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,15 +1,11 @@
-import { loadAsync } from 'jszip';
-import { action, observable, ObservableSet, runInAction } from 'mobx';
+import { action, observable, ObservableSet } from 'mobx';
 import { AnimationSym, Doc, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { listSpec } from '../../fields/Schema';
 import { Cast, DocCast, StrCast } from '../../fields/Types';
 import { AudioField } from '../../fields/URLField';
-import { emptyFunction } from '../../Utils';
 import { CollectionViewType } from '../documents/DocumentTypes';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
-import { CollectionView } from '../views/collections/CollectionView';
 import { TabDocView } from '../views/collections/TabDocView';
 import { LightboxView } from '../views/LightboxView';
 import { MainView } from '../views/MainView';
@@ -39,7 +35,7 @@ export class DocumentManager {
     private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = [];
     public AddViewRenderedCb = (doc: Opt<Doc>, func: (dv: DocumentView) => any) => {
         if (doc) {
-            const dv = this.getDocumentViewById(doc[Id]);
+            const dv = this.getDocumentView(doc);
             this._viewRenderedCbs.push({ doc, func });
             if (dv) {
                 this.callAddViewFuncs(dv);
@@ -129,42 +125,24 @@ export class DocumentManager {
         return this.getDocumentViewsById(doc[Id]);
     }
 
-    public getDocumentViewById(id: string, preferredCollection?: CollectionView): DocumentView | undefined {
-        if (!id) return undefined;
-        let toReturn: DocumentView | undefined;
-        const passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
-
-        for (const pass of passes) {
-            Array.from(DocumentManager.Instance.DocumentViews).map(view => {
-                if (view.rootDoc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
-                    toReturn = view;
-                    return;
-                }
-            });
-            if (!toReturn) {
-                Array.from(DocumentManager.Instance.DocumentViews).map(view => {
-                    const doc = view.rootDoc.proto;
-                    if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
-                        toReturn = view;
-                    }
-                });
-            } else {
-                break;
-            }
-        }
-
-        return toReturn;
-    }
-
-    public getDocumentView(toFind: Doc, preferredCollection?: CollectionView): DocumentView | undefined {
-        const found =
+    public getDocumentView(toFind: Doc | undefined, preferredCollection?: DocumentView): DocumentView | undefined {
+        const doc =
+            // bcz: this was temporary code used to match documents by data url instead of by id. intended only for repairing the DB
             // Array.from(DocumentManager.Instance.DocumentViews).find(
             //     dv =>
             //         ((dv.rootDoc.data as any)?.url?.href && (dv.rootDoc.data as any)?.url?.href === (toFind.data as any)?.url?.href) ||
             //         ((DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href && (DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href === (DocCast(toFind.annotationOn)?.data as any)?.url?.href)
             // )?.rootDoc ??
             toFind;
-        return this.getDocumentViewById(found[Id], preferredCollection);
+        const docViewArray = Array.from(DocumentManager.Instance.DocumentViews);
+        const passes = !doc ? [] : preferredCollection ? [preferredCollection, undefined] : [undefined];
+        return passes.reduce(
+            (pass, toReturn) =>
+                toReturn ??
+                docViewArray.filter(view => view.rootDoc === doc).find(view => !pass || view.props.docViewPath().lastElement() === preferredCollection) ??
+                docViewArray.filter(view => Doc.GetProto(view.rootDoc) === doc).find(view => !pass || view.props.docViewPath().lastElement() === preferredCollection),
+            undefined as Opt<DocumentView>
+        );
     }
 
     public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
@@ -247,7 +225,8 @@ export class DocumentManager {
     public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => {
         const docViewPath = targetDocView.docViewPath.slice();
         let rootContextView = docViewPath.shift();
-        return rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined }));
+        await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined })));
+        if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden;
     };
 
     // shows a document by first:
@@ -318,15 +297,25 @@ export class DocumentManager {
         }
     }
 }
-export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) {
-    const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc);
-    const dv = DocumentManager.Instance.getDocumentView(doc, (cv?.ComponentView as CollectionFreeFormView)?.props.CollectionView);
-    if (dv) {
-        DocumentManager.Instance.showDocumentView(dv, { willZoomCentered: true });
-    } else {
-        const context = doc.context !== Doc.MyFilesystem && Cast(doc.context, Doc, null);
-        const showDoc = context || doc;
-        DocumentManager.Instance.showDocument(Doc.BestAlias(showDoc), { openLocation: OpenWhere.addRight }, () => DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }));
-    }
+export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, openLocation: OpenWhere.addRight }, containingDoc?: Doc) {
+    const func = () => {
+        const cv = DocumentManager.Instance.getDocumentView(containingDoc);
+        const dv = DocumentManager.Instance.getDocumentView(doc, cv);
+        if (dv && (!containingDoc || dv.props.docViewPath().lastElement()?.Document === containingDoc)) {
+            DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc));
+        } else {
+            const showDoc = Doc.BestAlias(DocCast((containingDoc ?? doc.context) !== Doc.MyFilesystem ? containingDoc ?? doc.context : undefined, doc));
+            DocumentManager.Instance.showDocument(showDoc, { ...options, toggleTarget: undefined }, () => DocumentManager.Instance.showDocument(doc, options)).then(() => {
+                const cv = DocumentManager.Instance.getDocumentView(containingDoc);
+                const dv = DocumentManager.Instance.getDocumentView(doc, cv);
+                dv && Doc.linkFollowHighlight(dv.rootDoc);
+            });
+        }
+    };
+    if (doc.hidden) {
+        doc.hidden = false;
+        options.toggleTarget = false;
+        setTimeout(func);
+    } else func();
 }
 ScriptingGlobals.add(DocFocusOrOpen);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 7d2aa813f..7e6de5e67 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -3,10 +3,8 @@ import { DateField } from '../../fields/DateField';
 import { Doc, Field, Opt, StrListCast } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
-import { listSpec } from '../../fields/Schema';
-import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
 import { ScriptField } from '../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
+import { BoolCast, ScriptCast, StrCast } from '../../fields/Types';
 import { emptyFunction, Utils } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import * as globalCssVariables from '../views/global/globalCssVariables.scss';
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 559958c2b..76b1323c4 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -14,12 +14,14 @@ import { AcceptableMedia, Upload } from '../../../server/SharedMediaTypes';
 import { Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
 import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
 import { DocumentManager } from '../DocumentManager';
 import './DirectoryImportBox.scss';
 import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry';
 import React = require('react');
+import _ = require('lodash');
 
 const unsupported = ['text/html', 'text/plain'];
 
@@ -155,8 +157,8 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
             x: NumCast(doc.x),
             y: NumCast(doc.y) + offset,
         };
-        const parent = this.props.ContainingCollectionView;
-        if (parent) {
+        const parent = _.nth(this.props.docViewPath(), -2); // last element of path is this box's document view, 2nd to last is any collection or other document that may contain it.
+        if (parent?.rootDoc.type === DocumentType.COL) {
             let importContainer: Doc;
             if (docs.length < 50) {
                 importContainer = Docs.Create.MasonryDocument(docs, options);
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index d5bffc5e2..40261985a 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -7,6 +7,7 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie
 import { DocServer } from '../DocServer';
 import { Movement, Presentation } from './TrackMovements';
 import { OpenWhereMod } from '../views/nodes/DocumentView';
+import { returnTransparent } from '../../Utils';
 
 export class ReplayMovements {
     private timers: NodeJS.Timeout[] | null;
@@ -60,17 +61,11 @@ export class ReplayMovements {
             return;
         }
 
-        let docIdtoDoc: Map<string, Doc> = new Map();
-        try {
-            docIdtoDoc = await this.loadPresentation(presentation);
-        } catch {
-            console.error('[recordingApi.ts] setVideoBox(): error loading presentation - no replay movements');
-            throw 'error loading docs from server';
-        }
+        const docIdtoDoc = this.loadPresentation(presentation);
 
         this.videoBoxDisposeFunc = reaction(
             () => ({ playing: videoBox._playing, timeViewed: videoBox.player?.currentTime || 0 }),
-            ({ playing, timeViewed }) => (playing ? this.playMovements(presentation, docIdtoDoc, timeViewed) : this.pauseMovements())
+            ({ playing, timeViewed }) => (playing ? this.playMovements(presentation, timeViewed) : this.pauseMovements())
         );
         this.videoBox = videoBox;
     };
@@ -93,45 +88,33 @@ export class ReplayMovements {
         this.pauseMovements();
     };
 
-    loadPresentation = async (presentation: Presentation) => {
+    loadPresentation = (presentation: Presentation) => {
         const { movements } = presentation;
         if (movements === null) {
             throw '[recordingApi.ts] followMovements() failed: no presentation data';
         }
 
         // generate a set of all unique docIds
-        const docIds = new Set<string>();
-        for (const { docId } of movements) {
-            if (!docIds.has(docId)) docIds.add(docId);
-        }
-
-        const docIdtoDoc = new Map<string, Doc>();
-
-        let refFields = await DocServer.GetRefFields([...docIds.keys()]);
-        for (const docId in refFields) {
-            if (!refFields[docId]) {
-                throw `one field was undefined`;
-            }
-            docIdtoDoc.set(docId, refFields[docId] as Doc);
+        const docs = new Set<Doc>();
+        for (const { doc } of movements) {
+            if (!docs.has(doc)) docs.add(doc);
         }
-        // console.info('loadPresentation refFields', refFields, docIdtoDoc);
 
-        return docIdtoDoc;
+        return docs;
     };
 
     // returns undefined if the docView isn't open on the screen
-    getCollectionFFView = (docId: string) => {
-        const isInView = DocumentManager.Instance.getDocumentViewById(docId);
+    getCollectionFFView = (doc: Doc) => {
+        const isInView = DocumentManager.Instance.getDocumentView(doc);
         if (isInView) {
             return isInView.ComponentView as CollectionFreeFormView;
         }
     };
 
     // will open the doc in a tab then return the CollectionFFView that holds it
-    openTab = (docId: string, docIdtoDoc: Map<string, Doc>) => {
-        const doc = docIdtoDoc.get(docId);
-        if (doc == undefined) {
-            console.error(`docIdtoDoc did not contain docId ${docId}`);
+    openTab = (doc: Doc) => {
+        if (doc === undefined) {
+            console.error(`doc undefined`);
             return undefined;
         }
         // console.log('openTab', docId, doc);
@@ -149,13 +132,12 @@ export class ReplayMovements {
         document.Document._panY = panY;
     };
 
-    getFirstMovements = (movements: Movement[]): Map<string, Movement> => {
+    getFirstMovements = (movements: Movement[]): Map<Doc, Movement> => {
         if (movements === null) return new Map();
         // generate a set of all unique docIds
-        const docIdtoFirstMove = new Map();
+        const docIdtoFirstMove = new Map<Doc, Movement>();
         for (const move of movements) {
-            const { docId } = move;
-            if (!docIdtoFirstMove.has(docId)) docIdtoFirstMove.set(docId, move);
+            if (!docIdtoFirstMove.has(move.doc)) docIdtoFirstMove.set(move.doc, move);
         }
         return docIdtoFirstMove;
     };
@@ -165,7 +147,7 @@ export class ReplayMovements {
         Doc.UserDoc().presentationMode = 'none';
     };
 
-    public playMovements = (presentation: Presentation, docIdtoDoc: Map<string, Doc>, timeViewed: number = 0) => {
+    public playMovements = (presentation: Presentation, timeViewed: number = 0) => {
         // console.info('playMovements', presentation, timeViewed, docIdtoDoc);
 
         if (presentation.movements === null || presentation.movements.length === 0) {
@@ -183,13 +165,13 @@ export class ReplayMovements {
         const handleFirstMovements = () => {
             // if the first movement is a closed tab, open it
             const firstMovement = filteredMovements[0];
-            const isClosed = this.getCollectionFFView(firstMovement.docId) === undefined;
-            if (isClosed) this.openTab(firstMovement.docId, docIdtoDoc);
+            const isClosed = this.getCollectionFFView(firstMovement.doc) === undefined;
+            if (isClosed) this.openTab(firstMovement.doc);
 
             // for the open tabs, set it to the first move
             const docIdtoFirstMove = this.getFirstMovements(filteredMovements);
-            for (const [docId, firstMove] of docIdtoFirstMove) {
-                const colFFView = this.getCollectionFFView(docId);
+            for (const [doc, firstMove] of docIdtoFirstMove) {
+                const colFFView = this.getCollectionFFView(doc);
                 if (colFFView) this.zoomAndPan(firstMove, colFFView);
             }
         };
@@ -200,12 +182,12 @@ export class ReplayMovements {
             const timeDiff = movement.time - timeViewed * 1000;
 
             return setTimeout(() => {
-                const collectionFFView = this.getCollectionFFView(movement.docId);
+                const collectionFFView = this.getCollectionFFView(movement.doc);
                 if (collectionFFView) {
                     this.zoomAndPan(movement, collectionFFView);
                 } else {
                     // tab wasn't open - open it and play the movement
-                    const openedColFFView = this.openTab(movement.docId, docIdtoDoc);
+                    const openedColFFView = this.openTab(movement.doc);
                     console.log('openedColFFView', openedColFFView);
                     openedColFFView && this.zoomAndPan(movement, openedColFFView);
                 }
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 313c255a0..bfad93334 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,4 +1,3 @@
-import { ModalManager } from '@material-ui/core';
 import { action, observable, ObservableMap } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { Doc, Opt } from '../../fields/Doc';
@@ -64,8 +63,9 @@ export namespace SelectionManager {
     export function DeselectView(docView?: DocumentView): void {
         manager.DeselectView(docView);
     }
-    export function SelectView(docView: DocumentView, ctrlPressed: boolean): void {
-        manager.SelectView(docView, ctrlPressed);
+    export function SelectView(docView: DocumentView | undefined, ctrlPressed: boolean): void {
+        if (!docView) DeselectAll();
+        else manager.SelectView(docView, ctrlPressed);
     }
     export function SelectSchemaViewDoc(document: Opt<Doc>, deselectAllFirst?: boolean): void {
         if (deselectAllFirst) manager.DeselectAll();
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 3c05af4bb..a73eda04c 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -370,11 +370,10 @@ export class SharingManager extends React.Component<{}> {
         const docs = SelectionManager.Views().length > 1 ? SelectionManager.Views().map(docView => docView.props.Document) : [this.targetDoc];
         return (
             <span
-                className={'focus-span'}
+                className="focus-span"
                 title={title}
                 onClick={() => {
-                    let context: Opt<CollectionView>;
-                    if (this.targetDoc && this.targetDocView && docs.length === 1 && (context = this.targetDocView.props.ContainingCollectionView)) {
+                    if (this.targetDoc && this.targetDocView && docs.length === 1) {
                         DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
                     }
                 }}
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index 4a2ccd706..2f16307b3 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -9,7 +9,7 @@ export type Movement = {
     panX: number;
     panY: number;
     scale: number;
-    docId: string;
+    doc: Doc;
 };
 
 export type Presentation = {
@@ -28,7 +28,7 @@ export class TrackMovements {
     private tracking: boolean;
     private absoluteStart: number;
     // instance variable for holding the FFViews and their disposers
-    private recordingFFViews: Map<string, IReactionDisposer> | null;
+    private recordingFFViews: Map<Doc, IReactionDisposer> | null;
     private tabChangeDisposeFunc: IReactionDisposer | null;
 
     // create static instance and getter for global use
@@ -55,33 +55,33 @@ export class TrackMovements {
         return this.currentPresentation.movements === null;
     }
 
-    private addRecordingFFView(doc: Doc, key: string = doc[Id]): void {
+    private addRecordingFFView(doc: Doc): void {
         // console.info('adding dispose func : docId', key, 'doc', doc);
 
         if (this.recordingFFViews === null) {
             console.warn('addFFView on null RecordingApi');
             return;
         }
-        if (this.recordingFFViews.has(key)) {
-            console.warn('addFFView : key already in map');
+        if (this.recordingFFViews.has(doc)) {
+            console.warn('addFFView : doc already in map');
             return;
         }
 
         const disposeFunc = reaction(
             () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0) }),
-            res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, key, res.scale)
+            res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, doc, res.scale)
         );
-        this.recordingFFViews?.set(key, disposeFunc);
+        this.recordingFFViews?.set(doc, disposeFunc);
     }
 
-    private removeRecordingFFView = (key: string) => {
+    private removeRecordingFFView = (doc: Doc) => {
         // console.info('removing dispose func : docId', key);
         if (this.recordingFFViews === null) {
             console.warn('removeFFView on null RecordingApi');
             return;
         }
-        this.recordingFFViews.get(key)?.();
-        this.recordingFFViews.delete(key);
+        this.recordingFFViews.get(doc)?.();
+        this.recordingFFViews.delete(doc);
     };
 
     // in the case where only one tab was changed (updates not across dashboards), set only one to true
@@ -90,15 +90,15 @@ export class TrackMovements {
 
         // so that the size comparisons are correct, we must filter to only the FFViews
         const isFFView = (doc: Doc) => doc && 'viewType' in doc && doc.viewType === 'freeform';
-        const tabbedFFViews = new Set<string>();
+        const tabbedFFViews = new Set<Doc>();
         for (const DashDoc of tabbedDocs) {
-            if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]);
+            if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc);
         }
 
         // new tab was added - need to add it
         if (tabbedFFViews.size > this.recordingFFViews.size) {
             for (const DashDoc of tabbedDocs) {
-                if (!this.recordingFFViews.has(DashDoc[Id])) {
+                if (!this.recordingFFViews.has(DashDoc)) {
                     if (isFFView(DashDoc)) {
                         this.addRecordingFFView(DashDoc);
 
@@ -110,9 +110,9 @@ export class TrackMovements {
         }
         // tab was removed - need to remove it from recordingFFViews
         else if (tabbedFFViews.size < this.recordingFFViews.size) {
-            for (const [key] of this.recordingFFViews) {
-                if (!tabbedFFViews.has(key)) {
-                    this.removeRecordingFFView(key);
+            for (const [doc] of this.recordingFFViews) {
+                if (!tabbedFFViews.has(doc)) {
+                    this.removeRecordingFFView(doc);
                     if (onlyOne) return;
                 }
             }
@@ -214,7 +214,7 @@ export class TrackMovements {
         }
     };
 
-    private trackMovement = (panX: number, panY: number, docId: string, scale: number = 0) => {
+    private trackMovement = (panX: number, panY: number, doc: Doc, scale: number = 0) => {
         // ensure we are recording to track
         if (!this.tracking) {
             console.error('[recordingApi.ts] trackMovements(): tracking is false');
@@ -231,7 +231,7 @@ export class TrackMovements {
         // get the time
         const time = new Date().getTime() - this.absoluteStart;
         // make new movement object
-        const movement: Movement = { time, panX, panY, scale, docId };
+        const movement: Movement = { time, panX, panY, scale, doc };
 
         // add that movement to the current presentation data's movement array
         this.currentPresentation.movements && this.currentPresentation.movements.push(movement);
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 2b586b0e2..dee161931 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -109,17 +109,8 @@ export class DashboardView extends React.Component {
                     />
                 </div>
                 <div className="button-bar">
-                    <Button text="Cancel" borderRadius={10} hoverStyle={'gray'} fontSize={FontSize.SECONDARY} onClick={this.abortCreateNewDashboard} />
-                    <Button
-                        text="Create"
-                        borderRadius={10}
-                        backgroundColor={Colors.LIGHT_BLUE}
-                        hoverStyle={'darken'}
-                        fontSize={FontSize.SECONDARY}
-                        onClick={() => {
-                            this.createNewDashboard(this.newDashboardName!, this.newDashboardColor);
-                        }}
-                    />
+                    <Button text="Cancel" onClick={this.abortCreateNewDashboard} />
+                    <Button text="Create" onClick={() => this.createNewDashboard(this.newDashboardName!, this.newDashboardColor)} />
                 </div>
             </div>
         );
@@ -163,7 +154,7 @@ export class DashboardView extends React.Component {
                 <div className="dashboard-view">
                     <div className="left-menu">
                         <div className="new-dashboard-button">
-                            <Button icon={<FaPlus />} hoverStyle="darken" backgroundColor={Colors.LIGHT_BLUE} size={Size.MEDIUM} fontSize={FontSize.HEADER} text="New" onClick={() => this.setNewDashboardName('')} borderRadius={50} />
+                            <Button icon={<FaPlus />} size={Size.MEDIUM} text="New" onClick={() => this.setNewDashboardName('')} />
                         </div>
                         <div className={`text-button ${this.selectedDashboardGroup === DashboardGroup.MyDashboards && 'selected'}`} onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)}>
                             My Dashboards
@@ -196,7 +187,7 @@ export class DashboardView extends React.Component {
                                                 e.stopPropagation();
                                                 this.onContextMenu(dashboard, e);
                                             }}>
-                                            <IconButton isCircle={true} size={Size.SMALL} hoverStyle="gray" icon={<FontAwesomeIcon color="black" size="lg" icon="bars" />} />
+                                            <Button size={Size.SMALL} icon={<FontAwesomeIcon color="black" size="lg" icon="bars" />} />
                                         </div>
                                     </div>
                                 </div>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 9fc1487a0..a59189fd2 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -52,7 +52,6 @@ export function DocComponent<P extends DocComponentProps>() {
 interface ViewBoxBaseProps {
     Document: Doc;
     DataDoc?: Doc;
-    ContainingCollectionDoc: Opt<Doc>;
     DocumentView?: () => DocumentView;
     fieldKey: string;
     isSelected: (outsideReaction?: boolean) => boolean;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9ffbe083f..9a4aaf00e 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -33,6 +33,7 @@ import { ImageBox } from './nodes/ImageBox';
 import React = require('react');
 import { RichTextField } from '../../fields/RichTextField';
 import { LinkFollower } from '../util/LinkFollower';
+import _ = require('lodash');
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
@@ -303,8 +304,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         SelectionManager.DeselectAll();
     };
 
-    onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
-
+    onSelectorClick = () => SelectionManager.Views()?.[0]?.props.docViewPath?.().lastElement()?.select(false);
     /**
      * Handles setting up events when user clicks on the border radius editor
      * @param e PointerEvent
@@ -744,7 +744,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             seldocview.props.hideDeleteButton ||
             seldocview.rootDoc.hideDeleteButton ||
             SelectionManager.Views().some(docView => {
-                const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
+                const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DataSym]) : AclEdit;
                 return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
             });
 
@@ -865,7 +865,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                     <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
                                     <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
 
-                                    {seldocview.props.renderDepth <= 1 || !seldocview.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')}
+                                    {seldocview.props.renderDepth <= 1 || !seldocview.props.docViewPath().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')}
                                 </>
                             )}
                             {useRounding && (
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 0feccb742..13faae783 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1010,14 +1010,12 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                 renderDepth={0}
                 styleProvider={returnEmptyString}
                 docViewPath={returnEmptyDoclist}
-                focus={DocUtils.DefaultFocus}
+                focus={emptyFunction}
                 whenChildContentsActiveChanged={emptyFunction}
                 bringToFront={emptyFunction}
                 docRangeFilters={returnEmptyFilter}
                 docFilters={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionView={undefined}
-                ContainingCollectionDoc={undefined}
             />
         );
     };
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index e6df0801c..156825f41 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -373,10 +373,11 @@ export class InkStrokeProperties {
 
     snapToAllCurves = (screenDragPt: { X: number; Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number; Y: number }; distance: number }, ink: InkData, controlIndex: number) => {
         const containingCollection = inkView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+        const containingDocView = inkView.props.CollectionFreeFormDocumentView?.().props.DocumentView?.();
         containingCollection?.childDocs
             .filter(doc => doc.type === DocumentType.INK)
             .forEach(doc => {
-                const testInkView = DocumentManager.Instance.getDocumentView(doc, containingCollection?.props.CollectionView);
+                const testInkView = DocumentManager.Instance.getDocumentView(doc, containingDocView);
                 const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.rootDoc ? this.excludeSelfSnapSegs(ink, controlIndex) : []);
                 if (snapped && snapped.distance < snapData.distance) {
                     const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt : inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 976c8763e..69eec8456 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -266,8 +266,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                             docFilters={this.docFilters}
                             docRangeFilters={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
-                            ContainingCollectionView={undefined}
-                            ContainingCollectionDoc={undefined}
                             addDocument={undefined}
                             removeDocument={undefined}
                             whenChildContentsActiveChanged={emptyFunction}
@@ -275,7 +273,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                             pinToPres={TabDocView.PinDoc}
                             bringToFront={emptyFunction}
                             onBrowseClick={MainView.Instance.exploreMode}
-                            focus={DocUtils.DefaultFocus}
+                            focus={emptyFunction}
                         />
                     </GestureOverlay>
                 </div>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 60459cf30..0384d925e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -595,14 +595,12 @@ export class MainView extends React.Component {
                     PanelWidth={this.headerBarDocWidth}
                     PanelHeight={this.headerBarDocHeight}
                     renderDepth={0}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                 />
             </div>
         );
@@ -626,14 +624,12 @@ export class MainView extends React.Component {
                     ScreenToLocalTransform={this._hideUI ? this.mainScreenToLocalXf : Transform.Identity}
                     PanelWidth={this.mainDocViewWidth}
                     PanelHeight={this.mainDocViewHeight}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     suppressSetHeight={true}
                     renderDepth={this._hideUI ? 0 : -1}
                 />
@@ -727,14 +723,12 @@ export class MainView extends React.Component {
                         renderDepth={0}
                         isContentActive={returnTrue}
                         scriptContext={CollectionDockingView.Instance?.props.Document}
-                        focus={DocUtils.DefaultFocus}
+                        focus={emptyFunction}
                         whenChildContentsActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
                         docFilters={returnEmptyFilter}
                         docRangeFilters={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
                     />
                 </div>
                 {this.docButtons}
@@ -758,7 +752,7 @@ export class MainView extends React.Component {
                     PanelHeight={this.leftMenuHeight}
                     renderDepth={0}
                     docViewPath={returnEmptyDoclist}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     styleProvider={DefaultStyleProvider}
                     isContentActive={returnTrue}
                     whenChildContentsActiveChanged={emptyFunction}
@@ -766,8 +760,6 @@ export class MainView extends React.Component {
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     scriptContext={this}
                 />
             </div>
@@ -900,13 +892,11 @@ export class MainView extends React.Component {
                     PanelWidth={this.leftMenuFlyoutWidth}
                     PanelHeight={this.leftMenuFlyoutHeight}
                     renderDepth={0}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                 />
                 {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : <></>}
             </div>
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index ec22128d4..bdc48d03a 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable, trace } from 'mobx';
+import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
@@ -10,15 +10,13 @@ import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, retu
 import { DocUtils } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DragManager } from '../util/DragManager';
-import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { Transform } from '../util/Transform';
 import { CollectionFreeFormLinksView } from './collections/collectionFreeForm/CollectionFreeFormLinksView';
 import { LightboxView } from './LightboxView';
 import { MainView } from './MainView';
 import { DocumentView } from './nodes/DocumentView';
 import './OverlayView.scss';
-import { ScriptingRepl } from './ScriptingRepl';
-import { DefaultStyleProvider, testDocProps } from './StyleProvider';
+import { DefaultStyleProvider } from './StyleProvider';
 
 export type OverlayDisposer = () => void;
 
@@ -223,7 +221,7 @@ export class OverlayView extends React.Component {
                             isDocumentActive={returnTrue}
                             isContentActive={returnTrue}
                             whenChildContentsActiveChanged={emptyFunction}
-                            focus={DocUtils.DefaultFocus}
+                            focus={emptyFunction}
                             styleProvider={DefaultStyleProvider}
                             docViewPath={returnEmptyDoclist}
                             addDocTab={d.type === DocumentType.PRES ? MainView.addDocTabFunc : returnFalse}
@@ -231,8 +229,6 @@ export class OverlayView extends React.Component {
                             docFilters={returnEmptyFilter}
                             docRangeFilters={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
-                            ContainingCollectionView={undefined}
-                            ContainingCollectionDoc={undefined}
                         />
                     </div>
                 );
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 954529bc9..3ad28c418 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -1,12 +1,12 @@
-import { IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Doc } from "../../fields/Doc";
-import { NumCast } from "../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, emptyPath } from "../../Utils";
-import { Transform } from "../util/Transform";
-import { DocumentView } from "./nodes/DocumentView";
-import "./Palette.scss";
+import { IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc } from '../../fields/Doc';
+import { NumCast } from '../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, emptyPath } from '../../Utils';
+import { Transform } from '../util/Transform';
+import { DocumentView } from './nodes/DocumentView';
+import './Palette.scss';
 
 export interface PaletteProps {
     x: number;
@@ -23,20 +23,20 @@ export default class Palette extends React.Component<PaletteProps> {
     componentDidMount = () => {
         this._selectedDisposer = reaction(
             () => NumCast(this.props.thumbDoc.selectedIndex),
-            (i) => this._selectedIndex = i,
+            i => (this._selectedIndex = i),
             { fireImmediately: true }
         );
-    }
+    };
 
     componentWillUnmount = () => {
         this._selectedDisposer?.();
-    }
+    };
 
     render() {
         return (
             <div className="palette-container" style={{ transform: `translate(${this.props.x}px, ${this.props.y}px)` }}>
                 <div className="palette-thumb" style={{ transform: `translate(${this.props.thumb[0] - this.props.x}px, ${this.props.thumb[1] - this.props.y}px)` }}>
-                    <div className="palette-thumbContent" style={{ transform: `translate(-${(this._selectedIndex * 50) + 10}px, 0px)` }}>
+                    <div className="palette-thumbContent" style={{ transform: `translate(-${this._selectedIndex * 50 + 10}px, 0px)` }}>
                         <DocumentView
                             Document={this.props.thumbDoc}
                             DataDoc={undefined}
@@ -59,12 +59,11 @@ export default class Palette extends React.Component<PaletteProps> {
                             docFilters={returnEmptyFilter}
                             docRangeFilters={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
-                            ContainingCollectionView={undefined}
-                            ContainingCollectionDoc={undefined} />
+                        />
                         <div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div>
                     </div>
                 </div>
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 2c7da5931..93eec61f3 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -22,9 +22,9 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
     @computed get _docs() {
         if (!this.props.DocView) return [];
         const target = this.props.DocView.props.Document;
-        const targetContext = this.props.DocView.props.ContainingCollectionDoc;
+        const targetContext = this.props.DocView.props.docViewPath().lastElement()?.rootDoc;
         const aliases = DocListCast(target.aliases);
-        const containerProtos = aliases.filter(alias => alias.context && alias.context instanceof Doc && Cast(alias.context, Doc, null) !== targetContext).reduce((set, alias) => set.add(Cast(alias.context, Doc, null)), new Set<Doc>());
+        const containerProtos = aliases.filter(alias => alias.context && alias.context instanceof Doc).reduce((set, alias) => set.add(Cast(alias.context, Doc, null)), new Set<Doc>());
         const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.aliases));
         const containers = containerSets.reduce((p, set) => {
             set.map(s => p.add(s));
@@ -42,6 +42,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
         return doclayouts
             .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document))
             .filter(doc => !Doc.IsSystem(doc))
+            .filter(doc => doc !== targetContext)
             .map(doc => ({ col: doc, target }));
     }
 
@@ -53,8 +54,21 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
             col._panY = NumCast(target.y) + NumCast(target._height) / 2;
         }
         col.hidden = false;
-        this.props.addDocTab(col, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere);
-        setTimeout(() => DocFocusOrOpen(Doc.GetProto(this.props.DocView!.props.Document), col), 100);
+        //this.props.addDocTab(col, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere);
+        setTimeout(
+            () =>
+                this.props.DocView &&
+                DocFocusOrOpen(
+                    Doc.GetProto(this.props.DocView.props.Document),
+                    {
+                        //
+                        willZoomCentered: true,
+                        openLocation: (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere,
+                    },
+                    col
+                ),
+            100
+        );
     };
 
     render() {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index fbc7d7696..6582c3f2a 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -341,8 +341,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         docFilters={returnEmptyFilter}
                         docRangeFilters={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
-                        ContainingCollectionDoc={undefined}
-                        ContainingCollectionView={undefined}
                         addDocument={returnFalse}
                         moveDocument={undefined}
                         removeDocument={returnFalse}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index b950b4860..739d6d819 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -18,7 +18,7 @@ import { TreeSort } from './collections/TreeView';
 import { Colors } from './global/globalEnums';
 import { InkingStroke } from './InkingStroke';
 import { MainView } from './MainView';
-import { DocumentViewProps } from './nodes/DocumentView';
+import { DocumentViewProps, OpenWhere } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { SliderBox } from './nodes/SliderBox';
@@ -295,7 +295,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 default:
                     return doc.z
                         ? `#9c9396  ${StrCast(doc?.boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
-                        : props?.ContainingCollectionDoc?._useClusters
+                        : props?.docViewPath().lastElement()?.rootDoc._useClusters
                         ? `${backgroundCol()} ${StrCast(doc.boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : NumCast(doc.group, -1) !== -1
                         ? `gray ${StrCast(doc.boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
@@ -316,7 +316,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations:
             const lock = () => {
-                if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform) {
+                if (props?.docViewPath().lastElement()?.rootDoc?._viewType === CollectionViewType.Freeform) {
                     return doc?.pointerEvents !== 'none' ? null : (
                         <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
                             <FontAwesomeIcon icon={'lock'} style={{ color: 'red' }} size="lg" />
@@ -383,10 +383,7 @@ export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps
         return doc._viewType === CollectionViewType.Docking ? null : (
             <>
                 {DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => {
-                    doc.hidden = doc.hidden ? undefined : true;
-                    if (!doc.hidden) {
-                        DocFocusOrOpen(doc, props?.ContainingCollectionDoc);
-                    }
+                    DocFocusOrOpen(doc, { toggleTarget: true, willPan: true, openLocation: OpenWhere.addRight }, props?.docViewPath().lastElement()?.rootDoc);
                 })}
             </>
         );
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 45db240a9..c5a501aa6 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -114,8 +114,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 <CollectionTreeView
                     Document={Doc.MyTemplates}
                     CollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
-                    ContainingCollectionView={undefined}
                     styleProvider={DefaultStyleProvider}
                     setHeight={returnFalse}
                     docViewPath={returnEmptyDoclist}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index c0220f804..0e4556eb4 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -62,7 +62,6 @@ export class CollectionCarouselView extends CollectionSubView() {
                         onClick={this.onContentClick}
                         hideCaptions={showCaptions ? true : false}
                         renderDepth={this.props.renderDepth + 1}
-                        ContainingCollectionView={this.props.CollectionView}
                         LayoutTemplate={this.props.childLayoutTemplate}
                         LayoutTemplateString={this.props.childLayoutString}
                         Document={curDoc.layout}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 225743748..2007f73aa 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -134,8 +134,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                 />
             </div>
         );
@@ -642,8 +640,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
                         key="float"
                         style={{
                             backgroundColor: this.props.docView.layoutDoc.z ? '121212' : undefined,
-                            pointerEvents: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? 'none' : undefined,
-                            color: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? 'dimgrey' : undefined,
+                            pointerEvents: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._viewType !== CollectionViewType.Freeform ? 'none' : undefined,
+                            color: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._viewType !== CollectionViewType.Freeform ? 'dimgrey' : undefined,
                         }}
                         onClick={undoBatch(() => this.props.docView.props.CollectionFreeFormDocumentView?.().float())}>
                         <FontAwesomeIcon icon={['fab', 'buffer']} size={'lg'} />
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index aec0734b4..80e81bc1c 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -255,8 +255,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 hideTitle={this.props.childHideTitle?.()}
                 docRangeFilters={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                ContainingCollectionDoc={this.props.CollectionView?.props.Document}
-                ContainingCollectionView={this.props.CollectionView}
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
                 removeDocument={this.props.removeDocument}
@@ -632,8 +630,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                         docFilters={this.props.docFilters}
                         docRangeFilters={this.props.docRangeFilters}
                         searchFilterDocs={this.props.searchFilterDocs}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
                     />
                 </div>
             );
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index ba90ed8cd..fd9b0c0ce 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -30,7 +30,7 @@ export class CollectionPileView extends CollectionSubView() {
         // pileups are designed to go away when they are empty.
         this._disposers.selected = reaction(
             () => this.childDocs.length,
-            num => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document)
+            num => !num && this.props.CollectionFreeFormDocumentView?.().props.removeDocument?.(this.props.Document)
         );
     }
     componentWillUnmount() {
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index bbd81d06d..22a575989 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -815,8 +815,6 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                     whenChildContentsActiveChanged={emptyFunction}
                     focus={focusFunc}
                     isContentActive={returnFalse}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     searchFilterDocs={returnEmptyDoclist}
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index a85ee0e02..67f5dc9f4 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -346,8 +346,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 hideTitle={this.props.childHideTitle?.()}
                 docRangeFilters={this.childDocRangeFilters}
                 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}
@@ -678,8 +676,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         docFilters={this.props.docFilters}
                         docRangeFilters={this.props.docRangeFilters}
                         searchFilterDocs={this.props.searchFilterDocs}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
                     />
                 </div>
             );
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 4a11e8f0b..92932fb61 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -164,7 +164,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             this.props.removeDocument?.(doc);
             if (ind > 0) {
                 FormattedTextBox.SelectOnLoad = prev[Id];
-                DocumentManager.Instance.getDocumentView(prev, this.props.CollectionView)?.select(false);
+                DocumentManager.Instance.getDocumentView(prev, this.props.DocumentView?.())?.select(false);
             }
             return true;
         }
@@ -242,8 +242,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 docFilters={returnEmptyFilter}
                 docRangeFilters={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionDoc={this.doc}
-                ContainingCollectionView={this.props.CollectionView}
                 addDocument={returnFalse}
                 moveDocument={returnFalse}
                 removeDocument={returnFalse}
@@ -272,7 +270,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             this,
             this.doc,
             this.props.DataDoc,
-            this.props.ContainingCollectionDoc,
+            undefined,
             undefined,
             addDoc,
             this.remove,
@@ -331,10 +329,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                     isDocumentActive={returnTrue}
                     addDocument={this.props.addDocument}
                     moveDocument={this.props.moveDocument}
+                    removeDocument={this.props.removeDocument}
                     addDocTab={this.props.addDocTab}
                     pinToPres={emptyFunction}
                     rootSelected={this.props.isSelected}
-                    removeDocument={this.props.removeDocument}
                     ScreenToLocalTransform={Transform.Identity}
                     PanelWidth={this.return35}
                     PanelHeight={this.return35}
@@ -347,8 +345,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                     docFilters={this.props.docFilters}
                     docRangeFilters={this.props.docRangeFilters}
                     searchFilterDocs={this.props.searchFilterDocs}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                 />
             </div>
         );
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index bc25ad43a..53fbcc3cc 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -279,7 +279,10 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             CollectionView: this,
         };
         return (
-            <div className={'collectionView'} onContextMenu={this.onContextMenu} style={{ pointerEvents: this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? 'none' : undefined }}>
+            <div
+                className="collectionView"
+                onContextMenu={this.onContextMenu}
+                style={{ pointerEvents: this.props.DocumentView?.()?.props.docViewPath().lastElement()?.rootDoc?._viewType === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? 'none' : undefined }}>
                 {this.showIsTagged()}
                 {this.renderSubView(this.collectionViewType, props)}
             </div>
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 458712999..ef8f395c5 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -425,8 +425,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     hideTitle={this.props.keyValue}
                     Document={this._document}
                     DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     onBrowseClick={MainView.Instance.exploreMode}
                     waitForDoubleClickToClick={MainView.Instance.waitForDoubleClick}
                     isContentActive={returnTrue}
@@ -580,8 +578,6 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                 <CollectionFreeFormView
                     Document={this.props.document}
                     CollectionView={undefined}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     docViewPath={returnEmptyDoclist}
                     childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
                     noOverlay={true} // don't render overlay Docs since they won't scale
@@ -603,7 +599,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                     ScreenToLocalTransform={Transform.Identity}
                     renderDepth={0}
                     whenChildContentsActiveChanged={emptyFunction}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     styleProvider={TabMinimapView.miniStyleProvider}
                     addDocTab={this.props.addDocTab}
                     pinToPres={TabDocView.PinDoc}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 75e76019e..8b1dfc767 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -42,7 +42,7 @@ export interface TreeViewProps {
     prevSibling?: Doc;
     document: Doc;
     dataDoc?: Doc;
-    containerCollection: Doc;
+    treeViewParent: Doc;
     renderDepth: number;
     dropAction: dropActionType;
     addDocTab: (doc: Doc, where: OpenWhere) => boolean;
@@ -143,7 +143,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView;
     }
     @computed get MAX_EMBED_HEIGHT() {
-        return NumCast(this.props.containerCollection.maxEmbedHeight, 200);
+        return NumCast(this.props.treeViewParent.maxEmbedHeight, 200);
     }
     @computed get dataDoc() {
         return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym];
@@ -194,7 +194,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         const ind = this.dataDoc[key].indexOf(doc);
 
         const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
-        res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.CollectionView)?.select(false);
+        res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.DocumentView?.())?.select(false);
         return res;
     };
 
@@ -385,7 +385,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         };
         const addDoc = inside ? localAdd : parentAddDoc;
         const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument;
-        const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes('add')) || forceAdd;
+        const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.freezeChildren).includes('add')) || forceAdd;
         if (canAdd) {
             this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true);
             const res = UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false));
@@ -406,7 +406,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     getTransform = () => this.refTransform(this._tref.current);
     embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1);
     embeddedPanelHeight = () => {
-        const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+        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,
@@ -416,7 +416,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 return layoutDoc._fitWidth
                     ? !Doc.NativeHeight(layoutDoc)
                         ? NumCast(layoutDoc._height)
-                        : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)))
+                        : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height)))
                     : (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym]();
             })()
         );
@@ -452,7 +452,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     this,
                     doc,
                     undefined,
-                    this.props.containerCollection,
+                    this.props.treeViewParent,
                     this.props.prevSibling,
                     addDoc,
                     remDoc,
@@ -596,7 +596,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                                   this,
                                   this.layoutDoc,
                                   this.dataDoc,
-                                  this.props.containerCollection,
+                                  this.props.treeViewParent,
                                   this.props.prevSibling,
                                   addDoc,
                                   remDoc,
@@ -656,7 +656,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             this.onCheckedClick?.script.run(
                 {
                     this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
-                    heading: this.props.containerCollection.title,
+                    heading: this.props.treeViewParent.title,
                     checked: this.doc.treeViewChecked === 'check' ? 'x' : this.doc.treeViewChecked === 'x' ? 'remove' : 'check',
                     containingTreeView: this.props.treeView.props.Document,
                 },
@@ -724,9 +724,11 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
 
     @observable headerEleWidth = 0;
-    @computed get headerElements() {
+    @computed get titleButtons() {
+        const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations);
         return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : (
             <>
+                {customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */}
                 {this.doc.hideContextMenu ? null : (
                     <FontAwesomeIcon
                         title="context menu"
@@ -779,9 +781,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
     };
 
-    onChildClick = () => {
-        return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!);
-    };
+    onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!);
 
     onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null);
 
@@ -906,7 +906,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 styleProvider={this.titleStyleProvider}
                 enableDragWhenActive={true}
                 onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
-                docViewPath={returnEmptyDoclist}
+                docViewPath={this.props.treeView.props.docViewPath}
                 treeViewDoc={this.props.treeView.props.Document}
                 addDocument={undefined}
                 addDocTab={this.props.addDocTab}
@@ -937,12 +937,8 @@ export class TreeView extends React.Component<TreeViewProps> {
                 docFilters={returnEmptyFilter}
                 docRangeFilters={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionView={this.props.treeView.props.CollectionView}
-                ContainingCollectionDoc={this.props.treeView.props.Document}
             />
         );
-
-        const buttons = this.props.styleProvider?.(this.doc, { ...this.props.treeView.props, ContainingCollectionDoc: this.props.parentTreeView?.doc }, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ':afterHeader' : ''));
         return (
             <>
                 <div
@@ -958,8 +954,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     {view}
                 </div>
                 <div className="treeView-rightButtons" ref={action((r: any) => r && (this.headerEleWidth = r.getBoundingClientRect().width))}>
-                    {buttons} {/* hide and lock buttons */}
-                    {this.headerElements}
+                    {this.titleButtons}
                 </div>
             </>
         );
@@ -1017,8 +1012,6 @@ export class TreeView extends React.Component<TreeViewProps> {
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionDoc={this.props.containerCollection}
-                    ContainingCollectionView={undefined}
                     addDocument={this.props.addDocument}
                     moveDocument={this.move}
                     removeDocument={this.props.removeDoc}
@@ -1131,7 +1124,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         childDocs: Doc[],
         treeView: CollectionTreeView,
         parentTreeView: CollectionTreeView | TreeView | undefined,
-        containerCollection: Doc,
+        treeViewParent: Doc,
         dataDoc: Doc | undefined,
         parentCollectionDoc: Doc | undefined,
         containerPrevSibling: Doc | undefined,
@@ -1162,19 +1155,19 @@ export class TreeView extends React.Component<TreeViewProps> {
         hierarchyIndex?: number[],
         renderCount?: number
     ) {
-        const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField);
+        const viewSpecScript = Cast(treeViewParent.viewSpecScript, ScriptField);
         if (viewSpecScript) {
             childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
         }
 
-        const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None));
+        const docs = TreeView.sortDocs(childDocs, StrCast(treeViewParent.treeViewSortCriterion, TreeSort.None));
         const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1);
         const treeViewRefs = new Map<Doc, TreeView | undefined>();
         return docs
             .filter(child => child instanceof Doc)
             .map((child, i) => {
                 if (renderCount && i > renderCount) return null;
-                const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child);
+                const pair = Doc.GetLayoutDataDocPair(treeViewParent, dataDoc, child);
                 if (!pair.layout || pair.data instanceof Promise) {
                     return null;
                 }
@@ -1192,10 +1185,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     }
                 };
                 const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
-                const outdent =
-                    parentCollectionDoc?._viewType !== CollectionViewType.Tree
-                        ? undefined
-                        : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
+                const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
                 const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
                 const childLayout = Doc.Layout(pair.layout);
                 const rowHeight = () => {
@@ -1208,7 +1198,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         ref={r => treeViewRefs.set(child, r ? r : undefined)}
                         document={pair.layout}
                         dataDoc={pair.data}
-                        containerCollection={containerCollection}
+                        treeViewParent={treeViewParent}
                         prevSibling={docs[i]}
                         // TODO: [AL] add these
                         hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined}
@@ -1220,7 +1210,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         onCheckedClick={onCheckedClick}
                         onChildClick={onChildClick}
                         renderDepth={renderDepth}
-                        removeDoc={StrCast(containerCollection.freezeChildren).includes('remove') ? undefined : remove}
+                        removeDoc={StrCast(treeViewParent.freezeChildren).includes('remove') ? undefined : remove}
                         addDocument={addDocument}
                         styleProvider={styleProvider}
                         panelWidth={rowWidth}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7ae7be3c8..a3f5e73fb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -262,7 +262,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
-        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true));
+        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).map(dv => dv && SelectionManager.SelectView(dv, true));
     };
     addDocument = (newBox: Doc | Doc[]) => {
         let retVal = false;
@@ -319,7 +319,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
         if (didMove) {
             const focusTime = options?.instant ? 0 : options.zoomTime ?? 500;
-            options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale);
+            (options.zoomScale || options.willZoomCentered) && scale && (this.Document[this.scaleFieldKey] = scale);
             this.setPan(panX, panY, focusTime, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
             return focusTime;
         }
@@ -378,19 +378,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 1
             );
             if (sorted.length && refDoc && NumCast(sorted[0].layout.y) < NumCast(refDoc.y)) {
-                const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height);
+                const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height) / 2;
                 const deltay = sorted.length > 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0;
                 const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0;
 
                 let lastx = NumCast(refDoc.x);
                 let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height));
-                setTimeout(
-                    action(() =>
-                        sorted.slice(1).forEach((pair, i) => {
-                            lastx = pair.layout.x = lastx + deltax;
-                            lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height));
-                        })
-                    )
+                runInAction(() =>
+                    sorted.slice(1).forEach((pair, i) => {
+                        lastx = pair.layout.x = lastx + deltax;
+                        lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height));
+                    })
                 );
             }
         }
@@ -464,7 +462,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const ptsParent = e instanceof PointerEvent ? e : e.targetTouches.item(0);
             if (ptsParent) {
                 const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster);
-                const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!);
+                const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.DocumentView?.())!);
                 const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
                 const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'alias' : undefined);
                 de.moveDocument = this.props.moveDocument;
@@ -872,7 +870,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
 
         return this.childDocs
-            .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView))
+            .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.()))
             .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
             .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
             .filter(
@@ -966,7 +964,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.childDocs
             .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
             .forEach(doc => {
-                const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)?.ComponentView as InkingStroke;
+                const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())?.ComponentView as InkingStroke;
                 const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
                 const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
                 const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt));
@@ -1121,7 +1119,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     nudge = (x: number, y: number, nudgeTime: number = 500) => {
-        if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) {
+        const collectionDoc = this.props.docViewPath().lastElement().rootDoc;
+        if (collectionDoc?._viewType !== CollectionViewType.Freeform || collectionDoc._panX !== undefined) {
             this.setPan(
                 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(),
@@ -1256,8 +1255,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 replica={entry.replica}
                 suppressSetHeight={this.layoutEngine ? true : false}
                 renderCutoffProvider={this.renderCutoffProvider}
-                ContainingCollectionView={this.props.CollectionView}
-                ContainingCollectionDoc={this.props.Document}
                 CollectionFreeFormView={this}
                 LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
                 LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
@@ -1779,7 +1776,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const lines = text.split('\n');
                 const height = 30 + 15 * lines.length;
 
-                this.props.ContainingCollectionView?.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
+                this.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
             }
         }
     };
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index c7d9b6619..97eed7752 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -209,8 +209,6 @@ export class CollectionLinearView extends CollectionSubView() {
                     docFilters={this.props.docFilters}
                     docRangeFilters={this.props.docRangeFilters}
                     searchFilterDocs={this.props.searchFilterDocs}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     hideResizeHandles={true}
                 />
             </div>
@@ -251,8 +249,7 @@ export class CollectionLinearView extends CollectionSubView() {
                                 self: this.rootDoc,
                                 _readOnly_: false,
                                 scriptContext: this.props.scriptContext,
-                                thisContainer: this.props.ContainingCollectionDoc,
-                                documentView: this.props.docViewPath().lastElement(),
+                                documentView: this.props.DocumentView?.(),
                             });
                             this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked;
                         })}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index b73b1d779..78d3d1b6e 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -262,8 +262,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 docFilters={this.childDocFilters}
                 docRangeFilters={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                ContainingCollectionDoc={this.props.CollectionView?.props.Document}
-                ContainingCollectionView={this.props.CollectionView}
                 dontRegisterView={this.props.dontRegisterView}
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 0cca83803..4d61dc272 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -261,8 +261,6 @@ export class CollectionMultirowView extends CollectionSubView() {
                 docFilters={this.childDocFilters}
                 docRangeFilters={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                ContainingCollectionDoc={this.props.CollectionView?.props.Document}
-                ContainingCollectionView={this.props.CollectionView}
                 dontRegisterView={this.props.dontRegisterView}
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index fd9bcf681..6d5a73e55 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -793,7 +793,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 fitContentsToBox={returnTrue}
                                 dontCenter={'y'}
                                 onClickScriptDisable="always"
-                                focus={DocUtils.DefaultFocus}
+                                focus={emptyFunction}
                                 renderDepth={this.props.renderDepth + 1}
                                 rootSelected={this.rootSelected}
                                 PanelWidth={this.previewWidthFunc}
@@ -806,8 +806,6 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 searchFilterDocs={this.searchFilterDocs}
                                 styleProvider={DefaultStyleProvider}
                                 docViewPath={returnEmptyDoclist}
-                                ContainingCollectionDoc={this.props.CollectionView?.props.Document}
-                                ContainingCollectionView={this.props.CollectionView}
                                 moveDocument={this.props.moveDocument}
                                 addDocument={this.addRow}
                                 removeDocument={this.props.removeDocument}
@@ -849,8 +847,6 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
                                 Document={doc}
                                 DataDoc={dataDoc}
                                 renderDepth={this.props.schema.props.renderDepth + 1}
-                                ContainingCollectionView={this.props.schema.props.CollectionView}
-                                ContainingCollectionDoc={this.props.schema.Document}
                                 PanelWidth={this.tableWidthFunc}
                                 PanelHeight={this.rowHeightFunc}
                                 styleProvider={DefaultStyleProvider}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 2defaae00..856537927 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -29,7 +29,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get schemaDoc() {
-        return this.props.ContainingCollectionDoc!;
+        return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
     }
 
     @computed get rowIndex() {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 13e45963e..5f8ffe8b0 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -28,8 +28,6 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
             docViewPath: returnEmptyDoclist,
-            ContainingCollectionView: undefined,
-            ContainingCollectionDoc: undefined,
             fieldKey: this.props.fieldKey,
             rootSelected: returnFalse,
             isSelected: returnFalse,
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 29e7cd3ad..d703c9595 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -44,25 +44,10 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
         }
 
         const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
-        dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
-            docView.props.removeDocument?.(doc);
-            addDocument(doc);
-            return true;
-        };
-        const containingView = docView.props.ContainingCollectionView;
-        const finishDrag = (e: DragManager.DragCompleteEvent) =>
-            e.docDragData &&
-            (e.docDragData.droppedDocuments = dragData.draggedDocuments.reduce((droppedDocs, d) => {
-                const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView);
-                if (dvs.length) {
-                    dvs.forEach(dv => droppedDocs.push(dv.props.Document));
-                } else {
-                    droppedDocs.push(Doc.MakeAlias(d));
-                }
-                return droppedDocs;
-            }, [] as Doc[]));
+        dragData.canEmbed = true;
+        dragData.moveDocument = (docView.props.docViewPath().lastElement()?.ComponentView as any)?.props.CollectionView?.moveDocument; // this is equal to docView.props.moveDocument, but moveDocument is not a defined prop of a DocumentView .. but maybe it should be?
 
-        DragManager.StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag);
+        DragManager.StartDocumentDrag([dragEle], dragData, downX, downY, undefined);
     }
 }
 
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 7bdace2b6..5f2d4a7b6 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -85,15 +85,13 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
                         PanelWidth={this.getPWidth}
                         PanelHeight={this.getPHeight}
                         renderDepth={0}
-                        focus={DocUtils.DefaultFocus}
+                        focus={emptyFunction}
                         docViewPath={returnEmptyDoclist}
                         whenChildContentsActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
                         docFilters={returnEmptyFilter}
                         docRangeFilters={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
                     />
                 </div>
             </div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 9bdb2cee7..24b9f3b25 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -160,8 +160,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     @action public float = () => {
-        const { Document: topDoc, ContainingCollectionView: container } = this.props;
-        const screenXf = container?.screenToLocalTransform();
+        const topDoc = this.rootDoc;
+        const containerDocView = this.props.docViewPath().lastElement();
+        const screenXf = containerDocView?.screenToLocalTransform();
         if (screenXf) {
             SelectionManager.DeselectAll();
             if (topDoc.z) {
@@ -178,7 +179,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 topDoc.x = fpt[0];
                 topDoc.y = fpt[1];
             }
-            setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, container)!, false), 0);
+            setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, containerDocView), false), 0);
         }
     };
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index dbcfe43cf..76a5ce7b3 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -154,7 +154,6 @@ export class DocumentContentsView extends React.Component<
             // these are the properties in  DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
             'hideResizeHandles',
             'hideTitle',
-            'treeViewDoc',
             'contentPointerEvents',
             'radialMenu',
             'LayoutTemplateString',
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index df3299eef..47705d53d 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -142,7 +142,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             } else if (startLink !== endLink) {
                 endLink = endLinkView?.docView?._componentView?.getAnchor?.(true, pinProps) || endLink;
                 startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink;
-                const linkDoc = DocUtils.MakeLink(startLink, endLink, { linkRelationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined }, undefined, true);
+                const linkDoc = DocUtils.MakeLink(startLink, endLink, { linkRelationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined });
 
                 LinkManager.currentLink = linkDoc;
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f161a7b9b..be07f1fe5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -148,8 +148,6 @@ export interface DocumentViewSharedProps {
     DataDoc?: Doc;
     contentBounds?: () => undefined | { x: number; y: number; r: number; b: number };
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitContentsToBox property on a Document
-    ContainingCollectionView: Opt<CollectionView>;
-    ContainingCollectionDoc: Opt<Doc>;
     suppressSetHeight?: boolean;
     thumbShown?: () => boolean;
     setContentView?: (view: DocComponentView) => any;
@@ -179,6 +177,7 @@ export interface DocumentViewSharedProps {
     ScreenToLocalTransform: () => Transform;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
     canEmbedOnDrag?: boolean;
+    treeViewDoc?: Doc;
     xPadding?: number;
     yPadding?: number;
     dropAction?: dropActionType;
@@ -210,7 +209,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     hideOpenButton?: boolean;
     hideDeleteButton?: boolean;
     hideLinkAnchors?: boolean;
-    treeViewDoc?: Doc;
     isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
     isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
     contentPointerEvents?: string; // pointer events allowed for content of a document view.  eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
@@ -429,7 +427,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             this: this.layoutDoc,
                             self: this.rootDoc,
                             scriptContext: this.props.scriptContext,
-                            thisContainer: this.props.ContainingCollectionDoc,
                             documentView: this.props.DocumentView(),
                             clientX, clientY,  altKey,  shiftKey,  ctrlKey,
                             value: undefined,
@@ -456,7 +453,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                                 self: this.rootDoc,
                                 _readOnly_: false,
                                 scriptContext: this.props.scriptContext,
-                                thisContainer: this.props.ContainingCollectionDoc,
                                 documentView: this.props.DocumentView(),
                                 clientX,
                                 clientY,
@@ -605,7 +601,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 }
                 if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) {
                     const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.rootDoc;
-                    de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, undefined, [de.x, de.y - 50]);
+                    de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
                 }
             }
         }
@@ -697,7 +693,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
             !appearance && appearanceItems.length && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
 
-            if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
+            if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.docViewPath().lastElement()?.rootDoc?._viewType !== CollectionViewType.Tree) {
                 const existingOnClick = cm.findByDescription('OnClick...');
                 const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
 
@@ -1446,7 +1442,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     scaleToScreenSpace = () => (1 / (this.props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale;
     docViewPathFunc = () => this.docViewPath;
     isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
-    select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection);
+    select = (extendSelection: boolean) => SelectionManager.SelectView(this, extendSelection);
     NativeWidth = () => this.effectiveNativeWidth;
     NativeHeight = () => this.effectiveNativeHeight;
     PanelWidth = () => this.panelWidth;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 8d3534a5c..86779e0dd 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -32,6 +32,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
     // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields
     // Also, see InkingStroke for examples of creating text boxes from render() methods which set some of these fields
     backgroundColor?: string;
+    treeViewDoc?: Doc;
     color?: string;
     fontSize?: number;
     height?: number;
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 94434dce7..7ea6d42ff 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -61,8 +61,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
             docViewPath: returnEmptyDoclist,
-            ContainingCollectionView: undefined,
-            ContainingCollectionDoc: undefined,
             fieldKey: this.props.keyName,
             rootSelected: returnFalse,
             isSelected: returnFalse,
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index fcc5b6975..c58b5dd8c 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -271,14 +271,12 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                             docFilters={returnEmptyFilter}
                             docRangeFilters={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
-                            ContainingCollectionDoc={undefined}
-                            ContainingCollectionView={undefined}
                             renderDepth={0}
                             suppressSetHeight={true}
                             PanelWidth={this.width}
                             PanelHeight={this.height}
                             pointerEvents={returnNone}
-                            focus={DocUtils.DefaultFocus}
+                            focus={emptyFunction}
                             whenChildContentsActiveChanged={returnFalse}
                             ignoreAutoHeight={true} // need to ignore autoHeight otherwise autoHeight text boxes will expand beyond the preview panel size.
                             bringToFront={returnFalse}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index db11a7776..e015024fd 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -305,8 +305,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                             addDocument={returnFalse}
                             CollectionView={undefined}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform}
-                            renderDepth={this.props.renderDepth + 1}
-                            ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
+                            renderDepth={this.props.renderDepth + 1}>
                             <>
                                 {this.threed}
                                 {this.content}
@@ -330,7 +329,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                                 moveDocument={returnFalse}
                                 addDocument={returnFalse}
                                 renderDepth={this.props.renderDepth + 1}
-                                ContainingCollectionDoc={this.props.ContainingCollectionDoc}></FormattedTextBox>
+                            />
                         )}
                     </div>
                 </div>
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 8eacfbc51..1a75a7e76 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -568,12 +568,12 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
     if (selectedViews.length) {
         if (checkResult) {
             const selView = selectedViews.lastElement();
-            const layoutFrameNumber = Cast(selView.props.ContainingCollectionDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+            const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber).backgroundColor ?? 'transparent';
         }
         selectedViews.forEach(dv => {
-            const layoutFrameNumber = Cast(dv.props.ContainingCollectionDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+            const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             if (contentFrameNumber !== undefined) {
                 CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { backgroundColor: color });
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index c00ab6a7e..b31fc01ff 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -9,10 +9,9 @@ import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { ColorScheme } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
-import { DocumentView } from '../DocumentView';
+import { DocFocusOptions, DocumentView } from '../DocumentView';
 import { FormattedTextBox } from './FormattedTextBox';
 import React = require('react');
-import { SelectionManager } from '../../../util/SelectionManager';
 
 export class DashDocView {
     dom: HTMLSpanElement; // container for label and value
@@ -151,7 +150,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
         const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
         return new Transform(-translateX, -translateY, 1).scale(1 / scale);
     };
-    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document, {}); // ideally, this would scroll to show the focus target
+    outerFocus = (target: Doc, options: DocFocusOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
 
     onKeyDown = (e: any) => {
         e.stopPropagation();
@@ -212,8 +211,6 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
                     docFilters={this.props.tbox?.props.docFilters}
                     docRangeFilters={this.props.tbox?.props.docRangeFilters}
                     searchFilterDocs={this.props.tbox?.props.searchFilterDocs}
-                    ContainingCollectionView={this._textBox.props.ContainingCollectionView}
-                    ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
                 />
             </div>
         );
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index f23426bb3..c43206629 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -235,10 +235,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     };
 
     createPivotForField = (e: React.MouseEvent) => {
-        let container = this.props.tbox.props.ContainingCollectionView;
-        while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
-            container = container.props.ContainingCollectionView;
-        }
+        let container = this.props.tbox.props.DocumentView?.().props.docViewPath().lastElement();
         if (container) {
             const alias = Doc.MakeAlias(container.props.Document);
             alias._viewType = CollectionViewType.Time;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 677c4662b..bc2a5d797 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -541,7 +541,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 // embed document when dragg marked as embed
             } else if (de.embedKey) {
                 const target = dragData.droppedDocuments[0];
-                target._fitContentsToBox = true;
                 const node = schema.nodes.dashDoc.create({
                     width: target[WidthSym](),
                     height: target[HeightSym](),
@@ -552,6 +551,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 if (!['alias', 'copy'].includes((dragData.dropAction ?? '') as any)) {
                     dragData.removeDocument?.(dragData.draggedDocuments[0]);
                 }
+                target._fitContentsToBox = true;
+                target.context = this.rootDoc;
                 const view = this._editorView!;
                 view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
                 e.stopPropagation();
@@ -880,7 +881,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         if (this._editorView && this._recordingStart) {
             if (this._break) {
                 const textanchorFunc = () => {
-                    const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
+                    const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor', unrendered: true });
                     return this.addDocument(tanch) ? tanch : undefined;
                 };
                 const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
@@ -956,7 +957,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             let hadStart = start !== 0;
             frag.forEach((node, index) => {
                 const examinedNode = findAnchorNode(node, editor);
-                if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) {
+                if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) {
                     nodes.push(examinedNode.node);
                     !hadStart && (start = index + examinedNode.start);
                     hadStart = true;
@@ -971,9 +972,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 }
                 return undefined;
             }
+            if (node.type === this._editorView?.state.schema.nodes.dashDoc) {
+                if (node.attrs.docId === textAnchorId) {
+                    return { node, start: 0 };
+                }
+                return undefined;
+            }
             if (!node.isText) {
                 const content = findAnchorFrag(node.content, editor);
-                return { node: node.copy(content.frag), start: content.start };
+                if (content.frag.childCount) return { node: content.frag.childCount ? content.frag.child(0) : node, start: content.start };
             }
             const marks = [...node.marks];
             const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor);
@@ -987,7 +994,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             const ret = findAnchorFrag(editor.state.doc.content, editor);
 
             const content = (ret.frag as any)?.content;
-            if ((ret.frag.size > 2 || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) {
+            if ((ret.frag.size || (content?.length && content[0].type === this._editorView.state.schema.nodes.dashDoc) || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) {
                 !options.instant && (this._focusSpeed = focusSpeed);
                 let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
                 if (ret.frag.firstChild) {
@@ -998,6 +1005,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' });
                 setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed);
                 setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
+                return focusSpeed;
+            } else {
+                return this.props.focus(this.rootDoc, options);
             }
         }
     };
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 3589a9065..807a19771 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -2457,7 +2457,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         {mode !== CollectionViewType.Invalid ? (
                             <CollectionView
                                 {...this.props}
-                                ContainingCollectionDoc={this.props.Document}
                                 PanelWidth={this.props.PanelWidth}
                                 PanelHeight={this.panelHeight}
                                 childIgnoreNativeSize={true}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 92696240b..502eef373 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -39,7 +39,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // Idea: this boolean will determine whether to automatically show the video when this preselement is selected.
     // @observable static showVideo: boolean = false;
     @computed get indexInPres() {
-        return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc);
+        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;
@@ -51,11 +51,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return this.presBoxView?.selectedArray;
     }
     @computed get presBoxView() {
-        const vpath = this.props.docViewPath();
-        return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as PresBox) : undefined;
+        return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as PresBox;
     }
     @computed get presBox() {
-        return this.props.ContainingCollectionDoc!;
+        return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
     }
     @computed get targetDoc() {
         return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc;
@@ -110,14 +109,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     docFilters={this.props.docFilters}
                     docRangeFilters={this.props.docRangeFilters}
                     searchFilterDocs={this.props.searchFilterDocs}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     rootSelected={returnTrue}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     fitContentsToBox={returnTrue}
                     moveDocument={this.props.moveDocument!}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     whenChildContentsActiveChanged={returnFalse}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
@@ -195,7 +192,7 @@ 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.presBox._viewType === CollectionViewType.Tree ? this.props.ContainingCollectionDoc : undefined; // this.props.DocumentView?.()?.props.treeViewDoc;
+        dragData.treeViewDoc = this.presBox?._viewType === CollectionViewType.Tree ? this.presBox : undefined; // this.props.DocumentView?.()?.props.treeViewDoc;
         dragData.moveDocument = this.props.moveDocument;
         const dragItem: HTMLElement[] = [];
         if (dragArray.length === 1) {
@@ -269,7 +266,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @undoBatch
     removeItem = action((e: React.MouseEvent) => {
         e.stopPropagation();
-        if (this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
+        if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
             this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1;
         }
         this.props.removeDocument?.(this.rootDoc);
@@ -406,15 +403,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed
     get toolbarWidth(): number {
         const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
-        let width: number = NumCast(this.presBox._width);
+        let width: number = NumCast(this.presBox?._width);
         if (presBoxDocView) width = presBoxDocView.props.PanelWidth();
         if (width === 0) width = 300;
         return width;
     }
 
     @computed get presButtons() {
-        const presBox: Doc = this.presBox; //presBox
-        const presBoxColor: string = StrCast(presBox._backgroundColor);
+        const presBox = 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;
@@ -494,10 +491,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @computed get mainItem() {
         const isSelected: boolean = this.selectedArray?.has(this.rootDoc) ? true : false;
-        const isCurrent: boolean = this.presBox._itemIndex === this.indexInPres;
+        const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres;
         const miniView: boolean = this.toolbarWidth <= 110;
-        const presBox: Doc = this.presBox; //presBox
-        const presBoxColor: string = StrCast(presBox._backgroundColor);
+        const presBox = this.presBox; //presBox
+        const presBoxColor: string = StrCast(presBox?._backgroundColor);
         const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
         const activeItem: Doc = this.rootDoc;
 
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index f2e9be61d..d63c25dbe 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -46,7 +46,7 @@ export class TopBar extends React.Component {
         return (
             <div className="topbar-left">
                 {Doc.ActiveDashboard ? (
-                    <IconButton onClick={this.navigateToHome} icon={<FontAwesomeIcon icon="home" />} isCircle={true} hoverStyle="gray" color={this.textColor} />
+                    <IconButton onClick={this.navigateToHome} icon={<FontAwesomeIcon icon="home" />} color={this.textColor} />
                 ) : (
                     <div className="logo-container">
                         <img className="logo" src="/assets/medium-blue-light-blue-circle.png" alt="dash logo"></img>
@@ -55,18 +55,7 @@ export class TopBar extends React.Component {
                     </div>
                 )}
                 {Doc.ActiveDashboard && (
-                    <Button
-                        text="Explore"
-                        tooltip="Browsing mode for directly navigating to documents"
-                        fontSize={FontSize.SECONDARY}
-                        isActive={MainView.Instance._exploreMode}
-                        size={Size.SMALL}
-                        color={this.textColor}
-                        borderRadius={5}
-                        hoverStyle="gray"
-                        iconPosition="right"
-                        onClick={action(() => (MainView.Instance._exploreMode = !MainView.Instance._exploreMode))}
-                    />
+                    <Button text="Explore" tooltip="Browsing mode for directly navigating to documents" size={Size.SMALL} color={this.textColor} onClick={action(() => (MainView.Instance._exploreMode = !MainView.Instance._exploreMode))} />
                 )}
             </div>
         );
@@ -94,13 +83,8 @@ export class TopBar extends React.Component {
                 <Button
                     text={StrCast(Doc.ActiveDashboard.title)}
                     tooltip="Browsing mode for directly navigating to documents"
-                    fontSize={FontSize.SECONDARY}
                     size={Size.SMALL}
                     color={'white'}
-                    type="outline"
-                    backgroundColor={Colors.MEDIUM_BLUE}
-                    borderRadius={5}
-                    hoverStyle="none"
                     onClick={(e: React.MouseEvent) => {
                         const dashView = Doc.ActiveDashboard && DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard);
                         ContextMenu.Instance.addItem({ description: 'Open Dashboard View', event: this.navigateToHome, icon: 'edit' });
@@ -121,21 +105,13 @@ export class TopBar extends React.Component {
                     onClick={() => {
                         SharingManager.Instance.open(undefined, Doc.ActiveDashboard);
                     }}
-                    type="outline"
-                    fontSize={FontSize.SECONDARY}
                     size={Size.SMALL}
-                    borderRadius={5}
-                    hoverStyle="gray"
                 />
                 {!Doc.noviceMode && (
                     <IconButton
-                        fontSize={FontSize.SECONDARY}
-                        isCircle={true}
                         tooltip="Work on a copy of the dashboard layout"
                         size={Size.SMALL}
                         color={this.textColor}
-                        borderRadius={Borders.STANDARD}
-                        hoverStyle="gray"
                         onClick={async () => {
                             const batch = UndoManager.StartBatch('snapshot');
                             await DashboardView.snapshotDashboard();
@@ -156,26 +132,9 @@ export class TopBar extends React.Component {
     @computed get topbarRight() {
         return (
             <div className="topbar-right">
-                <IconButton size={Size.SMALL} isCircle={true} color={Colors.LIGHT_GRAY} backgroundColor={Colors.DARK_GRAY} hoverStyle="gray" onClick={() => ReportManager.Instance.open()} icon={<FaBug />} />
-                <IconButton
-                    size={Size.SMALL}
-                    isCircle={true}
-                    color={Colors.LIGHT_GRAY}
-                    backgroundColor={Colors.DARK_GRAY}
-                    hoverStyle="gray"
-                    onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')}
-                    icon={<FontAwesomeIcon icon="question-circle" />}
-                />
-                <IconButton
-                    size={Size.SMALL}
-                    isCircle={true}
-                    color={Colors.LIGHT_GRAY}
-                    backgroundColor={Colors.DARK_GRAY}
-                    hoverStyle="gray"
-                    isActive={SettingsManager.Instance.isOpen}
-                    onClick={() => SettingsManager.Instance.open()}
-                    icon={<FontAwesomeIcon icon="cog" />}
-                />
+                <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => ReportManager.Instance.open()} icon={<FaBug />} />
+                <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
+                <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => SettingsManager.Instance.open()} icon={<FontAwesomeIcon icon="cog" />} />
                 {/* <Button text={'Logout'} borderRadius={5} hoverStyle={'gray'} backgroundColor={Colors.DARK_GRAY} color={this.textColor} fontSize={FontSize.SECONDARY} onClick={() => window.location.assign(Utils.prepend('/logout'))} /> */}
             </div>
         );
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index 64baa351c..a0b437354 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -13,13 +13,18 @@ import { listSpec } from '../fields/Schema';
 import { Cast, FieldValue } from '../fields/Types';
 import { nullAudio } from '../fields/URLField';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, emptyPath } from '../Utils';
-import "./ImageUpload.scss";
+import './ImageUpload.scss';
 import { MobileInterface } from './MobileInterface';
 import React = require('react');
 
 @observer
 export class AudioUpload extends React.Component {
-    @observable public _audioCol: Doc = FieldValue(Cast(Docs.Create.FreeformDocument([Cast(Docs.Create.AudioDocument(nullAudio, { title: "mobile audio", _width: 500, _height: 100 }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: "0 0" }), Doc)) as Doc;
+    @observable public _audioCol: Doc = FieldValue(
+        Cast(
+            Docs.Create.FreeformDocument([Cast(Docs.Create.AudioDocument(nullAudio, { title: 'mobile audio', _width: 500, _height: 100 }), Doc) as Doc], { title: 'mobile audio', _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: '0 0' }),
+            Doc
+        )
+    ) as Doc;
 
     /**
      * Handles the onclick functionality for the 'Restart' button
@@ -28,25 +33,36 @@ export class AudioUpload extends React.Component {
     @action
     clearUpload = () => {
         for (let i = 1; i < 8; i++) {
-            this.setOpacity(i, "0.2");
+            this.setOpacity(i, '0.2');
         }
-        this._audioCol = FieldValue(Cast(
-            Docs.Create.FreeformDocument(
-                [Cast(Docs.Create.AudioDocument(nullAudio, {
-                    title: "mobile audio",
-                    _width: 500,
-                    _height: 100
-                }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: "0 0" }), Doc)) as Doc;
-    }
+        this._audioCol = FieldValue(
+            Cast(
+                Docs.Create.FreeformDocument(
+                    [
+                        Cast(
+                            Docs.Create.AudioDocument(nullAudio, {
+                                title: 'mobile audio',
+                                _width: 500,
+                                _height: 100,
+                            }),
+                            Doc
+                        ) as Doc,
+                    ],
+                    { title: 'mobile audio', _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: '0 0' }
+                ),
+                Doc
+            )
+        ) as Doc;
+    };
 
-    /** 
+    /**
      * Handles the onClick of the 'Close' button
      * Reset upload interface and toggle audio
      */
     closeUpload = () => {
         this.clearUpload();
         MobileInterface.Instance.toggleAudio();
-    }
+    };
 
     /**
      * Handles the on click of the 'Upload' button.
@@ -57,14 +73,14 @@ export class AudioUpload extends React.Component {
         const audioDoc = this._audioCol;
         const data = Cast(audioRightSidebar.data, listSpec(Doc));
         for (let i = 1; i < 8; i++) {
-            setTimeout(() => this.setOpacity(i, "1"), i * 200);
+            setTimeout(() => this.setOpacity(i, '1'), i * 200);
         }
         if (data) {
             data.push(audioDoc);
         }
         // Resets uploader after 3 seconds
         setTimeout(this.clearUpload, 3000);
-    }
+    };
 
     // Returns the upload audio menu
     private get uploadInterface() {
@@ -72,11 +88,13 @@ export class AudioUpload extends React.Component {
             <>
                 <ContextMenu />
                 <DictationOverlay />
-                <div style={{ display: "none" }}><RichTextMenu key="rich" /></div>
+                <div style={{ display: 'none' }}>
+                    <RichTextMenu key="rich" />
+                </div>
                 <div className="closeUpload" onClick={() => this.closeUpload()}>
-                    <FontAwesomeIcon icon="window-close" size={"lg"} />
+                    <FontAwesomeIcon icon="window-close" size={'lg'} />
                 </div>
-                <FontAwesomeIcon icon="microphone" size="lg" style={{ fontSize: "130" }} />
+                <FontAwesomeIcon icon="microphone" size="lg" style={{ fontSize: '130' }} />
                 <div className="audioUpload_cont">
                     <DocumentView
                         Document={this._audioCol}
@@ -96,12 +114,10 @@ export class AudioUpload extends React.Component {
                         isDocumentActive={returnTrue}
                         isContentActive={emptyFunction}
                         focus={emptyFunction}
-                        styleProvider={() => "rgba(0,0,0,0)"}
+                        styleProvider={() => 'rgba(0,0,0,0)'}
                         docViewPath={returnEmptyDoclist}
                         whenChildContentsActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
                     />
                 </div>
                 <div className="restart_label" onClick={this.clearUpload}>
@@ -125,28 +141,16 @@ export class AudioUpload extends React.Component {
 
     // Handles the setting of the loading bar
     setOpacity = (index: number, opacity: string) => {
-        const slab = document.getElementById("slab0" + index);
+        const slab = document.getElementById('slab0' + index);
         if (slab) {
             slab.style.opacity = opacity;
         }
-    }
+    };
 
     @observable private dialogueBoxOpacity = 1;
     @observable private overlayOpacity = 0.4;
 
     render() {
-        return (
-            <MainViewModal
-                contents={this.uploadInterface}
-                isDisplayed={true}
-                interactive={true}
-                dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
-                overlayDisplayedOpacity={this.overlayOpacity}
-                closeOnExternalClick={this.closeUpload}
-            />
-        );
+        return <MainViewModal contents={this.uploadInterface} isDisplayed={true} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.closeUpload} />;
     }
-
 }
-
-
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 2ae597b0b..a543c2f70 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -397,7 +397,7 @@ export class MobileInterface extends React.Component {
                     renderDepth={0}
                     isDocumentActive={returnTrue}
                     isContentActive={emptyFunction}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     styleProvider={this.whitebackground}
                     docViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
@@ -405,8 +405,6 @@ export class MobileInterface extends React.Component {
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                 />
             </div>
         );
-- 
cgit v1.2.3-70-g09d2


From 650cc00d18c97f89aef77f50703aa9b525dd0368 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 14 Apr 2023 11:12:49 -0400
Subject: got rid of CollectionView prop.  fixed DocFocusOrOpen to toggle
 targets and zoom correctly.

---
 src/client/util/DocumentManager.ts                  | 12 +++++++-----
 .../util/Import & Export/DirectoryImportBox.tsx     |  2 +-
 src/client/util/LinkFollower.ts                     |  2 +-
 src/client/views/MainView.tsx                       |  1 -
 src/client/views/PropertiesDocBacklinksSelector.tsx |  2 +-
 src/client/views/PropertiesDocContextSelector.tsx   | 21 +--------------------
 src/client/views/SidebarAnnos.tsx                   |  1 -
 src/client/views/StyleProvider.tsx                  |  4 ++--
 src/client/views/TemplateMenu.tsx                   |  1 -
 src/client/views/collections/CollectionMenu.tsx     |  1 -
 .../views/collections/CollectionNoteTakingView.tsx  |  2 +-
 .../views/collections/CollectionStackingView.tsx    |  2 +-
 src/client/views/collections/CollectionSubView.tsx  |  6 +++---
 src/client/views/collections/CollectionTreeView.tsx |  6 +++---
 src/client/views/collections/CollectionView.tsx     |  1 -
 src/client/views/collections/TabDocView.tsx         |  1 -
 src/client/views/collections/TreeView.tsx           |  6 +++---
 .../collectionFreeForm/CollectionFreeFormView.tsx   | 11 +++++++----
 src/client/views/linking/LinkMenuItem.tsx           |  2 +-
 src/client/views/nodes/AudioBox.tsx                 |  1 -
 src/client/views/nodes/DocumentView.tsx             | 14 +++++++-------
 src/client/views/nodes/ImageBox.tsx                 |  1 -
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx  |  1 -
 src/client/views/nodes/PDFBox.tsx                   |  1 -
 src/client/views/nodes/ScreenshotBox.tsx            |  1 -
 src/client/views/nodes/VideoBox.tsx                 |  2 --
 src/client/views/nodes/WebBox.tsx                   |  1 -
 .../views/nodes/formattedText/FormattedTextBox.tsx  |  1 -
 src/client/views/pdf/PDFViewer.tsx                  |  1 -
 29 files changed, 39 insertions(+), 69 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ad89e8653..e01457b4f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -227,6 +227,7 @@ export class DocumentManager {
         let rootContextView = docViewPath.shift();
         await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined })));
         if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden;
+        else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) MainView.addDocTabFunc(rootContextView.rootDoc, options.openLocation);
     };
 
     // shows a document by first:
@@ -246,10 +247,9 @@ export class DocumentManager {
             const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
             if (viewIndex !== -1) return res(this.getDocumentView(docContextPath[viewIndex])!);
             options.didMove = true;
-            docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation as OpenWhere);
+            docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
             this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
         });
-
         docContextPath.shift();
         const childViewIterator = async (docView: DocumentView) => {
             const innerDoc = docContextPath.shift();
@@ -297,15 +297,17 @@ export class DocumentManager {
         }
     }
 }
-export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, openLocation: OpenWhere.addRight }, containingDoc?: Doc) {
+export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
     const func = () => {
         const cv = DocumentManager.Instance.getDocumentView(containingDoc);
         const dv = DocumentManager.Instance.getDocumentView(doc, cv);
         if (dv && (!containingDoc || dv.props.docViewPath().lastElement()?.Document === containingDoc)) {
             DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc));
         } else {
-            const showDoc = Doc.BestAlias(DocCast((containingDoc ?? doc.context) !== Doc.MyFilesystem ? containingDoc ?? doc.context : undefined, doc));
-            DocumentManager.Instance.showDocument(showDoc, { ...options, toggleTarget: undefined }, () => DocumentManager.Instance.showDocument(doc, options)).then(() => {
+            const container = DocCast(containingDoc ?? doc.context);
+            const showDoc = !Doc.IsSystem(container) ? container : doc;
+            options.toggleTarget = undefined;
+            DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
                 const cv = DocumentManager.Instance.getDocumentView(containingDoc);
                 const dv = DocumentManager.Instance.getDocumentView(doc, cv);
                 dv && Doc.linkFollowHighlight(dv.rootDoc);
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 76b1323c4..b9bb22564 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -157,7 +157,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
             x: NumCast(doc.x),
             y: NumCast(doc.y) + offset,
         };
-        const parent = _.nth(this.props.docViewPath(), -2); // last element of path is this box's document view, 2nd to last is any collection or other document that may contain it.
+        const parent = this.props.DocumentView?.().props.docViewPath().lastElement();
         if (parent?.rootDoc.type === DocumentType.COL) {
             let importContainer: Doc;
             if (docs.length < 50) {
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index d4d7c66f5..ba2edb65c 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -75,7 +75,7 @@ export class LinkFollower {
                         zoomTime: NumCast(sourceDoc.followLinkTransitionTime, 500),
                         zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null),
                         easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any,
-                        openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox),
+                        openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox) as OpenWhere,
                         effect: sourceDoc,
                         zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText),
                     };
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0384d925e..ccc9a7215 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -883,7 +883,6 @@ export class MainView extends React.Component {
                     isSelected={returnFalse}
                     docViewPath={returnEmptyDoclist}
                     moveDocument={this.moveButtonDoc}
-                    CollectionView={undefined}
                     addDocument={this.addButtonDoc}
                     addDocTab={MainView.addDocTabFunc}
                     pinToPres={emptyFunction}
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index 00c3400cb..935d551c6 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -25,7 +25,7 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
 
         if (otherdoc) {
             otherdoc.hidden = false;
-            this.props.addDocTab(Doc.IsPrototype(otherdoc) ? Doc.MakeDelegate(otherdoc) : otherdoc, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere);
+            this.props.addDocTab(Doc.IsPrototype(otherdoc) ? Doc.MakeDelegate(otherdoc) : otherdoc, OpenWhere.toggleRight);
         }
     };
 
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 93eec61f3..c14d7ef73 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -49,26 +49,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
     getOnClick = (col: Doc, target: Doc) => {
         if (!this.props.DocView) return;
         col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
-        if (col._viewType === CollectionViewType.Freeform) {
-            col._panX = NumCast(target.x) + NumCast(target._width) / 2;
-            col._panY = NumCast(target.y) + NumCast(target._height) / 2;
-        }
-        col.hidden = false;
-        //this.props.addDocTab(col, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere);
-        setTimeout(
-            () =>
-                this.props.DocView &&
-                DocFocusOrOpen(
-                    Doc.GetProto(this.props.DocView.props.Document),
-                    {
-                        //
-                        willZoomCentered: true,
-                        openLocation: (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere,
-                    },
-                    col
-                ),
-            100
-        );
+        DocFocusOrOpen(Doc.GetProto(this.props.DocView.props.Document), undefined, col);
     };
 
     render() {
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 2d2b0f83e..b0aab968e 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -243,7 +243,6 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                         removeDocument={this.removeDocument}
                         moveDocument={this.moveDocument}
                         addDocument={this.addDocument}
-                        CollectionView={undefined}
                         ScreenToLocalTransform={this.screenToLocalTransform}
                         renderDepth={this.props.renderDepth + 1}
                         viewType={CollectionViewType.Stacking}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 739d6d819..5f16e0ebd 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -5,7 +5,7 @@ import { Shadows } from 'browndash-components';
 import { action, runInAction } from 'mobx';
 import { extname } from 'path';
 import { Doc, Opt, StrListCast } from '../../fields/Doc';
-import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types';
 import { DashColor, lightOrDark, Utils } from '../../Utils';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager';
@@ -383,7 +383,7 @@ export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps
         return doc._viewType === CollectionViewType.Docking ? null : (
             <>
                 {DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => {
-                    DocFocusOrOpen(doc, { toggleTarget: true, willPan: true, openLocation: OpenWhere.addRight }, props?.docViewPath().lastElement()?.rootDoc);
+                    DocFocusOrOpen(doc, { toggleTarget: true, willZoomCentered: true, zoomScale: 0 }, DocCast(doc?.context ?? doc?.annotationOn));
                 })}
             </>
         );
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index c5a501aa6..1c816aa05 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -113,7 +113,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 {templateMenu}
                 <CollectionTreeView
                     Document={Doc.MyTemplates}
-                    CollectionView={undefined}
                     styleProvider={DefaultStyleProvider}
                     setHeight={returnFalse}
                     docViewPath={returnEmptyDoclist}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 2007f73aa..5b631676e 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -120,7 +120,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     isSelected={returnFalse}
                     docViewPath={returnEmptyDoclist}
                     moveDocument={returnFalse}
-                    CollectionView={undefined}
                     addDocument={returnFalse}
                     addDocTab={returnFalse}
                     pinToPres={emptyFunction}
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 80e81bc1c..99d4d0bee 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -162,7 +162,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     }
 
     @action
-    moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
+    moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
         return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
     };
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 67f5dc9f4..bdad325d5 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -230,7 +230,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     isAnyChildContentActive = () => this.props.isAnyChildContentActive();
 
     @action
-    moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
+    moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
         return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
     };
     createRef = (ele: HTMLDivElement | null) => {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 132ed6fb6..5581ac8fe 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -19,7 +19,6 @@ import { DocComponent } from '../DocComponent';
 import React = require('react');
 
 export interface SubCollectionViewProps extends CollectionViewProps {
-    CollectionView: Opt<CollectionView>;
     isAnyChildContentActive: () => boolean;
 }
 
@@ -201,8 +200,9 @@ export function CollectionSubView<X>(moreProps?: X) {
             }
         }
 
-        addDocument = (doc: Doc | Doc[]) => this.props.addDocument?.(doc) || false;
-
+        addDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.addDocument?.(doc, annotationKey) || false;
+        removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.removeDocument?.(doc, annotationKey) || false;
+        moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this.props.moveDocument?.(doc, targetCollection, addDocument);
         @action
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
             const docDragData = de.complete.docDragData;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 92932fb61..f81c17a7b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -374,10 +374,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
     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;
+    addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this.props.fieldKey}-annotations`) || false;
+    remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false;
     moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) =>
-        this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false;
+        this.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false;
 
     @observable _headerHeight = 0;
     @computed get content() {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 53fbcc3cc..d7a889362 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -276,7 +276,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             childLayoutString: this.childLayoutString,
             childHideResizeHandles: this.childHideResizeHandles,
             childHideDecorationTitle: this.childHideDecorationTitle,
-            CollectionView: this,
         };
         return (
             <div
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index ef8f395c5..4bbc3bb44 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -577,7 +577,6 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
             <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
                 <CollectionFreeFormView
                     Document={this.props.document}
-                    CollectionView={undefined}
                     docViewPath={returnEmptyDoclist}
                     childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
                     noOverlay={true} // don't render overlay Docs since they won't scale
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 8b1dfc767..8fb610b87 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -48,7 +48,7 @@ export interface TreeViewProps {
     addDocTab: (doc: Doc, where: OpenWhere) => boolean;
     panelWidth: () => number;
     panelHeight: () => number;
-    addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean;
+    addDocument: (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => boolean;
     removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined;
     moveDocument: DragManager.MoveFunction;
     isContentActive: (outsideReaction?: boolean) => boolean;
@@ -373,7 +373,7 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     dropping: boolean = false;
     dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, removeDocument: DragManager.RemoveFunction | undefined, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) {
-        const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
+        const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, undefined, before);
         const localAdd = (doc: Doc | Doc[]) => {
             const innerAdd = (doc: Doc) => {
                 const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
@@ -1186,7 +1186,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 };
                 const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
                 const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
-                const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
+                const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
                 const childLayout = Doc.Layout(pair.layout);
                 const rowHeight = () => {
                     const aspect = Doc.NativeAspect(childLayout);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a3f5e73fb..33fc2ddf3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -311,7 +311,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const xfToCollection = options?.docTransform ?? Transform.Identity();
         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);
+        const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale ?? 0.75 : undefined);
 
         // focus on the document in the collection
         const didMove = !cantTransform && !anchor.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
@@ -319,7 +319,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
         if (didMove) {
             const focusTime = options?.instant ? 0 : options.zoomTime ?? 500;
-            (options.zoomScale || options.willZoomCentered) && scale && (this.Document[this.scaleFieldKey] = scale);
+            (options.zoomScale ?? options.willZoomCentered) && scale && (this.Document[this.scaleFieldKey] = scale);
             this.setPan(panX, panY, focusTime, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
             return focusTime;
         }
@@ -1181,9 +1181,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]());
         const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
 
-        if (scale) {
+        if (scale !== undefined) {
             const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
-            const newScale = Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
+            const newScale =
+                scale === 0
+                    ? NumCast(this.layoutDoc[this.scaleFieldKey])
+                    : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
             return {
                 panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
                 panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d703c9595..19d6c2ae2 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -45,7 +45,7 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
 
         const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
         dragData.canEmbed = true;
-        dragData.moveDocument = (docView.props.docViewPath().lastElement()?.ComponentView as any)?.props.CollectionView?.moveDocument; // this is equal to docView.props.moveDocument, but moveDocument is not a defined prop of a DocumentView .. but maybe it should be?
+        dragData.dropAction = 'alias';
 
         DragManager.StartDocumentDrag([dragEle], dragData, downX, downY, undefined);
     }
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index a6acf882c..68fb19208 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -663,7 +663,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 startTag={'_timecodeToShow' /* audioStart */}
                 endTag={'_timecodeToHide' /* audioEnd */}
                 bringToFront={emptyFunction}
-                CollectionView={undefined}
                 playFrom={this.playFrom}
                 setTime={this.setPlayheadTime}
                 playing={this.playing}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index be07f1fe5..7107707d1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -22,7 +22,7 @@ import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { InteractionUtils } from '../../util/InteractionUtils';
-import { FollowLinkScript, LinkFollower } from '../../util/LinkFollower';
+import { FollowLinkScript } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
@@ -30,7 +30,6 @@ import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { CollectionView } from '../collections/CollectionView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from '../DocComponent';
@@ -69,6 +68,7 @@ export enum OpenWhere {
     close = 'close',
     fullScreen = 'fullScreen',
     toggle = 'toggle',
+    toggleRight = 'toggle:right',
     replace = 'replace',
     replaceRight = 'replace:right',
     replaceLeft = 'replace:left',
@@ -87,7 +87,7 @@ export enum OpenWhereMod {
 
 export interface DocFocusOptions {
     willPan?: boolean; // determines whether to pan to target document
-    willZoomCentered?: boolean; // determines whether to zoom in on target document
+    willZoomCentered?: boolean; // determines whether to zoom in on target document.  if zoomScale is 0, this just centers the document
     zoomScale?: number; // percent of containing frame to zoom into document
     zoomTime?: number;
     didMove?: boolean; // whether a document was changed during the showDocument process
@@ -97,7 +97,7 @@ export interface DocFocusOptions {
     effect?: Doc; // animation effect for focus
     noSelect?: boolean; // whether target should be selected after focusing
     playAudio?: boolean; // whether to play audio annotation on focus
-    openLocation?: string; // where to open a missing document
+    openLocation?: OpenWhere; // where to open a missing document
     zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
     toggleTarget?: boolean; // whether to toggle target on and off
     anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc
@@ -170,9 +170,9 @@ export interface DocumentViewSharedProps {
     rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
     addDocTab: (doc: Doc, where: OpenWhere) => boolean;
     filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
-    addDocument?: (doc: Doc | Doc[]) => boolean;
-    removeDocument?: (doc: Doc | Doc[]) => boolean;
-    moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
+    addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
+    removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
+    moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean;
     pinToPres: (document: Doc, pinProps: PinProps) => void;
     ScreenToLocalTransform: () => Transform;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 22ecaa299..7c98aa6e4 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -478,7 +478,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     renderDepth={this.props.renderDepth + 1}
                     fieldKey={this.annotationKey}
                     styleProvider={this.props.styleProvider}
-                    CollectionView={undefined}
                     isAnnotationOverlay={true}
                     annotationLayerHostsContent={true}
                     PanelWidth={this.props.PanelWidth}
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index d65ef9c4c..7b437c7de 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -57,7 +57,6 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
                             Document={this.props.place}
                             DataDoc={undefined}
                             fieldKey="data"
-                            CollectionView={undefined}
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
                             docFilters={returnEmptyFilter}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 502811e35..8f87b9e08 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -547,7 +547,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         removeDocument={this.sidebarRemDocument}
                         moveDocument={this.sidebarMoveDocument}
                         addDocument={this.sidebarAddDocument}
-                        CollectionView={undefined}
                         ScreenToLocalTransform={this.sidebarScreenToLocal}
                         renderDepth={this.props.renderDepth + 1}
                         noSidebar={true}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index e015024fd..aa2b22e28 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -303,7 +303,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                             removeDocument={returnFalse}
                             moveDocument={returnFalse}
                             addDocument={returnFalse}
-                            CollectionView={undefined}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform}
                             renderDepth={this.props.renderDepth + 1}>
                             <>
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b144c9318..7a7d4fe37 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -963,7 +963,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     startTag={'_timecodeToShow' /* videoStart */}
                     endTag={'_timecodeToHide' /* videoEnd */}
                     bringToFront={emptyFunction}
-                    CollectionView={undefined}
                     playFrom={this.playFrom}
                     setTime={this.setPlayheadTime}
                     playing={this.playing}
@@ -1074,7 +1073,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             NativeHeight={returnZero}
                             renderDepth={this.props.renderDepth + 1}
                             fieldKey={this.annotationKey}
-                            CollectionView={undefined}
                             isAnnotationOverlay={true}
                             annotationLayerHostsContent={true}
                             PanelWidth={this.panelWidth}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 66d0fd385..ef3aa1f47 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -915,7 +915,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 renderDepth={this.props.renderDepth + 1}
                 isAnnotationOverlay={true}
                 fieldKey={this.annotationKey}
-                CollectionView={undefined}
                 setPreviewCursor={this.setPreviewCursor}
                 setBrushViewer={this.setBrushViewer}
                 PanelWidth={this.panelWidth}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bc2a5d797..5719ea83c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1844,7 +1844,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         removeDocument={this.sidebarRemDocument}
                         moveDocument={this.sidebarMoveDocument}
                         addDocument={this.sidebarAddDocument}
-                        CollectionView={undefined}
                         ScreenToLocalTransform={this.sidebarScreenToLocal}
                         renderDepth={this.props.renderDepth + 1}
                         setHeight={this.setSidebarHeight}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 6ab541207..d82a7d1ae 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -530,7 +530,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 renderDepth={this.props.renderDepth + 1}
                 isAnnotationOverlay={true}
                 fieldKey={this.props.fieldKey + '-annotations'}
-                CollectionView={undefined}
                 getScrollHeight={this.getScrollHeight}
                 setPreviewCursor={this.setPreviewCursor}
                 setBrushViewer={this.setBrushViewer}
-- 
cgit v1.2.3-70-g09d2


From 7342eb81f241ce0b2a5a33ddfb0c865eab6a492f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 27 Apr 2023 09:10:36 -0400
Subject: added proper scaling of multi-selections and groups.  fixed
 pinViewport to work again.

---
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/views/DocumentButtonBar.tsx             |   4 -
 src/client/views/DocumentDecorations.tsx           | 112 ++++++++++++---------
 src/client/views/collections/TabDocView.tsx        |   1 +
 .../collections/collectionFreeForm/MarqueeView.tsx |   5 +-
 src/client/views/nodes/DocumentView.tsx            |  10 +-
 src/client/views/nodes/button/FontIconBox.tsx      |   6 +-
 .../views/nodes/formattedText/RichTextRules.ts     |   2 +-
 src/client/views/nodes/formattedText/marks_rts.ts  |   2 +-
 src/client/views/nodes/trails/PresBox.tsx          |   1 +
 10 files changed, 80 insertions(+), 65 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 5475873a9..479fcd011 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -621,8 +621,8 @@ export class CurrentUserUtils {
     }
     static viewTools(): Button[] {
         return [
-            { title: "Grid",          icon: "border-all",   toolTip: "Show Grid",           btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid",       funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
             { title: "Snap\xA0Lines", icon: "th",           toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+            { title: "Grid",          icon: "border-all",   toolTip: "Show Grid",           btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid",       funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
             { title: "View\xA0All",   icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll",    funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
             { title: "Clusters",      icon: "braille",      toolTip: "Show Doc Clusters",   btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters",   funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
             { title: "Arrange",icon: "arrow-down-short-wide",toolTip: "Toggle Auto Arrange",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange",    funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 8c2ced55a..30e41b06c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -303,10 +303,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                             onPointerEnter={action(e => (this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : '')))}
                             onPointerLeave={action(e => (this.subEndLink = ''))}
                             onClick={e => {
-                                const docs = this.props
-                                    .views()
-                                    .filter(v => v)
-                                    .map(dv => dv!.rootDoc);
                                 this.view0 &&
                                     DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.props.Document, true, this.view0, {
                                         pinDocLayout: pinLayout,
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 042e8f6d0..2811c96eb 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -12,7 +12,7 @@ import { InkField } from '../../fields/InkField';
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../fields/Types';
 import { GetEffectiveAcl } from '../../fields/util';
-import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
+import { aggregateBounds, emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
 import { Docs } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DragManager } from '../util/DragManager';
@@ -34,6 +34,7 @@ import React = require('react');
 import { RichTextField } from '../../fields/RichTextField';
 import { LinkFollower } from '../util/LinkFollower';
 import _ = require('lodash');
+import { DocumentManager } from '../util/DocumentManager';
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
@@ -509,73 +510,86 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             dragRight = false,
             dragBotRight = false,
             dragTop = false;
-        let dX = 0,
-            dY = 0,
-            dW = 0,
-            dH = 0;
+        let dXin = 0,
+            dYin = 0,
+            dWin = 0,
+            dHin = 0;
         switch (this._resizeHdlId.split(' ')[0]) {
             case '':
                 break;
             case 'documentDecorations-topLeftResizer':
-                dX = -1;
-                dY = -1;
-                dW = -move[0];
-                dH = -move[1];
+                dXin = -1;
+                dYin = -1;
+                dWin = -move[0];
+                dHin = -move[1];
                 break;
             case 'documentDecorations-topRightResizer':
-                dW = move[0];
-                dY = -1;
-                dH = -move[1];
+                dWin = move[0];
+                dYin = -1;
+                dHin = -move[1];
                 break;
             case 'documentDecorations-topResizer':
-                dY = -1;
-                dH = -move[1];
+                dYin = -1;
+                dHin = -move[1];
                 dragTop = true;
                 break;
             case 'documentDecorations-bottomLeftResizer':
-                dX = -1;
-                dW = -move[0];
-                dH = move[1];
+                dXin = -1;
+                dWin = -move[0];
+                dHin = move[1];
                 break;
             case 'documentDecorations-bottomRightResizer':
-                dW = move[0];
-                dH = move[1];
+                dWin = move[0];
+                dHin = move[1];
                 dragBotRight = true;
                 break;
             case 'documentDecorations-bottomResizer':
-                dH = move[1];
+                dHin = move[1];
                 dragBottom = true;
                 break;
             case 'documentDecorations-leftResizer':
-                dX = -1;
-                dW = -move[0];
+                dXin = -1;
+                dWin = -move[0];
                 break;
             case 'documentDecorations-rightResizer':
-                dW = move[0];
+                dWin = move[0];
                 dragRight = true;
                 break;
         }
 
-        SelectionManager.Views().forEach(
+        const isGroup = first.rootDoc._isGroup ? first.rootDoc : undefined;
+        const scaleViews = isGroup ? DocListCast(isGroup.data).map(doc => DocumentManager.Instance.getFirstDocumentView(doc)!) : SelectionManager.Views();
+        const aggBounds = aggregateBounds(scaleViews.map(view => view.rootDoc) as any, 0, 0);
+        const refWidth = aggBounds.r - aggBounds.x;
+        const refHeight = aggBounds.b - aggBounds.y;
+        const scaleRefPt = first.props
+            .ScreenToLocalTransform()
+            .inverse()
+            .transformPoint(
+                NumCast(isGroup?._xPadding) + (dXin ? refWidth : 0), //
+                NumCast(isGroup?._yPadding) + (dYin ? refHeight : 0)
+            );
+        scaleViews.forEach(
             action((docView: DocumentView) => {
                 if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions();
-                if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
-                    const doc = Document(docView.rootDoc);
+                if (dXin !== 0 || dYin !== 0 || dWin !== 0 || dHin !== 0) {
+                    const doc = docView.rootDoc;
+                    const refCent = docView.props.ScreenToLocalTransform().transformPoint(scaleRefPt[0], scaleRefPt[1]);
+
                     if (doc.nativeHeightUnfrozen && !NumCast(doc.nativeHeight)) {
                         doc._nativeHeight = (NumCast(doc._height) / NumCast(doc._width, 1)) * docView.nativeWidth;
                     }
                     const nwidth = docView.nativeWidth;
                     const nheight = docView.nativeHeight;
-                    let docheight = doc._height || 0;
-                    let docwidth = doc._width || 0;
-                    const width = docwidth;
-                    let height = docheight || (nheight / nwidth) * width;
-                    height = !height || isNaN(height) ? 20 : height;
+                    const docwidth = NumCast(doc._width);
+                    let docheight = (hgt => (!hgt || isNaN(hgt) ? 20 : hgt))(NumCast(doc._height) || (nheight / nwidth) * docwidth);
+                    let dW = docwidth * (dWin / refWidth);
+                    let dH = docheight * (dHin / refHeight);
                     const scale = docView.props.ScreenToLocalTransform().Scale;
                     const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable && ((!dragBottom && !dragTop) || e.ctrlKey || doc.nativeHeightUnfrozen);
                     if (nwidth && nheight) {
-                        if (nwidth / nheight !== width / height && !dragBottom && !dragTop) {
-                            height = (nheight / nwidth) * width;
+                        if (nwidth / nheight !== docwidth / docheight && !dragBottom && !dragTop) {
+                            docheight = (nheight / nwidth) * docwidth;
                         }
                         if (modifyNativeDim && !dragBottom && !dragTop) {
                             // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
@@ -583,21 +597,25 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                             else dW = (dH * nwidth) / nheight;
                         }
                     }
-                    let actualdW = Math.max(width + dW * scale, 20);
-                    let actualdH = Math.max(height + dH * scale, 20);
+                    let actualdW = Math.max(docwidth + dW * scale, 20);
+                    let actualdH = Math.max(docheight + dH * scale, 20);
+                    let dX = !dWin ? 0 : scale * refCent[0] * (1 - (1 + dWin / refWidth));
+                    let dY = !dHin ? 0 : scale * refCent[1] * (1 - (1 + dHin / refHeight));
                     const preserveNativeDim = doc._nativeHeightUnfrozen === false && doc._nativeDimModifiable === false;
                     const fixedAspect = nwidth && nheight && (!doc._fitWidth || preserveNativeDim || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable);
                     if (fixedAspect) {
                         if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) {
                             if (dragRight && modifyNativeDim) {
                                 if (Doc.NativeWidth(doc)) {
-                                    doc._nativeWidth = (actualdW / (doc._width || 1)) * Doc.NativeWidth(doc);
+                                    doc._nativeWidth = (actualdW / (docwidth || 1)) * Doc.NativeWidth(doc);
                                 }
                             } else {
                                 if (!doc._fitWidth || preserveNativeDim) {
                                     actualdH = (nheight / nwidth) * actualdW;
                                     doc._height = actualdH;
-                                } else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
+                                } else if (!modifyNativeDim || dragBotRight) {
+                                    doc._height = actualdH;
+                                }
                             }
                             doc._width = actualdW;
                         } else {
@@ -605,21 +623,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                 // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height.  But they are marked to allow their nativeHeight
                                 // to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match
                                 // a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
-                                doc._nativeHeight = (actualdH / (doc._height || 1)) * Doc.NativeHeight(doc);
+                                doc._nativeHeight = (actualdH / (docheight || 1)) * Doc.NativeHeight(doc);
                                 doc._autoHeight = false;
                             } else {
                                 if (!doc._fitWidth || preserveNativeDim) {
                                     actualdW = (nwidth / nheight) * actualdH;
                                     doc._width = actualdW;
-                                } else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
+                                } else if (!modifyNativeDim || dragBotRight) {
+                                    doc._width = actualdW;
+                                }
                             }
                             if (!modifyNativeDim) {
-                                actualdH = Math.min((nheight / nwidth) * NumCast(doc._width), actualdH);
-                                doc._height = actualdH;
-                            } else doc._height = actualdH;
+                                actualdH = Math.min((nheight / nwidth) * docwidth, actualdH);
+                            }
+                            doc._height = actualdH;
                         }
                     } else {
-                        const rotCtr = [NumCast(doc._width) / 2, NumCast(doc._height) / 2];
+                        const rotCtr = [docwidth / 2, docheight / 2];
                         const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._rotation) / 180) * Math.PI);
 
                         const maxHeight = doc.nativeHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling();
@@ -632,8 +652,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         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]);
                     }
-                    doc.x = (doc.x || 0) + dX * (actualdW - docwidth);
-                    doc.y = (doc.y || 0) + (dragBottom ? 0 : dY * (actualdH - docheight));
+                    doc.x = NumCast(doc.x) + dX;
+                    doc.y = NumCast(doc.y) + dY;
                     doc._lastModified = new DateField();
                 }
                 const val = this._dragHeights.get(docView.layoutDoc);
@@ -726,7 +746,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         }
         // hide the decorations if the parent chooses to hide it or if the document itself hides it
         const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations;
-        const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || seldocview.rootDoc._isGroup || this._isRounding || this._isRotating;
+        const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || this._isRounding || this._isRotating;
         const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating;
         const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating;
         // if multiple documents have been opened at the same time, then don't show open button
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 4bbc3bb44..45604c1bf 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -266,6 +266,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
             const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
 
+            if (pinProps.pinViewport) PresBox.pinDocView(pinDoc, pinProps, anchorDoc ?? doc);
             if (!pinProps?.audioRange && duration !== undefined) {
                 pinDoc.mediaStart = 'manual';
                 pinDoc.mediaStop = 'manual';
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index eaeb5f933..e5f47823c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -425,9 +425,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
      */
     @undoBatch
     @action
-    pinWithView = async () => {
-        const doc = this.props.Document;
-        TabDocView.PinDoc(doc, { pinViewport: this.Bounds });
+    pinWithView = () => {
+        TabDocView.PinDoc(this.props.Document, { pinViewport: this.Bounds });
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
     };
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c957bc778..a25e5c42d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -765,9 +765,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
         }
         const constantItems: ContextMenuProps[] = [];
-
+        constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' });
         if (!Doc.IsSystem(this.rootDoc)) {
-            constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' });
             constantItems.push({ description: 'Export as Zip file', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
             constantItems.push({ description: 'Import Zipped file', icon: 'upload', event: ({ x, y }) => this.importDocument() });
             (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
@@ -775,8 +774,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
                 constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
             }
-            cm.addItem({ description: 'General...', noexpand: true, subitems: constantItems, icon: 'question' });
         }
+        cm.addItem({ description: 'General...', noexpand: !Doc.IsSystem(this.rootDoc), subitems: constantItems, icon: 'question' });
+
         const help = cm.findByDescription('Help...');
         const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
         !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' });
@@ -819,9 +819,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (documentationDescription && documentationLink) {
             helpItems.push({
                 description: documentationDescription,
-                event: () => {
-                    window.open(documentationLink, '_blank');
-                },
+                event: () => window.open(documentationLink, '_blank'),
                 icon: 'book',
             });
         }
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 1a75a7e76..4f570b5fc 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -610,16 +610,16 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
     selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
 });
 
-ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snap lines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snapline' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) {
     const selected = SelectionManager.Docs().lastElement();
     // prettier-ignore
-    const map: Map<'grid' | 'snap lines' | 'clusters' | 'arrange'| 'viewAll', { undo: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
+    const map: Map<'grid' | 'snapline' | 'clusters' | 'arrange'| 'viewAll', { undo: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
         ['grid', {
             undo: false,
             checkResult: (doc:Doc) => doc._backgroundGridShow,
             setDoc: (doc:Doc) => doc._backgroundGridShow = !doc._backgroundGridShow,
         }],
-        ['snap lines', {
+        ['snapline', {
             undo: false,
             checkResult: (doc:Doc) => doc.showSnapLines,
             setDoc: (doc:Doc) => doc._showSnapLines = !doc._showSnapLines,
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 20ce929ad..e5943f257 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -391,7 +391,7 @@ export class RichTextRules {
                 const content = selected.selection.content();
                 const replaced = node ? selected.replaceRangeWith(start, end, schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) : state.tr;
 
-                return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
+                return replaced.setSelection(new TextSelection(replaced.doc.resolve(end))).setStoredMarks([...node.marks, ...sm]);
             }),
 
             new InputRule(new RegExp(/%\)/), (state, match, start, end) => {
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 3898490d3..5b47e8a70 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -349,7 +349,7 @@ export const marks: { [index: string]: MarkSpec } = {
         group: 'inline',
         toDOM(node: any) {
             const uid = node.attrs.userid.replace('.', '').replace('@', '');
-            const min = Math.round(node.attrs.modified / 12);
+            const min = Math.round(node.attrs.modified / 60);
             const hr = Math.round(min / 60);
             const day = Math.round(hr / 60 / 24);
             const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : '';
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index f8c47aafe..0b780f589 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -567,6 +567,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 bestTarget._panY = viewport.panY;
                 const dv = DocumentManager.Instance.getDocumentView(bestTarget);
                 if (dv) {
+                    changed = true;
                     const computedScale = NumCast(activeItem.presZoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height);
                     activeItem.presMovement === PresMovement.Zoom && (bestTarget._viewScale = computedScale);
                     dv.ComponentView?.brushView?.(viewport);
-- 
cgit v1.2.3-70-g09d2

