From 18dc6b3c66c28d0e78a579479b80786aa6589970 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 2 May 2020 18:01:49 -0400
Subject: cleaned up presbox to not use reactions but to use a lookup script. 
 put lookupField into DocComponent for general use

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

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 0d859c3f1..822f44f5a 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -685,6 +685,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         if (curPres) {
             const pinDoc = Doc.MakeAlias(doc);
             pinDoc.presentationTargetDoc = doc;
+            pinDoc.presZoomButton = true;
             Doc.AddDocToList(curPres, "data", pinDoc);
             if (!DocumentManager.Instance.getDocumentView(curPres)) {
                 CollectionDockingView.AddRightSplit(curPres);
-- 
cgit v1.2.3-70-g09d2


From 3d5bc514936ba804eaa24e9ca1f4619bcaf81eb5 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 3 May 2020 23:59:35 -0400
Subject: extended documentBox's to support drag and drop of style. 
 reorganized imports to avoid some cycles

---
 src/client/documents/Documents.ts                  |  10 +-
 src/client/util/DragManager.ts                     | 112 +++++++--------------
 src/client/util/DropConverter.ts                   |   2 +
 src/client/util/SelectionManager.ts                |   5 -
 src/client/views/DocumentDecorations.tsx           |   5 +-
 src/client/views/MainView.tsx                      |   8 +-
 .../views/collections/CollectionDockingView.tsx    |   5 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |   2 +-
 .../views/collections/CollectionPileView.tsx       |   5 +-
 .../views/collections/CollectionSchemaCells.tsx    |   2 +-
 .../CollectionSchemaMovableTableHOC.tsx            |   4 +-
 .../views/collections/CollectionStackingView.tsx   |   4 +-
 .../CollectionStackingViewFieldColumn.tsx          |   4 +-
 .../views/collections/CollectionTreeView.tsx       |   4 +-
 .../CollectionFreeFormLinksView.tsx                |   3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   6 +-
 src/client/views/linking/LinkMenuGroup.tsx         |   4 +-
 src/client/views/linking/LinkMenuItem.tsx          |  39 ++++++-
 src/client/views/nodes/AudioBox.tsx                |  10 +-
 .../views/nodes/ContentFittingDocumentView.tsx     |   8 +-
 src/client/views/nodes/DocumentBox.scss            |   3 +-
 src/client/views/nodes/DocumentBox.tsx             |  57 +++++++----
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/QueryBox.tsx                |   3 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  13 ++-
 .../authentication/models/current_user_utils.ts    |  27 +++--
 26 files changed, 189 insertions(+), 158 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8ea8ded0f..45687f597 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -139,6 +139,7 @@ export interface DocumentOptions {
     ischecked?: ScriptField; // returns whether a font icon box is checked
     activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts)
     onClick?: ScriptField;
+    onDoubleClick?: ScriptField;
     onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked
     onChildDoubleClick?: ScriptField; // script given to children of a collection to execute when they are double clicked
     onPointerDown?: ScriptField;
@@ -493,7 +494,7 @@ export namespace Docs {
             const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey);
             const viewDoc = Doc.MakeDelegate(dataDoc, delegId);
 
-            viewDoc.type !== DocumentType.LINK && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: viewDoc }, { doc: d }, "audio link", "audio timeline"));
+            viewDoc.type !== DocumentType.LINK && DocUtils.MakeLinkToActiveAudio(viewDoc);
 
             return Doc.assign(viewDoc, delegateProps, true);
         }
@@ -645,7 +646,7 @@ export namespace Docs {
         }
 
         export function DocumentDocument(document?: Doc, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.DOCHOLDER), document, { title: document ? document.title + "" : "container", ...options });
+            return InstanceFromProto(Prototypes.get(DocumentType.DOCHOLDER), document, { title: document ? document.title + "" : "container", targetDropAction: "move", ...options });
         }
 
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -1005,6 +1006,11 @@ export namespace DocUtils {
         });
     }
 
+    export let ActiveRecordings: Doc[] = [];
+
+    export function MakeLinkToActiveAudio(doc: Doc) {
+        DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
+    }
     export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) {
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
         if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 041f2fc2c..d91eb60ef 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,26 +1,16 @@
-import { Doc, Field, DocListCast } from "../../new_fields/Doc";
-import { Cast, ScriptCast, StrCast, NumCast } from "../../new_fields/Types";
-import { emptyFunction } from "../../Utils";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import * as globalCssVariables from "../views/globalCssVariables.scss";
-import { DocumentManager } from "./DocumentManager";
-import { LinkManager } from "./LinkManager";
-import { SelectionManager } from "./SelectionManager";
-import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
-import { Docs, DocUtils } from "../documents/Documents";
-import { ScriptField } from "../../new_fields/ScriptField";
+import { action, observable, runInAction } from "mobx";
+import { DateField } from "../../new_fields/DateField";
+import { Doc, Field, Opt } from "../../new_fields/Doc";
 import { List } from "../../new_fields/List";
 import { PrefetchProxy } from "../../new_fields/Proxy";
 import { listSpec } from "../../new_fields/Schema";
-import { Scripting } from "./Scripting";
-import { convertDropDataToButtons } from "./DropConverter";
-import { AudioBox } from "../views/nodes/AudioBox";
-import { DateField } from "../../new_fields/DateField";
-import { DocumentView } from "../views/nodes/DocumentView";
+import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
+import { ScriptField } from "../../new_fields/ScriptField";
+import { Cast, NumCast, ScriptCast, StrCast } from "../../new_fields/Types";
+import { emptyFunction } from "../../Utils";
+import { Docs, DocUtils } from "../documents/Documents";
+import * as globalCssVariables from "../views/globalCssVariables.scss";
 import { UndoManager } from "./UndoManager";
-import { PointData } from "../../new_fields/InkField";
-import { MainView } from "../views/MainView";
-import { action } from "mobx";
 
 export type dropActionType = "alias" | "copy" | "move" | undefined; // undefined = move
 export function SetupDrag(
@@ -56,10 +46,10 @@ export function SetupDrag(
     const onItemDown = async (e: React.PointerEvent) => {
         if (e.button === 0) {
             e.stopPropagation();
-            if (e.shiftKey && CollectionDockingView.Instance) {
+            if (e.shiftKey) {
                 e.persist();
                 const dragDoc = await docFunc();
-                dragDoc && CollectionDockingView.Instance.StartOtherDrag({
+                dragDoc && DragManager.Vals.Instance.StartWindowDrag?.({
                     pageX: e.pageX,
                     pageY: e.pageY,
                     preventDefault: emptyFunction,
@@ -76,8 +66,19 @@ export function SetupDrag(
 
 export namespace DragManager {
     let dragDiv: HTMLDivElement;
-    export let horizSnapLines: number[] = [];
-    export let vertSnapLines: number[] = [];
+    export class Vals {
+        static Instance: Vals = new Vals();
+        @observable public IsDragging: boolean = false;
+        @observable public horizSnapLines: number[] = [];
+        @observable public vertSnapLines: number[] = [];
+        public StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined;
+        public SetIsDragging(dragging: boolean) { runInAction(() => this.IsDragging = dragging); }
+        public GetIsDragging() { return this.IsDragging; }
+        public clearSnapLines() {
+            this.vertSnapLines.length = 0;
+            this.horizSnapLines.length = 0;
+        }
+    }
 
     export function Root() {
         const root = document.getElementById("root");
@@ -215,7 +216,7 @@ export namespace DragManager {
     export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
         const addAudioTag = (dropDoc: any) => {
             dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField);
-            dropDoc instanceof Doc && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: dropDoc }, { doc: d }, "audio link", "audio timeline"));
+            dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(dropDoc);
             return dropDoc;
         };
         const batch = UndoManager.StartBatch("dragging");
@@ -246,40 +247,6 @@ export namespace DragManager {
         StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
     }
 
-    // drag links and drop link targets (aliasing them if needed)
-    export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
-        const draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
-
-        if (draggedDocs.length) {
-            const moddrag: Doc[] = [];
-            for (const draggedDoc of draggedDocs) {
-                const doc = await Cast(draggedDoc.annotationOn, Doc);
-                if (doc) moddrag.push(doc);
-            }
-
-            const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
-            dragData.moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => {
-                docView.props.removeDocument?.(doc);
-                addDocument(doc);
-                return true;
-            };
-            const containingView = docView.props.ContainingCollectionView;
-            const finishDrag = (e: 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[]));
-
-            StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag);
-        }
-    }
-
     // drag&drop the pdf annotation anchor which will create a text note  on drop via a dropCompleted() DragOption 
     export function StartPdfAnnoDrag(eles: HTMLElement[], dragData: PdfAnnoDragData, downX: number, downY: number, options?: DragOptions) {
         StartDrag(eles, dragData, downX, downY, options);
@@ -300,10 +267,8 @@ export namespace DragManager {
     }
 
     export function SetSnapLines(horizLines: number[], vertLines: number[]) {
-        horizSnapLines = horizLines;
-        vertSnapLines = vertLines;
-        MainView.Instance._hLines = horizLines;
-        MainView.Instance._vLines = vertLines;
+        DragManager.Vals.Instance.horizSnapLines.push(...horizLines);
+        DragManager.Vals.Instance.vertSnapLines.push(...vertLines);
     }
 
     export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) {
@@ -320,10 +285,13 @@ export namespace DragManager {
             return drag;
         };
 
-        return { thisX: snapVal([xFromLeft, xFromRight], e.pageX, vertSnapLines), thisY: snapVal([yFromTop, yFromBottom], e.pageY, horizSnapLines) };
+        return {
+            thisX: snapVal([xFromLeft, xFromRight], e.pageX, DragManager.Vals.Instance.vertSnapLines),
+            thisY: snapVal([yFromTop, yFromBottom], e.pageY, DragManager.Vals.Instance.horizSnapLines)
+        };
     }
     export let docsBeingDragged: Doc[] = [];
-    function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
+    export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
         eles = eles.filter(e => e);
         if (!dragDiv) {
             dragDiv = document.createElement("div");
@@ -331,7 +299,7 @@ export namespace DragManager {
             dragDiv.style.pointerEvents = "none";
             DragManager.Root().appendChild(dragDiv);
         }
-        SelectionManager.SetIsDragging(true);
+        DragManager.Vals.Instance.SetIsDragging(true);
         const scaleXs: number[] = [];
         const scaleYs: number[] = [];
         const xs: number[] = [];
@@ -413,14 +381,14 @@ export namespace DragManager {
             if (dragData instanceof DocumentDragData) {
                 dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : undefined;
             }
-            if (e.shiftKey && CollectionDockingView.Instance && dragData.droppedDocuments.length === 1) {
+            if (e.shiftKey && dragData.droppedDocuments.length === 1) {
                 !dragData.dropAction && (dragData.dropAction = alias);
                 if (dragData.dropAction === "move") {
                     dragData.removeDocument?.(dragData.draggedDocuments[0]);
                 }
                 AbortDrag();
                 finishDrag?.(new DragCompleteEvent(true, dragData));
-                CollectionDockingView.Instance.StartOtherDrag({
+                DragManager.Vals.Instance.StartWindowDrag?.({
                     pageX: e.pageX,
                     pageY: e.pageY,
                     preventDefault: emptyFunction,
@@ -447,22 +415,19 @@ export namespace DragManager {
         const endDrag = action(() => {
             document.removeEventListener("pointermove", moveHandler, true);
             document.removeEventListener("pointerup", upHandler);
-            MainView.Instance._hLines = [];
-            MainView.Instance._vLines = [];
-            vertSnapLines.length = 0;
-            horizSnapLines.length = 0;
+            DragManager.Vals.Instance.clearSnapLines();
         });
 
         AbortDrag = () => {
             hideDragShowOriginalElements();
-            SelectionManager.SetIsDragging(false);
+            DragManager.Vals.Instance.SetIsDragging(false);
             options?.dragComplete?.(new DragCompleteEvent(true, dragData));
             endDrag();
         };
         const upHandler = (e: PointerEvent) => {
             hideDragShowOriginalElements();
             dispatchDrag(eles, e, dragData, xFromLeft, yFromTop, xFromRight, yFromBottom, options, finishDrag);
-            SelectionManager.SetIsDragging(false);
+            DragManager.Vals.Instance.SetIsDragging(false);
             endDrag();
             options?.dragComplete?.(new DragCompleteEvent(false, dragData));
         };
@@ -520,4 +485,3 @@ export namespace DragManager {
         }
     }
 }
-Scripting.addGlobal(function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); });
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index b1993d401..d6db882b8 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -7,6 +7,7 @@ import { Docs } from "../documents/Documents";
 import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
 import { RichTextField } from "../../new_fields/RichTextField";
 import { ImageField } from "../../new_fields/URLField";
+import { Scripting } from "./Scripting";
 
 // 
 // converts 'doc' into a template that can be used to render other documents.
@@ -75,3 +76,4 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
         data.droppedDocuments[i] = dbox;
     });
 }
+Scripting.addGlobal(function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); });
\ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index a49977c42..d509168b6 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -8,10 +8,8 @@ export namespace SelectionManager {
 
     class Manager {
 
-        @observable IsDragging: boolean = false;
         SelectedDocuments: ObservableMap<DocumentView, boolean> = new ObservableMap();
 
-
         @action
         SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
             // if doc is not in SelectedDocuments, add it
@@ -78,9 +76,6 @@ export namespace SelectionManager {
         if (found) manager.SelectDoc(found, false);
     }
 
-    export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); }
-    export function GetIsDragging() { return manager.IsDragging; }
-
     export function SelectedDocuments(): Array<DocumentView> {
         return Array.from(manager.SelectedDocuments.keys());
     }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 396fe12b5..f8e77d90a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -364,8 +364,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         this.Interacting = false;
         (e.button === 0) && this._resizeUndo?.end();
         this._resizeUndo = undefined;
-        MainView.Instance._hLines = [];
-        MainView.Instance._vLines = [];
+        DragManager.Vals.Instance.clearSnapLines();
     }
 
     @computed
@@ -404,7 +403,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         const darkScheme = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimgray" : undefined;
         const bounds = this.Bounds;
         const seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
-        if (SelectionManager.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
+        if (DragManager.Vals.Instance.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
             return (null);
         }
         const minimal = bounds.r - bounds.x < 100 ? true : false;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a29a6baac..71c2bf245 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -43,6 +43,7 @@ import PDFMenu from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
 import { ScriptField } from '../../new_fields/ScriptField';
 import { TimelineMenu } from './animationtimeline/TimelineMenu';
+import { DragManager } from '../util/DragManager';
 
 @observer
 export class MainView extends React.Component {
@@ -574,9 +575,6 @@ export class MainView extends React.Component {
         return this._mainViewRef;
     }
 
-    @observable public _hLines: any;
-    @observable public _vLines: any;
-
     render() {
         return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")} ref={this._mainViewRef}>
             <DictationOverlay />
@@ -597,8 +595,8 @@ export class MainView extends React.Component {
             {// TO VIEW SNAP LINES
                 <div className="snapLines" style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", pointerEvents: "none" }}>
                     <svg style={{ width: "100%", height: "100%" }}>
-                        {this._hLines?.map((l: any) => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
-                        {this._vLines?.map((l: any) => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+                        {DragManager.Vals.Instance.horizSnapLines.map((l: any) => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+                        {DragManager.Vals.Instance.vertSnapLines.map((l: any) => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
                     </svg>
                 </div>}
             <TimelineMenu />
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 822f44f5a..33ece13cc 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -68,10 +68,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         //Why is this here?
         (window as any).React = React;
         (window as any).ReactDOM = ReactDOM;
+        DragManager.Vals.Instance.StartWindowDrag = this.StartOtherDrag;
     }
     hack: boolean = false;
     undohack: any = null;
-    public StartOtherDrag(e: any, dragDocs: Doc[]) {
+    public StartOtherDrag = (e: any, dragDocs: Doc[]) => {
         let config: any;
         if (dragDocs.length === 1) {
             config = CollectionDockingView.makeDocumentConfig(dragDocs[0]);
@@ -499,7 +500,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 const stack = tab.contentItem.parent;
                 // shifts the focus to this tab when another tab is dragged over it
                 tab.element[0].onmouseenter = (e: any) => {
-                    if (!this._isPointerDown || !SelectionManager.GetIsDragging()) return;
+                    if (!this._isPointerDown || !DragManager.Vals.Instance.GetIsDragging()) return;
                     const activeContentItem = tab.header.parent.getActiveContentItem();
                     if (tab.contentItem !== activeContentItem) {
                         tab.header.parent.setActiveContentItem(tab.contentItem);
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 7ad15ef41..c74cfbcf4 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -132,7 +132,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
         this._color = color;
     }
 
-    pointerEnteredRow = action(() => SelectionManager.GetIsDragging() && (this._background = "#b4b4b4"));
+    pointerEnteredRow = action(() => DragManager.Vals.Instance.GetIsDragging() && (this._background = "#b4b4b4"));
 
     @action
     pointerLeaveRow = () => {
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 8b8cbc6e8..0a10c24b3 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -12,6 +12,7 @@ import React = require("react");
 import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
 import { SelectionManager } from "../../util/SelectionManager";
 import { UndoManager } from "../../util/UndoManager";
+import { DragManager } from "../../util/DragManager";
 
 @observer
 export class CollectionPileView extends CollectionSubView(doc => doc) {
@@ -78,7 +79,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
     _undoBatch: UndoManager.Batch | undefined;
     pointerDown = (e: React.PointerEvent) => {
         let dist = 0;
-        SelectionManager.SetIsDragging(true);
+        DragManager.Vals.Instance.SetIsDragging(true);
         // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header
         setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
             if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) {
@@ -98,7 +99,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
         }, () => {
             this._undoBatch?.end();
             this._undoBatch = undefined;
-            SelectionManager.SetIsDragging(false);
+            DragManager.Vals.Instance.SetIsDragging(false);
             if (!this.childDocs.length) {
                 this.props.ContainingCollectionView?.removeDocument(this.props.Document);
             }
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 82204ca7b..0e6489947 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -189,7 +189,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
                 this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
         };
         const onPointerEnter = (e: React.PointerEvent): void => {
-            if (e.buttons === 1 && SelectionManager.GetIsDragging() && (type === "document" || type === undefined)) {
+            if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging() && (type === "document" || type === undefined)) {
                 dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over";
             }
         };
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 972714e34..f9cd9a628 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -32,7 +32,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
     private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
 
     onPointerEnter = (e: React.PointerEvent): void => {
-        if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
+        if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging()) {
             this._header!.current!.className = "collectionSchema-col-wrapper";
             document.addEventListener("pointermove", this.onDragMove, true);
         }
@@ -143,7 +143,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
     private _rowDropDisposer?: DragManager.DragDropDisposer;
 
     onPointerEnter = (e: React.PointerEvent): void => {
-        if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
+        if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging()) {
             this._header!.current!.className = "collectionSchema-row-wrapper";
             document.addEventListener("pointermove", this.onDragMove, true);
         }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 1fd5c3f44..f6cdebc9b 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -312,7 +312,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
                     this.refList.push(ref);
                     const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
-                        if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) {
+                        if (this.props.Document._autoHeight && ref && this.refList.length && !DragManager.Vals.Instance.GetIsDragging()) {
                             Doc.Layout(doc)._height = Math.min(1200, Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
                         }
                     }));
@@ -359,7 +359,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
                     this.refList.push(ref);
                     const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
-                        if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) {
+                        if (this.props.Document._autoHeight && ref && this.refList.length && !DragManager.Vals.Instance.GetIsDragging()) {
                             Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
                         }
                     }));
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 8a9539eb0..1d16a5478 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -120,7 +120,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     @action
     pointerEntered = () => {
-        if (SelectionManager.GetIsDragging()) {
+        if (DragManager.Vals.Instance.GetIsDragging()) {
             this._background = "#b4b4b4";
         }
     }
@@ -355,7 +355,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             <div className="collectionStackingViewFieldColumn" key={heading}
                 style={{
                     width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`,
-                    height: undefined, // SelectionManager.GetIsDragging() ? "100%" : undefined,
+                    height: undefined, // DragManager.Vals.Instance.GetIsDragging() ? "100%" : undefined,
                     background: this._background
                 }}
                 ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 6d6f8d316..288fa8794 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -147,7 +147,7 @@ class TreeView extends React.Component<TreeViewProps> {
 
     onPointerEnter = (e: React.PointerEvent): void => {
         this.props.active(true) && Doc.BrushDoc(this.dataDoc);
-        if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
+        if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging()) {
             this._header!.current!.className = "treeViewItem-header";
             document.addEventListener("pointermove", this.onDragMove, true);
         }
@@ -451,7 +451,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     fontWeight: this.props.document.searchMatch ? "bold" : undefined,
                     textDecoration: Doc.GetT(this.props.document, "title", "string", true) ? "underline" : undefined,
                     outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
-                    pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? undefined : "none"
+                    pointerEvents: this.props.active() || DragManager.Vals.Instance.GetIsDragging() ? undefined : "none"
                 }} >
                 {Doc.GetT(this.props.document, "editTitle", "boolean", true) ?
                     this.editableView("title") :
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 7df1e909f..0873fd1bb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -10,6 +10,7 @@ import React = require("react");
 import { Utils, emptyFunction } from "../../../../Utils";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { DocumentType } from "../../../documents/DocumentTypes";
+import { DragManager } from "../../../util/DragManager";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component {
@@ -36,7 +37,7 @@ export class CollectionFreeFormLinksView extends React.Component {
     }
 
     render() {
-        return SelectionManager.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container">
+        return DragManager.Vals.Instance.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container">
             <svg className="collectionfreeformlinksview-svgCanvas">
                 {this.uniqueConnections}
             </svg>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 411fbd9e2..b2401e69e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1174,7 +1174,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         DragManager.SetSnapLines(horizLines, vertLines);
     }
     onPointerOver = (e: React.PointerEvent) => {
-        if (SelectionManager.GetIsDragging()) {
+        if (DragManager.Vals.Instance.GetIsDragging()) {
             this.setupDragLines();
         }
         e.stopPropagation();
@@ -1233,7 +1233,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale ? wscale : hscale;
     }
-    @computed get backgroundEvents() { return this.layoutDoc.isBackground && SelectionManager.GetIsDragging(); }
+    @computed get backgroundEvents() { return this.layoutDoc.isBackground && DragManager.Vals.Instance.GetIsDragging(); }
     render() {
         TraceMobx();
         const clientRect = this._mainCont?.getBoundingClientRect();
@@ -1247,7 +1247,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return <div className={"collectionfreeformview-container"}
             ref={this.createDashEventsTarget}
             onPointerOver={this.onPointerOver}
-            onWheel={this.onPointerWheel} onClick={this.onClick}  //pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+            onWheel={this.onPointerWheel} onClick={this.onClick}  //pointerEvents: DragManager.Vals.Instance.GetIsDragging() ? "all" : undefined,
             onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
             style={{
                 pointerEvents: this.backgroundEvents ? "all" : undefined,
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 928413a11..c97e1062d 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -9,7 +9,7 @@ import { DragManager, SetupDrag } from "../../util/DragManager";
 import { LinkManager } from "../../util/LinkManager";
 import { DocumentView } from "../nodes/DocumentView";
 import './LinkMenu.scss';
-import { LinkMenuItem } from "./LinkMenuItem";
+import { LinkMenuItem, StartLinkTargetsDrag } from "./LinkMenuItem";
 import React = require("react");
 
 interface LinkMenuGroupProps {
@@ -47,7 +47,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
             document.removeEventListener("pointerup", this.onLinkButtonUp);
 
             const targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[];
-            DragManager.StartLinkTargetsDrag(this._drag.current, this.props.docView, e.x, e.y, this.props.sourceDoc, targets);
+            StartLinkTargetsDrag(this._drag.current, this.props.docView, e.x, e.y, this.props.sourceDoc, targets);
         }
         e.stopPropagation();
     }
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d091e06ef..d4e58fa8e 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -3,7 +3,7 @@ import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes } from
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable } from 'mobx';
 import { observer } from "mobx-react";
-import { Doc } from '../../../new_fields/Doc';
+import { Doc, DocListCast } from '../../../new_fields/Doc';
 import { Cast, StrCast } from '../../../new_fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { LinkManager } from '../../util/LinkManager';
@@ -26,6 +26,41 @@ interface LinkMenuItemProps {
     addDocTab: (document: Doc, where: string) => boolean;
 }
 
+// drag links and drop link targets (aliasing them if needed)
+export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
+    const draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
+
+    if (draggedDocs.length) {
+        const moddrag: Doc[] = [];
+        for (const draggedDoc of draggedDocs) {
+            const doc = await Cast(draggedDoc.annotationOn, Doc);
+            if (doc) moddrag.push(doc);
+        }
+
+        const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
+        dragData.moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (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[]));
+
+        DragManager.StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag);
+    }
+}
+
+
 @observer
 export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     private _drag = React.createRef<HTMLDivElement>();
@@ -83,7 +118,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
             document.removeEventListener("pointerup", this.onLinkButtonUp);
 
             this._eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
-            DragManager.StartLinkTargetsDrag(this._eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
+            StartLinkTargetsDrag(this._eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
         }
         e.stopPropagation();
     }
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 3feb533a0..1c5e13620 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -17,10 +17,9 @@ import { ContextMenu } from "../ContextMenu";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { DocumentView } from "./DocumentView";
-import { Docs } from "../../documents/Documents";
+import { Docs, DocUtils } from "../../documents/Documents";
 import { ComputedField } from "../../../new_fields/ScriptField";
 import { Networking } from "../../Network";
-import { Upload } from "../../../server/SharedMediaTypes";
 import { LinkAnchorBox } from "./LinkAnchorBox";
 
 // testing testing 
@@ -57,7 +56,6 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
     @computed get audioState(): undefined | "recording" | "paused" | "playing" { return this.dataDoc.audioState as (undefined | "recording" | "paused" | "playing"); }
     set audioState(value) { this.dataDoc.audioState = value; }
     public static SetScrubTime = (timeInMillisFrom1970: number) => { runInAction(() => AudioBox._scrubTime = 0); runInAction(() => AudioBox._scrubTime = timeInMillisFrom1970); };
-    public static ActiveRecordings: Doc[] = [];
 
     @computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); }
     async slideTemplate() { return (await Cast((await Cast(Doc.UserDoc().slidesBtn, Doc) as Doc).dragFactory, Doc) as Doc); }
@@ -145,7 +143,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
         this._stream = await navigator.mediaDevices.getUserMedia({ audio: true });
         this._recorder = new MediaRecorder(this._stream);
         this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(new Date());
-        AudioBox.ActiveRecordings.push(this.props.Document);
+        DocUtils.ActiveRecordings.push(this.props.Document);
         this._recorder.ondataavailable = async (e: any) => {
             const [{ result }] = await Networking.UploadFilesToServer(e.data);
             if (!(result instanceof Error)) {
@@ -172,8 +170,8 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
         this.dataDoc.duration = (new Date().getTime() - this._recordStart) / 1000;
         this.audioState = "paused";
         this._stream?.getAudioTracks()[0].stop();
-        const ind = AudioBox.ActiveRecordings.indexOf(this.props.Document);
-        ind !== -1 && (AudioBox.ActiveRecordings.splice(ind, 1));
+        const ind = DocUtils.ActiveRecordings.indexOf(this.props.Document);
+        ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
     });
 
     recordClick = (e: React.MouseEvent) => {
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 3c2c6c87e..1c6250b94 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -3,7 +3,7 @@ import { computed } from "mobx";
 import { observer } from "mobx-react";
 import "react-table/react-table.css";
 import { Doc, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
-import { NumCast, StrCast } from "../../../new_fields/Types";
+import { NumCast, StrCast, Cast } from "../../../new_fields/Types";
 import { TraceMobx } from "../../../new_fields/util";
 import { emptyFunction, returnOne } from "../../../Utils";
 import '../DocumentDecorations.scss';
@@ -14,7 +14,11 @@ import "./ContentFittingDocumentView.scss";
 @observer
 export class ContentFittingDocumentView extends React.Component<DocumentViewProps>{
     public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
-    private get layoutDoc() { return this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document); }
+    private get layoutDoc() {
+        return this.props.LayoutTemplate?.() ||
+            (this.props.layoutKey && Doc.Layout(this.props.Document, Cast(this.props.Document[this.props.layoutKey], Doc, null))) ||
+            Doc.Layout(this.props.Document);
+    }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
     nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth()));
     nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight()));
diff --git a/src/client/views/nodes/DocumentBox.scss b/src/client/views/nodes/DocumentBox.scss
index ce21391ce..3a27c16c1 100644
--- a/src/client/views/nodes/DocumentBox.scss
+++ b/src/client/views/nodes/DocumentBox.scss
@@ -2,7 +2,8 @@
     width: 100%;
     height: 100%;
     pointer-events: all;
-    background: gray;
+    background: rgb(241, 239, 235);
+    position: absolute;
     .documentBox-lock {
         margin: auto;
         color: white;
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 8d422fe67..b53c7cfe6 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -1,23 +1,24 @@
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { IReactionDisposer, reaction, computed } from "mobx";
+import { action, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, Field } from "../../../new_fields/Doc";
-import { documentSchema, collectionSchema } from "../../../new_fields/documentSchemas";
+import { collectionSchema, documentSchema } from "../../../new_fields/documentSchemas";
 import { makeInterface } from "../../../new_fields/Schema";
 import { ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { TraceMobx } from "../../../new_fields/util";
 import { emptyPath, returnFalse, returnOne, returnZero } from "../../../Utils";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { DragManager } from "../../util/DragManager";
+import { undoBatch } from "../../util/UndoManager";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 import "./DocumentBox.scss";
+import { DocumentView } from "./DocumentView";
 import { FieldView, FieldViewProps } from "./FieldView";
 import React = require("react");
-import { TraceMobx } from "../../../new_fields/util";
-import { Docs } from "../../documents/Documents";
-import { KeyValueBox } from "./KeyValueBox";
-import { DocumentView } from "./DocumentView";
 
 type DocHolderBoxSchema = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
 const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema);
@@ -26,7 +27,9 @@ const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema);
 export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); }
     _prevSelectionDisposer: IReactionDisposer | undefined;
+    _dropDisposer?: DragManager.DragDropDisposer;
     _selections: Doc[] = [];
+    _contRef = React.createRef<HTMLDivElement>();
     _curSelection = -1;
     componentDidMount() {
         this._prevSelectionDisposer = reaction(() => this.layoutDoc[this.props.fieldKey], (data) => {
@@ -101,21 +104,15 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
             e.stopPropagation();
         }
     }
-    _contRef = React.createRef<HTMLDivElement>();
     pwidth = () => this.props.PanelWidth() - 2 * this.xPad;
     pheight = () => this.props.PanelHeight() - 2 * this.yPad;
     getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad);
     isActive = () => this.active() || !this.props.renderDepth;
+    layoutTemplateDoc = () => Cast(this.props.Document.childLayoutTemplate, Doc, null);
     get renderContents() {
         const containedDoc = Cast(this.dataDoc[this.props.fieldKey], Doc, null);
-        const childTemplateName = StrCast(this.layoutDoc.childTemplateName);
-        if (containedDoc && childTemplateName && !containedDoc["layout_" + childTemplateName]) {
-            setTimeout(() => {
-                Doc.createCustomView(containedDoc, Docs.Create.StackingDocument, childTemplateName);
-                Doc.expandTemplateLayout(Cast(containedDoc["layout_" + childTemplateName], Doc, null), containedDoc, undefined);
-            }, 0);
-        }
-        const contents = !(containedDoc instanceof Doc) ? (null) : this.layoutDoc.childLayoutString ?
+        const layoutTemplate = StrCast(this.layoutDoc.childLayoutString);
+        const contents = !(containedDoc instanceof Doc) ? (null) : this.layoutDoc.childLayoutString || this.layoutTemplateDoc() ?
             <DocumentView
                 Document={containedDoc}
                 DataDoc={undefined}
@@ -123,8 +120,8 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
                 ContainingCollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
                 ContainingCollectionDoc={undefined}
                 fitToBox={true}
-                LayoutTemplateString={StrCast(this.layoutDoc.childLayoutString)}
-                //layoutKey={childTemplateName ? "layout_" + childTemplateName : "layout"}
+                LayoutTemplateString={layoutTemplate}
+                LayoutTemplate={this.layoutTemplateDoc}
                 rootSelected={this.props.isSelected}
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
@@ -139,7 +136,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
                 PanelHeight={this.pheight}
                 focus={this.props.focus}
                 parentActive={this.isActive}
-                dontRegisterView={!this.isSelectionLocked()}
+                dontRegisterView={true}
                 whenActiveChanged={this.props.whenActiveChanged}
                 bringToFront={returnFalse}
                 ContentScaling={returnOne} /> :
@@ -150,8 +147,8 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
                 ContainingCollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
                 ContainingCollectionDoc={undefined}
                 fitToBox={true}
-                LayoutTemplateString={StrCast(this.layoutDoc.childLayoutString)}
-                //layoutKey={childTemplateName ? "layout_" + childTemplateName : "layout"}
+                LayoutTemplateString={layoutTemplate}
+                LayoutTemplate={this.layoutTemplateDoc}
                 rootSelected={this.props.isSelected}
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
@@ -166,7 +163,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
                 PanelHeight={this.pheight}
                 focus={this.props.focus}
                 parentActive={this.isActive}
-                dontRegisterView={!this.isSelectionLocked()}
+                dontRegisterView={true}
                 whenActiveChanged={this.props.whenActiveChanged}
                 bringToFront={returnFalse}
                 ContentScaling={returnOne}
@@ -185,10 +182,26 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
                 borderBottom: `#0000005e solid ${this.yPad}px`,
             }}>
             {this.renderContents}
-            <div className="documentBox-lock" onClick={this.onLockClick}
+            <div className="documentBox-lock" onClick={this.onLockClick} ref={this.createDropTarget}
                 style={{ marginTop: - this.yPad }}>
                 <FontAwesomeIcon icon={this.isSelectionLocked() ? "lock" : "unlock"} size="sm" />
             </div>
         </div >;
     }
+
+    @undoBatch
+    @action
+    drop = (e: Event, de: DragManager.DropEvent) => {
+        if (de.complete.docDragData) {
+            if (de.complete.docDragData.draggedDocuments[0].type === DocumentType.FONTICON) {
+                const doc = Cast(de.complete.docDragData.draggedDocuments[0].dragFactory, Doc, null);
+                this.props.Document.childLayoutTemplate = doc;
+            }
+        }
+    }
+    protected createDropTarget = (ele: HTMLDivElement) => {
+        this._dropDisposer?.();
+        ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
+    }
+
 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7a16e8836..c4cd5978a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1102,7 +1102,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
     @computed get ignorePointerEvents() {
         return this.props.pointerEvents === false ||
-            (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) ||
+            (this.Document.isBackground && !this.isSelected() && !DragManager.Vals.Instance.GetIsDragging()) ||
             (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
     }
     @undoBatch
diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx
index 76885eada..d248b098c 100644
--- a/src/client/views/nodes/QueryBox.tsx
+++ b/src/client/views/nodes/QueryBox.tsx
@@ -11,6 +11,7 @@ import { SearchBox } from "../search/SearchBox";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./QueryBox.scss";
 import { List } from "../../../new_fields/List";
+import { DragManager } from "../../util/DragManager";
 
 type QueryDocument = makeInterface<[typeof documentSchema]>;
 const QueryDocument = makeInterface(documentSchema);
@@ -27,7 +28,7 @@ export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryD
     }
 
     render() {
-        const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
+        const dragging = !DragManager.Vals.Instance.GetIsDragging() ? "" : "-dragging";
         return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} >
             <SearchBox
                 id={this.props.Document[Id]}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 30c4a45ac..658a55f51 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -198,15 +198,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const tsel = this._editorView.state.selection.$from;
             tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
             const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
-            const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField);
-            const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null);
+            const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField);               // the actual text in the text box
+            const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null);              // the default text inherited from a prototype
+            const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
             const json = JSON.stringify(state.toJSON());
             if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
                 this._applyingChange = true;
                 this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
-                if ((!curTemp && !curProto) || curText) { // if no template, or there's text, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
-                    this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
-                    this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+                if ((!curTemp && !curProto) || curText) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
+                    if (curText !== curLayout?.Text) {
+                        this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
+                        this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+                    }
                 } else { // if we've deleted all the text in a note driven by a template, then restore the template data
                     this.dataDoc[this.props.fieldKey] = undefined;
                     this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index b63a3dbbf..bf99f449f 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -217,28 +217,29 @@ export class CurrentUserUtils {
     static setupDefaultIconTemplates(doc: Doc) {
         if (doc["template-icon-view"] === undefined) {
             const iconView = Docs.Create.TextDocument("", {
-                title: "icon", _width: 150, _height: 30, isTemplateDoc: true,
-                onClick: ScriptField.MakeScript("deiconifyView(self)")
+                title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
             });
             Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', "");
             iconView.isTemplateDoc = makeTemplate(iconView);
             doc["template-icon-view"] = new PrefetchProxy(iconView);
         }
         if (doc["template-icon-view-rtf"] === undefined) {
-            const iconRtfView = Docs.Create.LabelDocument({ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(self)") });
+            const iconRtfView = Docs.Create.LabelDocument({
+                title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset",
+                _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
+            });
             iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
             doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
         }
         if (doc["template-icon-view-img"] === undefined) {
             const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", {
-                title: "data", _width: 50, isTemplateDoc: true,
-                onClick: ScriptField.MakeScript("deiconifyView(self)")
+                title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
             });
             iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG);
             doc["template-icon-view-img"] = new PrefetchProxy(iconImageView);
         }
         if (doc["template-icon-view-col"] === undefined) {
-            const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onClick: ScriptField.MakeScript("deiconifyView(self)") });
+            const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") });
             iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL);
             doc["template-icon-view-col"] = new PrefetchProxy(iconColView);
         }
@@ -265,9 +266,17 @@ export class CurrentUserUtils {
             doc.emptyCollection = Docs.Create.FreeformDocument([],
                 { _nativeWidth: undefined, _nativeHeight: undefined, _LODdisable: true, _width: 150, _height: 100, title: "freeform" });
         }
+        if (doc.emptyDocHolder === undefined) {
+            doc.emptyDocHolder = Docs.Create.DocumentDocument(
+                ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]") as any,
+                { _width: 250, _height: 250, title: "container" });
+        }
+        if (doc.emptyWebpage === undefined) {
+            doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600 })
+        }
         return [
             { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
-            { title: "Drag a web page", label: "Web", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("", { title: "New Webpage" })' },
+            { title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc },
             { title: "Drag a cat image", label: "Img", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' },
             { title: "Drag a screenshot", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
             { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
@@ -284,7 +293,7 @@ export class CurrentUserUtils {
             // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
             // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc },
             // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc },
-            { title: "Drag a document previewer", label: "Prev", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]"), { _width: 250, _height: 250, title: "container" })' },
+            { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc },
             { title: "Drag a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
         ];
 
@@ -308,7 +317,7 @@ export class CurrentUserUtils {
                 title: data.title,
                 label: data.label,
                 ignoreClick: data.ignoreClick,
-                dropAction: data.click ? "copy" : undefined,
+                dropAction: "copy",
                 onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
                 onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
                 ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined,
-- 
cgit v1.2.3-70-g09d2


From d9227908ba8e8db5084c468a242b2839ab11a33d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 4 May 2020 12:50:46 -0400
Subject: fixed links and snap lines broken by moving things into DragManager.

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/util/DragManager.ts                     |  34 ++--
 src/client/util/SelectionManager.ts                |   3 +
 src/client/util/SnappingManager.ts                 |  29 +++
 src/client/views/DocumentDecorations.tsx           |   5 +-
 src/client/views/MainView.scss                     |   9 +
 src/client/views/MainView.tsx                      |  18 +-
 .../views/collections/CollectionDockingView.tsx    |   5 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |   4 +-
 .../views/collections/CollectionPileView.tsx       |   6 +-
 .../views/collections/CollectionSchemaCells.tsx    |   4 +-
 .../CollectionSchemaMovableTableHOC.tsx            |   6 +-
 .../views/collections/CollectionStackingView.tsx   |   7 +-
 .../CollectionStackingViewFieldColumn.tsx          |   6 +-
 .../views/collections/CollectionTreeView.tsx       |   7 +-
 .../CollectionFreeFormLinkView.tsx                 |   9 +-
 .../CollectionFreeFormLinksView.tsx                |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   7 +-
 src/client/views/nodes/DocHolderBox.scss           |  15 ++
 src/client/views/nodes/DocHolderBox.tsx            | 207 +++++++++++++++++++++
 src/client/views/nodes/DocumentBox.scss            |  15 --
 src/client/views/nodes/DocumentBox.tsx             | 207 ---------------------
 src/client/views/nodes/DocumentContentsView.tsx    |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   3 +-
 src/client/views/nodes/QueryBox.tsx                |   5 +-
 25 files changed, 328 insertions(+), 291 deletions(-)
 create mode 100644 src/client/util/SnappingManager.ts
 create mode 100644 src/client/views/nodes/DocHolderBox.scss
 create mode 100644 src/client/views/nodes/DocHolderBox.tsx
 delete mode 100644 src/client/views/nodes/DocumentBox.scss
 delete mode 100644 src/client/views/nodes/DocumentBox.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 45687f597..f5d6cd7f6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -37,7 +37,7 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { QueryBox } from "../views/nodes/QueryBox";
 import { ColorBox } from "../views/nodes/ColorBox";
 import { LinkAnchorBox } from "../views/nodes/LinkAnchorBox";
-import { DocHolderBox } from "../views/nodes/DocumentBox";
+import { DocHolderBox } from "../views/nodes/DocHolderBox";
 import { InkingStroke } from "../views/InkingStroke";
 import { InkField } from "../../new_fields/InkField";
 import { InkingControl } from "../views/InkingControl";
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 348aba588..0d208cf1b 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -11,6 +11,7 @@ import { emptyFunction } from "../../Utils";
 import { Docs, DocUtils } from "../documents/Documents";
 import * as globalCssVariables from "../views/globalCssVariables.scss";
 import { UndoManager } from "./UndoManager";
+import { SnappingManager } from "./SnappingManager";
 
 export type dropActionType = "alias" | "copy" | "move" | undefined; // undefined = move
 export function SetupDrag(
@@ -49,7 +50,7 @@ export function SetupDrag(
             if (e.shiftKey) {
                 e.persist();
                 const dragDoc = await docFunc();
-                dragDoc && DragManager.Vals.Instance.StartWindowDrag?.({
+                dragDoc && DragManager.StartWindowDrag?.({
                     pageX: e.pageX,
                     pageY: e.pageY,
                     preventDefault: emptyFunction,
@@ -66,19 +67,7 @@ export function SetupDrag(
 
 export namespace DragManager {
     let dragDiv: HTMLDivElement;
-    export class Vals {
-        static Instance: Vals = new Vals();
-        @observable public IsDragging: boolean = false;
-        @observable public horizSnapLines: number[] = [];
-        @observable public vertSnapLines: number[] = [];
-        public StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined;
-        public SetIsDragging(dragging: boolean) { runInAction(() => this.IsDragging = dragging); }
-        public GetIsDragging() { return this.IsDragging; }
-        @action public clearSnapLines() {
-            this.vertSnapLines.length = 0;
-            this.horizSnapLines.length = 0;
-        }
-    }
+    export let StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined;
 
     export function Root() {
         const root = document.getElementById("root");
@@ -267,8 +256,7 @@ export namespace DragManager {
     }
 
     export function SetSnapLines(horizLines: number[], vertLines: number[]) {
-        DragManager.Vals.Instance.horizSnapLines.push(...horizLines);
-        DragManager.Vals.Instance.vertSnapLines.push(...vertLines);
+        SnappingManager.setSnapLines(horizLines, vertLines);
     }
 
     export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) {
@@ -286,8 +274,8 @@ export namespace DragManager {
         };
 
         return {
-            thisX: snapVal([xFromLeft, xFromRight], e.pageX, DragManager.Vals.Instance.vertSnapLines),
-            thisY: snapVal([yFromTop, yFromBottom], e.pageY, DragManager.Vals.Instance.horizSnapLines)
+            thisX: snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines()),
+            thisY: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines())
         };
     }
     export let docsBeingDragged: Doc[] = [];
@@ -299,7 +287,7 @@ export namespace DragManager {
             dragDiv.style.pointerEvents = "none";
             DragManager.Root().appendChild(dragDiv);
         }
-        DragManager.Vals.Instance.SetIsDragging(true);
+        SnappingManager.SetIsDragging(true);
         const scaleXs: number[] = [];
         const scaleYs: number[] = [];
         const xs: number[] = [];
@@ -388,7 +376,7 @@ export namespace DragManager {
                 }
                 AbortDrag();
                 finishDrag?.(new DragCompleteEvent(true, dragData));
-                DragManager.Vals.Instance.StartWindowDrag?.({
+                DragManager.StartWindowDrag?.({
                     pageX: e.pageX,
                     pageY: e.pageY,
                     preventDefault: emptyFunction,
@@ -415,19 +403,19 @@ export namespace DragManager {
         const endDrag = action(() => {
             document.removeEventListener("pointermove", moveHandler, true);
             document.removeEventListener("pointerup", upHandler);
-            DragManager.Vals.Instance.clearSnapLines();
+            SnappingManager.clearSnapLines();
         });
 
         AbortDrag = () => {
             hideDragShowOriginalElements();
-            DragManager.Vals.Instance.SetIsDragging(false);
+            SnappingManager.SetIsDragging(false);
             options?.dragComplete?.(new DragCompleteEvent(true, dragData));
             endDrag();
         };
         const upHandler = (e: PointerEvent) => {
             hideDragShowOriginalElements();
             dispatchDrag(eles, e, dragData, xFromLeft, yFromTop, xFromRight, yFromBottom, options, finishDrag);
-            DragManager.Vals.Instance.SetIsDragging(false);
+            SnappingManager.SetIsDragging(false);
             endDrag();
             options?.dragComplete?.(new DragCompleteEvent(false, dragData));
         };
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index d509168b6..11d2cafb2 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -8,6 +8,7 @@ export namespace SelectionManager {
 
     class Manager {
 
+        @observable IsDragging: boolean = false;
         SelectedDocuments: ObservableMap<DocumentView, boolean> = new ObservableMap();
 
         @action
@@ -53,6 +54,8 @@ export namespace SelectionManager {
         manager.SelectDoc(docView, ctrlPressed);
     }
 
+    export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); }
+    export function GetIsDragging() { return manager.IsDragging; }
     // computed functions, such as used in IsSelected generate errors if they're called outside of a
     // reaction context.  Specifying the context with 'outsideReaction' allows an efficiency feature
     // to avoid unnecessary mobx invalidations when running inside a reaction.
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
new file mode 100644
index 000000000..fc07e8ab4
--- /dev/null
+++ b/src/client/util/SnappingManager.ts
@@ -0,0 +1,29 @@
+import { observable, action, runInAction } from "mobx";
+
+export namespace SnappingManager {
+
+    class Manager {
+        @observable IsDragging: boolean = false;
+        @observable public horizSnapLines: number[] = [];
+        @observable public vertSnapLines: number[] = [];
+        @action public clearSnapLines() {
+            this.vertSnapLines = [];
+            this.horizSnapLines = [];
+        }
+        @action public setSnapLines(horizLines: number[], vertLines: number[]) {
+            this.horizSnapLines = horizLines;
+            this.vertSnapLines = vertLines;
+        }
+    }
+
+    const manager = new Manager();
+
+    export function clearSnapLines() { manager.clearSnapLines(); }
+    export function setSnapLines(horizLines: number[], vertLines: number[]) { manager.setSnapLines(horizLines, vertLines); }
+    export function horizSnapLines() { return manager.horizSnapLines; }
+    export function vertSnapLines() { return manager.vertSnapLines; }
+
+    export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); }
+    export function GetIsDragging() { return manager.IsDragging; }
+}
+
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index f8e77d90a..b89806656 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -21,6 +21,7 @@ import { Id } from '../../new_fields/FieldSymbols';
 import e = require('express');
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { MainView } from './MainView';
+import { SnappingManager } from '../util/SnappingManager';
 
 library.add(faCaretUp);
 library.add(faObjectGroup);
@@ -364,7 +365,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         this.Interacting = false;
         (e.button === 0) && this._resizeUndo?.end();
         this._resizeUndo = undefined;
-        DragManager.Vals.Instance.clearSnapLines();
+        SnappingManager.clearSnapLines();
     }
 
     @computed
@@ -403,7 +404,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         const darkScheme = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimgray" : undefined;
         const bounds = this.Bounds;
         const seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
-        if (DragManager.Vals.Instance.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
+        if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
             return (null);
         }
         const minimal = bounds.r - bounds.x < 100 ? true : false;
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 04288a9e1..753ba700c 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -22,6 +22,15 @@
     z-index: 1;
 }
 
+.mainView-snapLines {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    pointer-events:none;
+}
+
 .mainView-container, .mainView-container-dark {
     input {
         color: unset !important;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 71c2bf245..9ebd60c52 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -44,6 +44,7 @@ import { PreviewCursor } from './PreviewCursor';
 import { ScriptField } from '../../new_fields/ScriptField';
 import { TimelineMenu } from './animationtimeline/TimelineMenu';
 import { DragManager } from '../util/DragManager';
+import { SnappingManager } from '../util/SnappingManager';
 
 @observer
 export class MainView extends React.Component {
@@ -575,6 +576,15 @@ export class MainView extends React.Component {
         return this._mainViewRef;
     }
 
+    @computed get snapLines() {
+        return <div className="mainView-snapLines">
+            <svg style={{ width: "100%", height: "100%" }}>
+                {SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+                {SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+            </svg>
+        </div>
+    }
+
     render() {
         return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")} ref={this._mainViewRef}>
             <DictationOverlay />
@@ -592,14 +602,8 @@ export class MainView extends React.Component {
             <MarqueeOptionsMenu />
             <RichTextMenu />
             <OverlayView />
-            {// TO VIEW SNAP LINES
-                <div className="snapLines" style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", pointerEvents: "none" }}>
-                    <svg style={{ width: "100%", height: "100%" }}>
-                        {DragManager.Vals.Instance.horizSnapLines.map((l: any) => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
-                        {DragManager.Vals.Instance.vertSnapLines.map((l: any) => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
-                    </svg>
-                </div>}
             <TimelineMenu />
+            {this.snapLines}
         </div >);
     }
 }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 33ece13cc..6cd5d1b1b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -30,6 +30,7 @@ import { SubCollectionViewProps } from "./CollectionSubView";
 import { DockingViewButtonSelector } from './ParentDocumentSelector';
 import React = require("react");
 import { CollectionViewType } from './CollectionView';
+import { SnappingManager } from '../../util/SnappingManager';
 library.add(faFile);
 const _global = (window /* browser */ || global /* node */) as any;
 
@@ -68,7 +69,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         //Why is this here?
         (window as any).React = React;
         (window as any).ReactDOM = ReactDOM;
-        DragManager.Vals.Instance.StartWindowDrag = this.StartOtherDrag;
+        DragManager.StartWindowDrag = this.StartOtherDrag;
     }
     hack: boolean = false;
     undohack: any = null;
@@ -500,7 +501,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 const stack = tab.contentItem.parent;
                 // shifts the focus to this tab when another tab is dragged over it
                 tab.element[0].onmouseenter = (e: any) => {
-                    if (!this._isPointerDown || !DragManager.Vals.Instance.GetIsDragging()) return;
+                    if (!this._isPointerDown || !SnappingManager.GetIsDragging()) return;
                     const activeContentItem = tab.header.parent.getActiveContentItem();
                     if (tab.contentItem !== activeContentItem) {
                         tab.header.parent.setActiveContentItem(tab.contentItem);
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index c74cfbcf4..95c7643c9 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -12,12 +12,12 @@ import { numberRange, setupMoveUpEvents, emptyFunction } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { CompileScript } from "../../util/Scripting";
-import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
 import { EditableView } from "../EditableView";
 import { CollectionStackingView } from "./CollectionStackingView";
 import "./CollectionStackingView.scss";
+import { SnappingManager } from "../../util/SnappingManager";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -132,7 +132,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
         this._color = color;
     }
 
-    pointerEnteredRow = action(() => DragManager.Vals.Instance.GetIsDragging() && (this._background = "#b4b4b4"));
+    pointerEnteredRow = action(() => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4"));
 
     @action
     pointerLeaveRow = () => {
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 0a10c24b3..d3ae21f3a 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -12,7 +12,7 @@ import React = require("react");
 import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
 import { SelectionManager } from "../../util/SelectionManager";
 import { UndoManager } from "../../util/UndoManager";
-import { DragManager } from "../../util/DragManager";
+import { SnappingManager } from "../../util/SnappingManager";
 
 @observer
 export class CollectionPileView extends CollectionSubView(doc => doc) {
@@ -79,7 +79,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
     _undoBatch: UndoManager.Batch | undefined;
     pointerDown = (e: React.PointerEvent) => {
         let dist = 0;
-        DragManager.Vals.Instance.SetIsDragging(true);
+        SnappingManager.SetIsDragging(true);
         // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header
         setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
             if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) {
@@ -99,7 +99,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
         }, () => {
             this._undoBatch?.end();
             this._undoBatch = undefined;
-            DragManager.Vals.Instance.SetIsDragging(false);
+            SnappingManager.SetIsDragging(false);
             if (!this.childDocs.length) {
                 this.props.ContainingCollectionView?.removeDocument(this.props.Document);
             }
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 0e6489947..5253ee0b9 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -18,11 +18,11 @@ import "./CollectionSchemaView.scss";
 import { CollectionView } from "./CollectionView";
 import { NumCast, StrCast, BoolCast, FieldValue, Cast } from "../../../new_fields/Types";
 import { Docs } from "../../documents/Documents";
-import { SelectionManager } from "../../util/SelectionManager";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faExpand } from '@fortawesome/free-solid-svg-icons';
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { undoBatch } from "../../util/UndoManager";
+import { SnappingManager } from "../../util/SnappingManager";
 
 library.add(faExpand);
 
@@ -189,7 +189,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
                 this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
         };
         const onPointerEnter = (e: React.PointerEvent): void => {
-            if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging() && (type === "document" || type === undefined)) {
+            if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === "document" || type === undefined)) {
                 dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over";
             }
         };
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index f9cd9a628..8636e3eb5 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -4,7 +4,6 @@ import "./CollectionSchemaView.scss";
 import { Transform } from "../../util/Transform";
 import { Doc } from "../../../new_fields/Doc";
 import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
-import { SelectionManager } from "../../util/SelectionManager";
 import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { ContextMenu } from "../ContextMenu";
 import { action } from "mobx";
@@ -14,6 +13,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { DocumentManager } from "../../util/DocumentManager";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { undoBatch } from "../../util/UndoManager";
+import { SnappingManager } from "../../util/SnappingManager";
 
 library.add(faGripVertical, faTrash);
 
@@ -32,7 +32,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
     private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
 
     onPointerEnter = (e: React.PointerEvent): void => {
-        if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging()) {
+        if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
             this._header!.current!.className = "collectionSchema-col-wrapper";
             document.addEventListener("pointermove", this.onDragMove, true);
         }
@@ -143,7 +143,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
     private _rowDropDisposer?: DragManager.DragDropDisposer;
 
     onPointerEnter = (e: React.PointerEvent): void => {
-        if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging()) {
+        if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
             this._header!.current!.className = "collectionSchema-row-wrapper";
             document.addEventListener("pointermove", this.onDragMove, true);
         }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index f6cdebc9b..b6faea845 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -14,7 +14,6 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_field
 import { TraceMobx } from "../../../new_fields/util";
 import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from "../../../Utils";
 import { DragManager, dropActionType } from "../../util/DragManager";
-import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
 import { ContextMenu } from "../ContextMenu";
@@ -26,7 +25,7 @@ import "./CollectionStackingView.scss";
 import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
 import { CollectionSubView } from "./CollectionSubView";
 import { CollectionViewType } from "./CollectionView";
-import { ScriptField } from "../../../new_fields/ScriptField";
+import { SnappingManager } from "../../util/SnappingManager";
 const _global = (window /* browser */ || global /* node */) as any;
 
 type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>;
@@ -312,7 +311,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
                     this.refList.push(ref);
                     const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
-                        if (this.props.Document._autoHeight && ref && this.refList.length && !DragManager.Vals.Instance.GetIsDragging()) {
+                        if (this.props.Document._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
                             Doc.Layout(doc)._height = Math.min(1200, Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
                         }
                     }));
@@ -359,7 +358,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
                     this.refList.push(ref);
                     const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
-                        if (this.props.Document._autoHeight && ref && this.refList.length && !DragManager.Vals.Instance.GetIsDragging()) {
+                        if (this.props.Document._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
                             Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
                         }
                     }));
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 1d16a5478..dccef7983 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -13,7 +13,6 @@ import { ImageField } from "../../../new_fields/URLField";
 import { TraceMobx } from "../../../new_fields/util";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
-import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
 import { ContextMenu } from "../ContextMenu";
@@ -23,6 +22,7 @@ import { CollectionStackingView } from "./CollectionStackingView";
 import { setupMoveUpEvents, emptyFunction } from "../../../Utils";
 import "./CollectionStackingView.scss";
 import { listSpec } from "../../../new_fields/Schema";
+import { SnappingManager } from "../../util/SnappingManager";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -120,7 +120,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     @action
     pointerEntered = () => {
-        if (DragManager.Vals.Instance.GetIsDragging()) {
+        if (SnappingManager.GetIsDragging()) {
             this._background = "#b4b4b4";
         }
     }
@@ -355,7 +355,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             <div className="collectionStackingViewFieldColumn" key={heading}
                 style={{
                     width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`,
-                    height: undefined, // DragManager.Vals.Instance.GetIsDragging() ? "100%" : undefined,
+                    height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined,
                     background: this._background
                 }}
                 ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 288fa8794..f2b0e3155 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -6,6 +6,7 @@ import { observer } from "mobx-react";
 import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { List } from '../../../new_fields/List';
+import { PrefetchProxy } from '../../../new_fields/Proxy';
 import { Document, listSpec } from '../../../new_fields/Schema';
 import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types';
@@ -13,6 +14,7 @@ import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZer
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from "../../documents/DocumentTypes";
 import { DocumentManager } from '../../util/DocumentManager';
+import { SnappingManager } from '../../util/SnappingManager';
 import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
 import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from '../../util/SelectionManager';
@@ -31,7 +33,6 @@ import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import { CollectionViewType } from './CollectionView';
 import React = require("react");
-import { PrefetchProxy } from '../../../new_fields/Proxy';
 
 
 export interface TreeViewProps {
@@ -147,7 +148,7 @@ class TreeView extends React.Component<TreeViewProps> {
 
     onPointerEnter = (e: React.PointerEvent): void => {
         this.props.active(true) && Doc.BrushDoc(this.dataDoc);
-        if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging()) {
+        if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
             this._header!.current!.className = "treeViewItem-header";
             document.addEventListener("pointermove", this.onDragMove, true);
         }
@@ -451,7 +452,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     fontWeight: this.props.document.searchMatch ? "bold" : undefined,
                     textDecoration: Doc.GetT(this.props.document, "title", "string", true) ? "underline" : undefined,
                     outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
-                    pointerEvents: this.props.active() || DragManager.Vals.Instance.GetIsDragging() ? undefined : "none"
+                    pointerEvents: this.props.active() || SnappingManager.GetIsDragging() ? undefined : "none"
                 }} >
                 {Doc.GetT(this.props.document, "editTitle", "boolean", true) ?
                     this.editableView("title") :
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 0c9a1aa9a..695898ca9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -9,7 +9,7 @@ import { DocumentType } from "../../../documents/DocumentTypes";
 import { observable, action, reaction, IReactionDisposer } from "mobx";
 import { StrCast } from "../../../../new_fields/Types";
 import { Id } from "../../../../new_fields/FieldSymbols";
-import { DragManager } from "../../../util/DragManager";
+import { SnappingManager } from "../../../util/SnappingManager";
 
 export interface CollectionFreeFormLinkViewProps {
     A: DocumentView;
@@ -25,7 +25,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
     componentDidMount() {
         this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)],
             action(() => {
-                if (DragManager.Vals.Instance.GetIsDragging()) return;
+                if (SnappingManager.GetIsDragging()) return;
                 setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
                 setTimeout(action(() => (!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
                 const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
@@ -83,7 +83,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
     }
 
     render() {
-        if (DragManager.Vals.Instance.GetIsDragging()) return null;
+        if (SnappingManager.GetIsDragging()) return null;
         this.props.A.props.ScreenToLocalTransform().transform(this.props.B.props.ScreenToLocalTransform());
         const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
         const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
@@ -107,8 +107,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         const aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
         const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
         const text = StrCast(this.props.A.props.Document.linkRelationship);
+
         return !a.width || !b.width || ((!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
-            <text x={(pt1[0] + pt2[0]) / 2} y={(pt1[1] + pt2[1]) / 2}>
+            <text x={(Math.min(pt1[0], pt2[0]) * 2 + Math.max(pt1[0], pt2[0])) / 3} y={(pt1[1] + pt2[1]) / 2}>
                 {text !== "-ungrouped-" ? text : ""}
             </text>
             <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 6c6dbf653..5e5ad7db5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -9,7 +9,7 @@ import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
 import React = require("react");
 import { Utils, emptyFunction } from "../../../../Utils";
 import { DocumentType } from "../../../documents/DocumentTypes";
-import { DragManager } from "../../../util/DragManager";
+import { SnappingManager } from "../../../util/SnappingManager";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component {
@@ -36,7 +36,7 @@ export class CollectionFreeFormLinksView extends React.Component {
     }
 
     render() {
-        return DragManager.Vals.Instance.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container">
+        return SnappingManager.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container">
             <svg className="collectionfreeformlinksview-svgCanvas">
                 {this.uniqueConnections}
             </svg>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2aa9b1f5f..37957b0f6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -45,6 +45,7 @@ import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import { CollectionViewType } from "../CollectionView";
 import { Timeline } from "../../animationtimeline/Timeline";
+import { SnappingManager } from "../../../util/SnappingManager";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -1174,7 +1175,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         DragManager.SetSnapLines(horizLines, vertLines);
     }
     onPointerOver = (e: React.PointerEvent) => {
-        if (DragManager.Vals.Instance.GetIsDragging()) {
+        if (SnappingManager.GetIsDragging()) {
             this.setupDragLines();
         }
         e.stopPropagation();
@@ -1233,7 +1234,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale ? wscale : hscale;
     }
-    @computed get backgroundEvents() { return this.layoutDoc.isBackground && DragManager.Vals.Instance.GetIsDragging(); }
+    @computed get backgroundEvents() { return this.layoutDoc.isBackground && SnappingManager.GetIsDragging(); }
     render() {
         TraceMobx();
         const clientRect = this._mainCont?.getBoundingClientRect();
@@ -1247,7 +1248,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return <div className={"collectionfreeformview-container"}
             ref={this.createDashEventsTarget}
             onPointerOver={this.onPointerOver}
-            onWheel={this.onPointerWheel} onClick={this.onClick}  //pointerEvents: DragManager.Vals.Instance.GetIsDragging() ? "all" : undefined,
+            onWheel={this.onPointerWheel} onClick={this.onClick}  //pointerEvents: DraggingManager.GetIsDragging() ? "all" : undefined,
             onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
             style={{
                 pointerEvents: this.backgroundEvents ? "all" : undefined,
diff --git a/src/client/views/nodes/DocHolderBox.scss b/src/client/views/nodes/DocHolderBox.scss
new file mode 100644
index 000000000..3a27c16c1
--- /dev/null
+++ b/src/client/views/nodes/DocHolderBox.scss
@@ -0,0 +1,15 @@
+.documentBox-container {
+    width: 100%;
+    height: 100%;
+    pointer-events: all;
+    background: rgb(241, 239, 235);
+    position: absolute;
+    .documentBox-lock {
+        margin: auto;
+        color: white;
+        position: absolute;
+    }
+    .contentFittingDocumentView {
+        position: absolute;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx
new file mode 100644
index 000000000..9787877cc
--- /dev/null
+++ b/src/client/views/nodes/DocHolderBox.tsx
@@ -0,0 +1,207 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, IReactionDisposer, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, Field } from "../../../new_fields/Doc";
+import { collectionSchema, documentSchema } from "../../../new_fields/documentSchemas";
+import { makeInterface } from "../../../new_fields/Schema";
+import { ComputedField } from "../../../new_fields/ScriptField";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { TraceMobx } from "../../../new_fields/util";
+import { emptyPath, returnFalse, returnOne, returnZero } from "../../../Utils";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { DragManager } from "../../util/DragManager";
+import { undoBatch } from "../../util/UndoManager";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
+import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
+import "./DocHolderBox.scss";
+import { DocumentView } from "./DocumentView";
+import { FieldView, FieldViewProps } from "./FieldView";
+import React = require("react");
+
+type DocHolderBoxSchema = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
+const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema);
+
+@observer
+export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); }
+    _prevSelectionDisposer: IReactionDisposer | undefined;
+    _dropDisposer?: DragManager.DragDropDisposer;
+    _selections: Doc[] = [];
+    _contRef = React.createRef<HTMLDivElement>();
+    _curSelection = -1;
+    componentDidMount() {
+        this._prevSelectionDisposer = reaction(() => this.layoutDoc[this.props.fieldKey], (data) => {
+            if (data instanceof Doc && !this.isSelectionLocked()) {
+                this._selections.indexOf(data) !== -1 && this._selections.splice(this._selections.indexOf(data), 1);
+                this._selections.push(data);
+                this._curSelection = this._selections.length - 1;
+            }
+        });
+    }
+    componentWillUnmount() {
+        this._prevSelectionDisposer?.();
+    }
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const funcs: ContextMenuProps[] = [];
+        funcs.push({ description: (this.isSelectionLocked() ? "Show" : "Lock") + " Selection", event: () => this.toggleLockSelection, icon: "expand-arrows-alt" });
+        funcs.push({ description: (this.layoutDoc.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => this.layoutDoc.excludeCollections = !this.layoutDoc.excludeCollections, icon: "expand-arrows-alt" });
+        funcs.push({ description: `${this.layoutDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.layoutDoc.forceActive = !this.layoutDoc.forceActive, icon: "project-diagram" });
+        funcs.push({ description: `Show ${this.layoutDoc.childLayoutTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childLayoutString = this.layoutDoc.childLayoutString ? undefined : "<KeyValueBox {...props} />", icon: "project-diagram" });
+
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+    }
+    lockSelection = () => {
+        this.layoutDoc[this.props.fieldKey] = this.layoutDoc[this.props.fieldKey];
+    }
+    showSelection = () => {
+        this.layoutDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`);
+    }
+    isSelectionLocked = () => {
+        const kvpstring = Field.toKeyValueString(this.layoutDoc, this.props.fieldKey);
+        return !kvpstring || kvpstring.includes("DOC");
+    }
+    toggleLockSelection = () => {
+        !this.isSelectionLocked() ? this.lockSelection() : this.showSelection();
+        return true;
+    }
+    prevSelection = () => {
+        this.lockSelection();
+        if (this._curSelection > 0) {
+            this.layoutDoc[this.props.fieldKey] = this._selections[--this._curSelection];
+            return true;
+        }
+    }
+    nextSelection = () => {
+        if (this._curSelection < this._selections.length - 1 && this._selections.length) {
+            this.layoutDoc[this.props.fieldKey] = this._selections[++this._curSelection];
+            return true;
+        }
+    }
+    onPointerDown = (e: React.PointerEvent) => {
+        if (this.active() && e.button === 0 && !e.ctrlKey) {
+            e.stopPropagation();
+        }
+    }
+    onLockClick = (e: React.MouseEvent) => {
+        this.toggleLockSelection();
+        (e.nativeEvent as any).formattedHandled = true;
+        e.stopPropagation();
+    }
+    get xPad() { return NumCast(this.props.Document._xPadding); }
+    get yPad() { return NumCast(this.props.Document._yPadding); }
+    onClick = (e: React.MouseEvent) => {
+        let hitWidget: boolean | undefined = false;
+        if (this._contRef.current!.getBoundingClientRect().top + this.yPad > e.clientY) hitWidget = (() => { this.props.select(false); return true; })();
+        else if (this._contRef.current!.getBoundingClientRect().bottom - this.yPad < e.clientY) hitWidget = (() => { this.props.select(false); return true; })();
+        else {
+            if (this._contRef.current!.getBoundingClientRect().left + this.xPad > e.clientX) hitWidget = this.prevSelection();
+            if (this._contRef.current!.getBoundingClientRect().right - this.xPad < e.clientX) hitWidget = this.nextSelection();
+        }
+        if (hitWidget) {
+            (e.nativeEvent as any).formattedHandled = true;
+            e.stopPropagation();
+        }
+    }
+    pwidth = () => this.props.PanelWidth() - 2 * this.xPad;
+    pheight = () => this.props.PanelHeight() - 2 * this.yPad;
+    getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad);
+    isActive = () => this.active() || !this.props.renderDepth;
+    layoutTemplateDoc = () => Cast(this.props.Document.childLayoutTemplate, Doc, null);
+    get renderContents() {
+        const containedDoc = Cast(this.dataDoc[this.props.fieldKey], Doc, null);
+        const layoutTemplate = StrCast(this.layoutDoc.childLayoutString);
+        const contents = !(containedDoc instanceof Doc) ? (null) : this.layoutDoc.childLayoutString || this.layoutTemplateDoc() ?
+            <DocumentView
+                Document={containedDoc}
+                DataDoc={undefined}
+                LibraryPath={emptyPath}
+                ContainingCollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
+                ContainingCollectionDoc={undefined}
+                fitToBox={true}
+                LayoutTemplateString={layoutTemplate}
+                LayoutTemplate={this.layoutTemplateDoc}
+                rootSelected={this.props.isSelected}
+                addDocument={this.props.addDocument}
+                moveDocument={this.props.moveDocument}
+                removeDocument={this.props.removeDocument}
+                addDocTab={this.props.addDocTab}
+                pinToPres={this.props.pinToPres}
+                ScreenToLocalTransform={this.getTransform}
+                renderDepth={this.props.renderDepth + 1}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
+                PanelWidth={this.pwidth}
+                PanelHeight={this.pheight}
+                focus={this.props.focus}
+                parentActive={this.isActive}
+                dontRegisterView={true}
+                whenActiveChanged={this.props.whenActiveChanged}
+                bringToFront={returnFalse}
+                ContentScaling={returnOne} /> :
+            <ContentFittingDocumentView
+                Document={containedDoc}
+                DataDoc={undefined}
+                LibraryPath={emptyPath}
+                ContainingCollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
+                ContainingCollectionDoc={undefined}
+                fitToBox={true}
+                LayoutTemplateString={layoutTemplate}
+                LayoutTemplate={this.layoutTemplateDoc}
+                rootSelected={this.props.isSelected}
+                addDocument={this.props.addDocument}
+                moveDocument={this.props.moveDocument}
+                removeDocument={this.props.removeDocument}
+                addDocTab={this.props.addDocTab}
+                pinToPres={this.props.pinToPres}
+                ScreenToLocalTransform={this.getTransform}
+                renderDepth={this.props.renderDepth + 1}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
+                PanelWidth={this.pwidth}
+                PanelHeight={this.pheight}
+                focus={this.props.focus}
+                parentActive={this.isActive}
+                dontRegisterView={true}
+                whenActiveChanged={this.props.whenActiveChanged}
+                bringToFront={returnFalse}
+                ContentScaling={returnOne}
+            />;
+        return contents;
+    }
+    render() {
+        TraceMobx();
+        return <div className="documentBox-container" ref={this._contRef}
+            onContextMenu={this.specificContextMenu}
+            onPointerDown={this.onPointerDown} onClick={this.onClick}
+            style={{
+                background: StrCast(this.layoutDoc.backgroundColor),
+                border: `#00000021 solid ${this.xPad}px`,
+                borderTop: `#0000005e solid ${this.yPad}px`,
+                borderBottom: `#0000005e solid ${this.yPad}px`,
+            }}>
+            {this.renderContents}
+            <div className="documentBox-lock" onClick={this.onLockClick} ref={this.createDropTarget}
+                style={{ marginTop: - this.yPad }}>
+                <FontAwesomeIcon icon={this.isSelectionLocked() ? "lock" : "unlock"} size="sm" />
+            </div>
+        </div >;
+    }
+
+    @undoBatch
+    @action
+    drop = (e: Event, de: DragManager.DropEvent) => {
+        if (de.complete.docDragData) {
+            if (de.complete.docDragData.draggedDocuments[0].type === DocumentType.FONTICON) {
+                const doc = Cast(de.complete.docDragData.draggedDocuments[0].dragFactory, Doc, null);
+                this.props.Document.childLayoutTemplate = doc;
+            }
+        }
+    }
+    protected createDropTarget = (ele: HTMLDivElement) => {
+        this._dropDisposer?.();
+        ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
+    }
+
+}
diff --git a/src/client/views/nodes/DocumentBox.scss b/src/client/views/nodes/DocumentBox.scss
deleted file mode 100644
index 3a27c16c1..000000000
--- a/src/client/views/nodes/DocumentBox.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-.documentBox-container {
-    width: 100%;
-    height: 100%;
-    pointer-events: all;
-    background: rgb(241, 239, 235);
-    position: absolute;
-    .documentBox-lock {
-        margin: auto;
-        color: white;
-        position: absolute;
-    }
-    .contentFittingDocumentView {
-        position: absolute;
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
deleted file mode 100644
index b53c7cfe6..000000000
--- a/src/client/views/nodes/DocumentBox.tsx
+++ /dev/null
@@ -1,207 +0,0 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, IReactionDisposer, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Field } from "../../../new_fields/Doc";
-import { collectionSchema, documentSchema } from "../../../new_fields/documentSchemas";
-import { makeInterface } from "../../../new_fields/Schema";
-import { ComputedField } from "../../../new_fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { TraceMobx } from "../../../new_fields/util";
-import { emptyPath, returnFalse, returnOne, returnZero } from "../../../Utils";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { DragManager } from "../../util/DragManager";
-import { undoBatch } from "../../util/UndoManager";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { ViewBoxAnnotatableComponent } from "../DocComponent";
-import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
-import "./DocumentBox.scss";
-import { DocumentView } from "./DocumentView";
-import { FieldView, FieldViewProps } from "./FieldView";
-import React = require("react");
-
-type DocHolderBoxSchema = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
-const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema);
-
-@observer
-export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) {
-    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); }
-    _prevSelectionDisposer: IReactionDisposer | undefined;
-    _dropDisposer?: DragManager.DragDropDisposer;
-    _selections: Doc[] = [];
-    _contRef = React.createRef<HTMLDivElement>();
-    _curSelection = -1;
-    componentDidMount() {
-        this._prevSelectionDisposer = reaction(() => this.layoutDoc[this.props.fieldKey], (data) => {
-            if (data instanceof Doc && !this.isSelectionLocked()) {
-                this._selections.indexOf(data) !== -1 && this._selections.splice(this._selections.indexOf(data), 1);
-                this._selections.push(data);
-                this._curSelection = this._selections.length - 1;
-            }
-        });
-    }
-    componentWillUnmount() {
-        this._prevSelectionDisposer?.();
-    }
-    specificContextMenu = (e: React.MouseEvent): void => {
-        const funcs: ContextMenuProps[] = [];
-        funcs.push({ description: (this.isSelectionLocked() ? "Show" : "Lock") + " Selection", event: () => this.toggleLockSelection, icon: "expand-arrows-alt" });
-        funcs.push({ description: (this.layoutDoc.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => this.layoutDoc.excludeCollections = !this.layoutDoc.excludeCollections, icon: "expand-arrows-alt" });
-        funcs.push({ description: `${this.layoutDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.layoutDoc.forceActive = !this.layoutDoc.forceActive, icon: "project-diagram" });
-        funcs.push({ description: `Show ${this.layoutDoc.childLayoutTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childLayoutString = this.layoutDoc.childLayoutString ? undefined : "<KeyValueBox {...props} />", icon: "project-diagram" });
-
-        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
-    }
-    lockSelection = () => {
-        this.layoutDoc[this.props.fieldKey] = this.layoutDoc[this.props.fieldKey];
-    }
-    showSelection = () => {
-        this.layoutDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`);
-    }
-    isSelectionLocked = () => {
-        const kvpstring = Field.toKeyValueString(this.layoutDoc, this.props.fieldKey);
-        return !kvpstring || kvpstring.includes("DOC");
-    }
-    toggleLockSelection = () => {
-        !this.isSelectionLocked() ? this.lockSelection() : this.showSelection();
-        return true;
-    }
-    prevSelection = () => {
-        this.lockSelection();
-        if (this._curSelection > 0) {
-            this.layoutDoc[this.props.fieldKey] = this._selections[--this._curSelection];
-            return true;
-        }
-    }
-    nextSelection = () => {
-        if (this._curSelection < this._selections.length - 1 && this._selections.length) {
-            this.layoutDoc[this.props.fieldKey] = this._selections[++this._curSelection];
-            return true;
-        }
-    }
-    onPointerDown = (e: React.PointerEvent) => {
-        if (this.active() && e.button === 0 && !e.ctrlKey) {
-            e.stopPropagation();
-        }
-    }
-    onLockClick = (e: React.MouseEvent) => {
-        this.toggleLockSelection();
-        (e.nativeEvent as any).formattedHandled = true;
-        e.stopPropagation();
-    }
-    get xPad() { return NumCast(this.props.Document._xPadding); }
-    get yPad() { return NumCast(this.props.Document._yPadding); }
-    onClick = (e: React.MouseEvent) => {
-        let hitWidget: boolean | undefined = false;
-        if (this._contRef.current!.getBoundingClientRect().top + this.yPad > e.clientY) hitWidget = (() => { this.props.select(false); return true; })();
-        else if (this._contRef.current!.getBoundingClientRect().bottom - this.yPad < e.clientY) hitWidget = (() => { this.props.select(false); return true; })();
-        else {
-            if (this._contRef.current!.getBoundingClientRect().left + this.xPad > e.clientX) hitWidget = this.prevSelection();
-            if (this._contRef.current!.getBoundingClientRect().right - this.xPad < e.clientX) hitWidget = this.nextSelection();
-        }
-        if (hitWidget) {
-            (e.nativeEvent as any).formattedHandled = true;
-            e.stopPropagation();
-        }
-    }
-    pwidth = () => this.props.PanelWidth() - 2 * this.xPad;
-    pheight = () => this.props.PanelHeight() - 2 * this.yPad;
-    getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad);
-    isActive = () => this.active() || !this.props.renderDepth;
-    layoutTemplateDoc = () => Cast(this.props.Document.childLayoutTemplate, Doc, null);
-    get renderContents() {
-        const containedDoc = Cast(this.dataDoc[this.props.fieldKey], Doc, null);
-        const layoutTemplate = StrCast(this.layoutDoc.childLayoutString);
-        const contents = !(containedDoc instanceof Doc) ? (null) : this.layoutDoc.childLayoutString || this.layoutTemplateDoc() ?
-            <DocumentView
-                Document={containedDoc}
-                DataDoc={undefined}
-                LibraryPath={emptyPath}
-                ContainingCollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
-                ContainingCollectionDoc={undefined}
-                fitToBox={true}
-                LayoutTemplateString={layoutTemplate}
-                LayoutTemplate={this.layoutTemplateDoc}
-                rootSelected={this.props.isSelected}
-                addDocument={this.props.addDocument}
-                moveDocument={this.props.moveDocument}
-                removeDocument={this.props.removeDocument}
-                addDocTab={this.props.addDocTab}
-                pinToPres={this.props.pinToPres}
-                ScreenToLocalTransform={this.getTransform}
-                renderDepth={this.props.renderDepth + 1}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
-                PanelWidth={this.pwidth}
-                PanelHeight={this.pheight}
-                focus={this.props.focus}
-                parentActive={this.isActive}
-                dontRegisterView={true}
-                whenActiveChanged={this.props.whenActiveChanged}
-                bringToFront={returnFalse}
-                ContentScaling={returnOne} /> :
-            <ContentFittingDocumentView
-                Document={containedDoc}
-                DataDoc={undefined}
-                LibraryPath={emptyPath}
-                ContainingCollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
-                ContainingCollectionDoc={undefined}
-                fitToBox={true}
-                LayoutTemplateString={layoutTemplate}
-                LayoutTemplate={this.layoutTemplateDoc}
-                rootSelected={this.props.isSelected}
-                addDocument={this.props.addDocument}
-                moveDocument={this.props.moveDocument}
-                removeDocument={this.props.removeDocument}
-                addDocTab={this.props.addDocTab}
-                pinToPres={this.props.pinToPres}
-                ScreenToLocalTransform={this.getTransform}
-                renderDepth={this.props.renderDepth + 1}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
-                PanelWidth={this.pwidth}
-                PanelHeight={this.pheight}
-                focus={this.props.focus}
-                parentActive={this.isActive}
-                dontRegisterView={true}
-                whenActiveChanged={this.props.whenActiveChanged}
-                bringToFront={returnFalse}
-                ContentScaling={returnOne}
-            />;
-        return contents;
-    }
-    render() {
-        TraceMobx();
-        return <div className="documentBox-container" ref={this._contRef}
-            onContextMenu={this.specificContextMenu}
-            onPointerDown={this.onPointerDown} onClick={this.onClick}
-            style={{
-                background: StrCast(this.layoutDoc.backgroundColor),
-                border: `#00000021 solid ${this.xPad}px`,
-                borderTop: `#0000005e solid ${this.yPad}px`,
-                borderBottom: `#0000005e solid ${this.yPad}px`,
-            }}>
-            {this.renderContents}
-            <div className="documentBox-lock" onClick={this.onLockClick} ref={this.createDropTarget}
-                style={{ marginTop: - this.yPad }}>
-                <FontAwesomeIcon icon={this.isSelectionLocked() ? "lock" : "unlock"} size="sm" />
-            </div>
-        </div >;
-    }
-
-    @undoBatch
-    @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        if (de.complete.docDragData) {
-            if (de.complete.docDragData.draggedDocuments[0].type === DocumentType.FONTICON) {
-                const doc = Cast(de.complete.docDragData.draggedDocuments[0].dragFactory, Doc, null);
-                this.props.Document.childLayoutTemplate = doc;
-            }
-        }
-    }
-    protected createDropTarget = (ele: HTMLDivElement) => {
-        this._dropDisposer?.();
-        ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
-    }
-
-}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 81667e549..395a6e0b2 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -14,7 +14,7 @@ import { LabelBox } from "./LabelBox";
 import { SliderBox } from "./SliderBox";
 import { LinkBox } from "./LinkBox";
 import { ScriptingBox } from "./ScriptingBox";
-import { DocHolderBox } from "./DocumentBox";
+import { DocHolderBox } from "./DocHolderBox";
 import { DocumentViewProps } from "./DocumentView";
 import "./DocumentView.scss";
 import { FontIconBox } from "./FontIconBox";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7a81801e1..86e58ca7a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -23,6 +23,7 @@ import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from '../../documents/DocumentTypes';
 import { ClientUtils } from '../../util/ClientUtils';
 import { DocumentManager } from "../../util/DocumentManager";
+import { SnappingManager } from '../../util/SnappingManager';
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { Scripting } from '../../util/Scripting';
@@ -1105,7 +1106,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
     @computed get ignorePointerEvents() {
         return this.props.pointerEvents === false ||
-            (this.Document.isBackground && !this.isSelected() && !DragManager.Vals.Instance.GetIsDragging()) ||
+            (this.Document.isBackground && !this.isSelected() && !SnappingManager.GetIsDragging()) ||
             (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
     }
     @undoBatch
diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx
index d248b098c..2a21a380d 100644
--- a/src/client/views/nodes/QueryBox.tsx
+++ b/src/client/views/nodes/QueryBox.tsx
@@ -5,13 +5,12 @@ import { documentSchema } from "../../../new_fields/documentSchemas";
 import { Id } from '../../../new_fields/FieldSymbols';
 import { makeInterface, listSpec } from "../../../new_fields/Schema";
 import { StrCast, Cast } from "../../../new_fields/Types";
-import { SelectionManager } from "../../util/SelectionManager";
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { SearchBox } from "../search/SearchBox";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./QueryBox.scss";
 import { List } from "../../../new_fields/List";
-import { DragManager } from "../../util/DragManager";
+import { SnappingManager } from "../../util/SnappingManager";
 
 type QueryDocument = makeInterface<[typeof documentSchema]>;
 const QueryDocument = makeInterface(documentSchema);
@@ -28,7 +27,7 @@ export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryD
     }
 
     render() {
-        const dragging = !DragManager.Vals.Instance.GetIsDragging() ? "" : "-dragging";
+        const dragging = !SnappingManager.GetIsDragging() ? "" : "-dragging";
         return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} >
             <SearchBox
                 id={this.props.Document[Id]}
-- 
cgit v1.2.3-70-g09d2


From 51a1f88b4e975946eb5ac8cef75a122e197cd872 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 7 May 2020 01:35:11 -0400
Subject: added batch requesting of list items.  fixed some performance issues
 with tree views.

---
 src/client/DocServer.ts                            | 64 +++++++++++-------
 src/client/util/Scripting.ts                       |  2 +-
 src/client/views/DocumentButtonBar.tsx             | 32 ++++-----
 src/client/views/DocumentDecorations.tsx           |  2 +-
 .../views/collections/CollectionDockingView.tsx    |  8 +--
 .../views/collections/CollectionStackingView.tsx   | 10 ++-
 .../views/collections/CollectionTreeView.scss      |  1 -
 .../views/collections/CollectionTreeView.tsx       | 18 +++--
 .../views/collections/ParentDocumentSelector.tsx   |  4 +-
 src/new_fields/List.ts                             | 76 +++++++++++++++-------
 src/new_fields/Proxy.ts                            | 18 ++++-
 .../authentication/models/current_user_utils.ts    | 13 ++--
 src/server/database.ts                             |  2 +-
 13 files changed, 161 insertions(+), 89 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 0c9d5f75c..bf9fd232c 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -7,6 +7,7 @@ import { RefField } from '../new_fields/RefField';
 import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
 import GestureOverlay from './views/GestureOverlay';
 import MobileInkOverlay from '../mobile/MobileInkOverlay';
+import { runInAction } from 'mobx';
 
 /**
  * This class encapsulates the transfer and cross-client synchronization of
@@ -109,6 +110,7 @@ export namespace DocServer {
         GUID = identifier;
         _socket = OpenSocket(`${protocol}//${hostname}:${port}`);
 
+        _GetCachedRefField = _GetCachedRefFieldImpl;
         _GetRefField = _GetRefFieldImpl;
         _GetRefFields = _GetRefFieldsImpl;
         _CreateField = _CreateFieldImpl;
@@ -243,12 +245,22 @@ export namespace DocServer {
             return Promise.resolve(cached);
         }
     };
+    const _GetCachedRefFieldImpl = (id: string): Opt<RefField> => {
+        const cached = _cache[id];
+        if (cached !== undefined && !(cached instanceof Promise)) {
+            return cached;
+        }
+    }
 
     let _GetRefField: (id: string) => Promise<Opt<RefField>> = errorFunc;
+    let _GetCachedRefField: (id: string) => Opt<RefField> = errorFunc;
 
     export function GetRefField(id: string): Promise<Opt<RefField>> {
         return _GetRefField(id);
     }
+    export function GetCachedRefField(id: string): Opt<RefField> {
+        return _GetCachedRefField(id);
+    }
 
     export async function getYoutubeChannels() {
         const apiKey = await Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
@@ -308,32 +320,34 @@ export namespace DocServer {
         const deserializeFields = getSerializedFields.then(async fields => {
             const fieldMap: { [id: string]: RefField } = {};
             const proms: Promise<void>[] = [];
-            for (const field of fields) {
-                if (field !== undefined && field !== null) {
-                    // deserialize
-                    const prom = SerializationHelper.Deserialize(field).then(deserialized => {
-                        fieldMap[field.id] = deserialized;
-
-                        //overwrite or delete any promises (that we inserted as flags
-                        // to indicate that the field was in the process of being fetched). Now everything
-                        // should be an actual value within or entirely absent from the cache.
-                        if (deserialized !== undefined) {
-                            _cache[field.id] = deserialized;
-                        } else {
-                            delete _cache[field.id];
-                        }
-                        return deserialized;
-                    });
-                    // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
-                    // we set the value at the field's id to a promise that will resolve to the field. 
-                    // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
-                    // The mapping in the .then call ensures that when other callers await these promises, they'll
-                    // get the resolved field
-                    _cache[field.id] = prom;
-                    // adds to a list of promises that will be awaited asynchronously
-                    proms.push(prom);
+            runInAction(() => {
+                for (const field of fields) {
+                    if (field !== undefined && field !== null) {
+                        // deserialize
+                        const prom = SerializationHelper.Deserialize(field).then(deserialized => {
+                            fieldMap[field.id] = deserialized;
+
+                            //overwrite or delete any promises (that we inserted as flags
+                            // to indicate that the field was in the process of being fetched). Now everything
+                            // should be an actual value within or entirely absent from the cache.
+                            if (deserialized !== undefined) {
+                                _cache[field.id] = deserialized;
+                            } else {
+                                delete _cache[field.id];
+                            }
+                            return deserialized;
+                        });
+                        // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
+                        // we set the value at the field's id to a promise that will resolve to the field. 
+                        // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
+                        // The mapping in the .then call ensures that when other callers await these promises, they'll
+                        // get the resolved field
+                        _cache[field.id] = prom;
+                        // adds to a list of promises that will be awaited asynchronously
+                        proms.push(prom);
+                    }
                 }
-            }
+            });
             await Promise.all(proms);
             return fieldMap;
         });
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 12628273b..8b7b9c9c7 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -136,7 +136,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
             if (batch) {
                 batch.end();
             }
-            onError && onError(error);
+            onError?.(error);
             return { success: false, error, result: errorVal };
         }
     };
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index cf57094b2..d58eb5875 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -50,11 +50,9 @@ enum UtilityButtonState {
 }
 
 @observer
-export class DocumentButtonBar extends React.Component<{ views: (DocumentView | undefined)[], stack?: any }, {}> {
+export class DocumentButtonBar extends React.Component<{ views: () => (DocumentView | undefined)[], stack?: any }, {}> {
     private _linkButton = React.createRef<HTMLDivElement>();
     private _dragRef = React.createRef<HTMLDivElement>();
-    private _downX = 0;
-    private _downY = 0;
     private _pullAnimating = false;
     private _pushAnimating = false;
     private _pullColorAnimating = false;
@@ -71,7 +69,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
     public static hasPushedHack = false;
     public static hasPulledHack = false;
 
-    constructor(props: { views: (DocumentView | undefined)[] }) {
+    constructor(props: { views: () => (DocumentView | undefined)[] }) {
         super(props);
         runInAction(() => DocumentButtonBar.Instance = this);
     }
@@ -113,7 +111,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
         this._pullColorAnimating = false;
     });
 
-    get view0() { return this.props.views?.[0]; }
+    get view0() { return this.props.views()?.[0]; }
 
     @action
     onLinkButtonMoved = (e: PointerEvent) => {
@@ -265,7 +263,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
         const view0 = this.view0;
         return !view0 ? (null) : <div title="Show metadata panel" className="documentButtonBar-linkFlyout">
             <Flyout anchorPoint={anchorPoints.LEFT_TOP}
-                content={<MetadataEntryMenu docs={() => this.props.views.filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction />  /* tfs: @bcz This might need to be the data document? */}>
+                content={<MetadataEntryMenu docs={() => this.props.views().filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction />  /* tfs: @bcz This might need to be the data document? */}>
                 <div className={"documentButtonBar-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} >
                     {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" />}
                 </div>
@@ -289,7 +287,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
     }
     onAliasButtonMoved = () => {
         if (this._dragRef.current) {
-            const dragDocView = this.props.views[0]!;
+            const dragDocView = this.view0!;
             const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
             const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
             dragData.embedDoc = true;
@@ -308,16 +306,18 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
     get templateButton() {
         const view0 = this.view0;
         const templates: Map<Template, boolean> = new Map();
+        const views = this.props.views();
         Array.from(Object.values(Templates.TemplateList)).map(template =>
-            templates.set(template, this.props.views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean)));
-        return !view0 ? (null) : <div title="Tap: Customize layout.  Drag: Create alias" className="documentButtonBar-linkFlyout" ref={this._dragRef}>
-            <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
-                content={!this._aliasDown ? (null) : <TemplateMenu docViews={this.props.views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
-                <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
-                    {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
-                </div>
-            </Flyout>
-        </div>;
+            templates.set(template, views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean)));
+        return !view0 ? (null) :
+            <div title="Tap: Customize layout.  Drag: Create alias" className="documentButtonBar-linkFlyout" ref={this._dragRef}>
+                <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
+                    content={!this._aliasDown ? (null) : <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
+                    <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
+                        {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
+                    </div>
+                </Flyout>
+            </div>;
     }
 
     render() {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b89806656..9b6105811 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -499,7 +499,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
             </div >
             <div className="link-button-container" style={{ left: bounds.x - this._resizeBorderWidth / 2, top: bounds.b + this._resizeBorderWidth / 2 }}>
-                <DocumentButtonBar views={SelectionManager.SelectedDocuments()} />
+                <DocumentButtonBar views={SelectionManager.SelectedDocuments} />
             </div>
         </div >
         );
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6cd5d1b1b..6f5989052 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -517,14 +517,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY);
                     }
                 };
-                let rendered = false;
+
                 tab.buttonDisposer = reaction(() => ((view: Opt<DocumentView>) => view ? [view] : [])(DocumentManager.Instance.getDocumentView(doc)),
                     (views) => {
-                        !rendered && ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} >
-                            <DockingViewButtonSelector views={views} Stack={stack} />
+                        ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} >
+                            <DockingViewButtonSelector views={() => views} Stack={stack} />
                         </span>,
                             gearSpan);
-                        rendered = true;
+                        tab.buttonDisposer?.();
                     });
 
                 tab.reactComponents = [gearSpan];
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 4cc2e1a5f..c199988c0 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -57,6 +57,14 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
     }
     @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
 
+    constructor(props: any) {
+        super(props);
+
+        if (this.sectionHeaders === undefined) {
+            this.props.Document.sectionHeaders = new List<SchemaHeaderField>();
+        }
+    }
+
     children(docs: Doc[], columns?: number) {
         TraceMobx();
         this._docXfs.length = 0;
@@ -85,7 +93,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
             setTimeout(() => this.props.Document.sectionHeaders = new List<SchemaHeaderField>(), 0);
             return new Map<SchemaHeaderField, Doc[]>();
         }
-        const sectionHeaders: SchemaHeaderField[] = Array.from(this.sectionHeaders);
+        const sectionHeaders = Array.from(this.sectionHeaders);
         const fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
         let changed = false;
         this.filteredChildren.map(d => {
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index a00bb6bfb..2aac81146 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -81,7 +81,6 @@
     position: relative;
     text-overflow: ellipsis;
     white-space: pre-wrap;
-    overflow: hidden;
     min-width: 10px;
     // width:100%;//width: max-content;
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 297c11e35..4fb54cc07 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -34,6 +34,7 @@ import "./CollectionTreeView.scss";
 import { CollectionViewType } from './CollectionView';
 import React = require("react");
 import { makeTemplate } from '../../util/DropConverter';
+import { TraceMobx } from '../../../new_fields/util';
 
 
 export interface TreeViewProps {
@@ -100,7 +101,7 @@ class TreeView extends React.Component<TreeViewProps> {
     get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, "fields"); }
     @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
     set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
-    @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
+    @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.props.document.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
     @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
     @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); }
     @computed get dataDoc() { return this.templateDataDoc ? this.templateDataDoc : this.props.document; }
@@ -326,6 +327,7 @@ class TreeView extends React.Component<TreeViewProps> {
     rtfHeight = () => this.rtfWidth() < Doc.Layout(this.props.document)?.[WidthSym]() ? Math.min(Doc.Layout(this.props.document)?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
 
     @computed get renderContent() {
+        TraceMobx();
         const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
         if (expandKey !== undefined) {
             const remDoc = (doc: Doc) => this.remove(doc, expandKey);
@@ -419,13 +421,15 @@ class TreeView extends React.Component<TreeViewProps> {
         return [{ script: focusScript!, label: "Focus" }];
     }
     _docRef = React.createRef<DocumentView>();
+    _editTitleScript: ScriptField | undefined;
     /**
      * Renders the EditableView title element for placement into the tree.
      */
     @computed
     get renderTitle() {
+        TraceMobx();
         const onItemDown = SetupDrag(this._tref, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId[Id], true);
-        const editTitle = ScriptField.MakeFunction("setInPlace(this, 'editTitle', true)");
+        //!this._editTitleScript && (this._editTitleScript = ScriptField.MakeFunction("setInPlace(this, 'editTitle', true)"));
 
         const headerElements = (
             <>
@@ -449,7 +453,6 @@ class TreeView extends React.Component<TreeViewProps> {
         return <>
             <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`} onPointerDown={onItemDown}
                 style={{
-                    background: Doc.IsHighlighted(this.props.document) ? "orange" : Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
                     fontWeight: this.props.document.searchMatch ? "bold" : undefined,
                     textDecoration: Doc.GetT(this.props.document, "title", "string", true) ? "underline" : undefined,
                     outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
@@ -461,12 +464,12 @@ class TreeView extends React.Component<TreeViewProps> {
                         ref={this._docRef}
                         Document={this.props.document}
                         DataDoc={undefined}
-                        LibraryPath={this.props.libraryPath || []}
+                        LibraryPath={this.props.libraryPath || emptyPath}
                         addDocument={undefined}
                         addDocTab={this.props.addDocTab}
                         rootSelected={returnTrue}
                         pinToPres={emptyFunction}
-                        onClick={this.props.onChildClick || editTitle}
+                        onClick={this.props.onChildClick || this._editTitleScript}
                         dropAction={this.props.dropAction}
                         moveDocument={this.move}
                         removeDocument={this.removeDoc}
@@ -478,7 +481,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         NativeWidth={returnZero}
                         contextMenuItems={this.contextMenuItems}
                         renderDepth={1}
-                        focus={emptyFunction}
+                        focus={returnTrue}
                         parentActive={returnTrue}
                         whenActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
@@ -493,8 +496,9 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     render() {
+        TraceMobx();
         const sorting = this.props.document[`${this.fieldKey}-sortAscending`];
-        setTimeout(() => runInAction(() => untracked(() => this._overrideTreeViewOpen = this.treeViewOpen)), 0);
+        //setTimeout(() => runInAction(() => untracked(() => this._overrideTreeViewOpen = this.treeViewOpen)), 0);
         return <div className="treeViewItem-container" ref={this.createTreeDropTarget}>
             <li className="collection-child">
                 <div className="treeViewItem-header" ref={this._header} onClick={e => {
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 10c6ead1a..6e4f801c0 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -94,7 +94,7 @@ export class ParentDocSelector extends React.Component<SelectorProps> {
 }
 
 @observer
-export class DockingViewButtonSelector extends React.Component<{ views: DocumentView[], Stack: any }> {
+export class DockingViewButtonSelector extends React.Component<{ views: () => DocumentView[], Stack: any }> {
     customStylesheet(styles: any) {
         return {
             ...styles,
@@ -120,7 +120,7 @@ export class DockingViewButtonSelector extends React.Component<{ views: Document
                 if (getComputedStyle(this._ref.current!).width !== "100%") {
                     e.stopPropagation(); e.preventDefault();
                 }
-                this.props.views[0]?.select(false);
+                this.props.views()[0]?.select(false);
             }} className="buttonSelector">
             <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
                 <FontAwesomeIcon icon={"cog"} size={"sm"} />
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index a43f11e82..fdabea365 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -2,12 +2,13 @@ import { Deserializable, autoObject, afterDocDeserialize } from "../client/util/
 import { Field } from "./Doc";
 import { setter, getter, deleteProperty, updateFunction } from "./util";
 import { serializable, alias, list } from "serializr";
-import { observable, action } from "mobx";
+import { observable, action, runInAction } from "mobx";
 import { ObjectField } from "./ObjectField";
 import { RefField } from "./RefField";
 import { ProxyField } from "./Proxy";
-import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, ToString, Copy } from "./FieldSymbols";
+import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, ToString, Copy, Id } from "./FieldSymbols";
 import { Scripting } from "../client/util/Scripting";
+import { DocServer } from "../client/DocServer";
 
 const listHandlers: any = {
     /// Mutator methods
@@ -54,11 +55,13 @@ const listHandlers: any = {
         return res;
     },
     sort(cmpFunc: any) {
+        this[Self].__realFields(); // coerce retrieving entire array
         const res = this[Self].__fields.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
         this[Update]();
         return res;
     },
     splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
+        this[Self].__realFields(); // coerce retrieving entire array
         items = items.map(toObjectField);
         const list = this[Self];
         for (let i = 0; i < items.length; i++) {
@@ -94,102 +97,102 @@ const listHandlers: any = {
     },
     /// Accessor methods
     concat: action(function (this: any, ...items: any[]) {
+        this[Self].__realFields();
         return this[Self].__fields.map(toRealField).concat(...items);
     }),
     includes(valueToFind: any, fromIndex: number) {
-        const fields = this[Self].__fields;
         if (valueToFind instanceof RefField) {
-            return fields.map(toRealField).includes(valueToFind, fromIndex);
+            return this[Self].__realFields().includes(valueToFind, fromIndex);
         } else {
-            return fields.includes(valueToFind, fromIndex);
+            return this[Self].__fields.includes(valueToFind, fromIndex);
         }
     },
     indexOf(valueToFind: any, fromIndex: number) {
-        const fields = this[Self].__fields;
         if (valueToFind instanceof RefField) {
-            return fields.map(toRealField).indexOf(valueToFind, fromIndex);
+            return this[Self].__realFields().indexOf(valueToFind, fromIndex);
         } else {
-            return fields.indexOf(valueToFind, fromIndex);
+            return this[Self].__fields.indexOf(valueToFind, fromIndex);
         }
     },
     join(separator: any) {
+        this[Self].__realFields();
         return this[Self].__fields.map(toRealField).join(separator);
     },
     lastIndexOf(valueToFind: any, fromIndex: number) {
-        const fields = this[Self].__fields;
         if (valueToFind instanceof RefField) {
-            return fields.map(toRealField).lastIndexOf(valueToFind, fromIndex);
+            return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex);
         } else {
-            return fields.lastIndexOf(valueToFind, fromIndex);
+            return this[Self].__fields.lastIndexOf(valueToFind, fromIndex);
         }
     },
     slice(begin: number, end: number) {
+        this[Self].__realFields();
         return this[Self].__fields.slice(begin, end).map(toRealField);
     },
 
     /// Iteration methods
     entries() {
-        return this[Self].__fields.map(toRealField).entries();
+        return this[Self].__realFields().entries();
     },
     every(callback: any, thisArg: any) {
-        return this[Self].__fields.map(toRealField).every(callback, thisArg);
+        return this[Self].__realFields().every(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
         // return this[Self].__fields.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     filter(callback: any, thisArg: any) {
-        return this[Self].__fields.map(toRealField).filter(callback, thisArg);
+        return this[Self].__realFields().filter(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
         // return this[Self].__fields.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     find(callback: any, thisArg: any) {
-        return this[Self].__fields.map(toRealField).find(callback, thisArg);
+        return this[Self].__realFields().find(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
         // return this[Self].__fields.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     findIndex(callback: any, thisArg: any) {
-        return this[Self].__fields.map(toRealField).findIndex(callback, thisArg);
+        return this[Self].__realFields().findIndex(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
         // return this[Self].__fields.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     forEach(callback: any, thisArg: any) {
-        return this[Self].__fields.map(toRealField).forEach(callback, thisArg);
+        return this[Self].__realFields().forEach(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
         // return this[Self].__fields.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     map(callback: any, thisArg: any) {
-        return this[Self].__fields.map(toRealField).map(callback, thisArg);
+        return this[Self].__realFields().map(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
         // return this[Self].__fields.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     reduce(callback: any, initialValue: any) {
-        return this[Self].__fields.map(toRealField).reduce(callback, initialValue);
+        return this[Self].__realFields().reduce(callback, initialValue);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
         // return this[Self].__fields.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
     },
     reduceRight(callback: any, initialValue: any) {
-        return this[Self].__fields.map(toRealField).reduceRight(callback, initialValue);
+        return this[Self].__realFields().reduceRight(callback, initialValue);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
         // return this[Self].__fields.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
     },
     some(callback: any, thisArg: any) {
-        return this[Self].__fields.map(toRealField).some(callback, thisArg);
+        return this[Self].__realFields().some(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
         // return this[Self].__fields.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     values() {
-        return this[Self].__fields.map(toRealField).values();
+        return this[Self].__realFields().values();
     },
     [Symbol.iterator]() {
-        return this[Self].__fields.map(toRealField).values();
+        return this[Self].__realFields().values();
     }
 };
 
@@ -254,6 +257,31 @@ class ListImpl<T extends Field> extends ObjectField {
 
     [key: number]: T | (T extends RefField ? Promise<T> : never);
 
+    // this requests all ProxyFields at the same time to avoid the overhead
+    // of separate network requests and separate updates to the React dom.
+    private __realFields() {
+        const waiting = this.__fields.filter(f => f instanceof ProxyField && f.promisedValue());
+        const promised = waiting.map(f => f instanceof ProxyField ? f.promisedValue() : "");
+        // if we find any ProxyFields that don't have a current value, then
+        // start the server request for all of them
+        if (promised.length) {
+            const promise = DocServer.GetRefFields(promised);
+            // as soon as we get the fields from the server, set all the list values in one
+            // action to generate one React dom update.
+            promise.then(fields => runInAction(() => {
+                waiting.map((w, i) => w instanceof ProxyField && w.setValue(fields[promised[i]]));
+            }));
+            // we also have to mark all lists items with this promise so that any calls to them
+            // will await the batch request.
+            // This counts on the handler for 'promise' in the call above being invoked before the
+            // handler for 'promise' in the lines below.
+            waiting.map((w, i) => {
+                w instanceof ProxyField && w.setPromise(promise.then(fields => fields[promised[i]]));
+            });
+        }
+        return this.__fields.map(toRealField);
+    }
+
     @serializable(alias("fields", list(autoObject(), { afterDeserialize: afterDocDeserialize })))
     private get __fields() {
         return this.___fields;
@@ -283,7 +311,7 @@ class ListImpl<T extends Field> extends ObjectField {
         // console.log(diff);
         const update = this[OnUpdate];
         // update && update(diff);
-        update && update();
+        update?.();
     }
 
     private [Self] = this;
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
index d50c0f14e..555faaad0 100644
--- a/src/new_fields/Proxy.ts
+++ b/src/new_fields/Proxy.ts
@@ -1,7 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { FieldWaiting } from "./Doc";
 import { primitive, serializable } from "serializr";
-import { observable, action } from "mobx";
+import { observable, action, runInAction } from "mobx";
 import { DocServer } from "../client/DocServer";
 import { RefField } from "./RefField";
 import { ObjectField } from "./ObjectField";
@@ -61,6 +61,11 @@ export class ProxyField<T extends RefField> extends ObjectField {
             return undefined;
         }
         if (!this.promise) {
+            const cached = DocServer.GetCachedRefField(this.fieldId);
+            if (cached !== undefined) {
+                runInAction(() => this.cache = cached as any);
+                return cached as any;
+            }
             this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => {
                 this.promise = undefined;
                 this.cache = field;
@@ -70,6 +75,17 @@ export class ProxyField<T extends RefField> extends ObjectField {
         }
         return this.promise as any;
     }
+    promisedValue(): string { return !this.cache && !this.failed && !this.promise ? this.fieldId : ""; }
+    setPromise(promise: any) {
+        this.promise = promise;
+    }
+    @action
+    setValue(field: any) {
+        this.promise = undefined;
+        this.cache = field;
+        if (field === undefined) this.failed = true;
+        return field;
+    }
 }
 
 export namespace ProxyField {
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index e7b448358..6e06fe931 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -21,8 +21,7 @@ import { FormattedTextBox } from "../../../client/views/nodes/formattedText/Form
 import { MainView } from "../../../client/views/MainView";
 import { DocumentType } from "../../../client/documents/DocumentTypes";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { faBoxOpen } from "@fortawesome/free-solid-svg-icons";
-import { CollectionMulticolumnView, DimUnit } from "../../../client/views/collections/collectionMulticolumn/CollectionMulticolumnView";
+import { DimUnit } from "../../../client/views/collections/collectionMulticolumn/CollectionMulticolumnView";
 
 export class CurrentUserUtils {
     private static curr_id: string;
@@ -282,8 +281,12 @@ export class CurrentUserUtils {
             doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc], { title: "icon templates", _height: 75 }));
         } else {
             const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
-            DocListCastAsync(templateIconsDoc).then(list => templateIconsDoc.data = new List<Doc>([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
-            doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc]));
+            const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
+            doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
+            DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
+                await Promise.all(curIcons!);
+                requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype));
+            });
         }
         return doc["template-icons"] as Doc;
     }
@@ -501,7 +504,7 @@ export class CurrentUserUtils {
     static setupDocumentCollection(doc: Doc) {
         if (doc.myDocuments === undefined) {
             doc.myDocuments = new PrefetchProxy(Docs.Create.TreeDocument([], {
-                title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true,
+                title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true,
             }));
         }
         return doc.myDocuments as Doc;
diff --git a/src/server/database.ts b/src/server/database.ts
index cfd707825..580f7f919 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -291,7 +291,7 @@ export namespace Database {
         }
     }
 
-    export const Instance: IDatabase = getDatabase();
+    export const Instance = getDatabase();
 
     export namespace Auxiliary {
 
-- 
cgit v1.2.3-70-g09d2


From 78e0b57cab3a5fea7421ca4661d890b635bb4790 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 7 May 2020 20:36:31 -0400
Subject: fixed gear in tab panes showing up.  fixed tree view titling.  fixed
 some current_user_util templates to drag as render templates, not prototypes.

---
 src/client/views/MainView.tsx                      | 21 ++++++++-
 .../views/collections/CollectionDockingView.tsx    | 12 ++---
 .../views/collections/CollectionTreeView.tsx       | 52 +++++++++-------------
 .../authentication/models/current_user_utils.ts    | 14 ++++--
 4 files changed, 57 insertions(+), 42 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 5b838446d..fad6bf295 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,12 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons';
+import {
+    faTrashAlt, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+    faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
+    faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
+    faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
+    faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
+    faThumbtack, faTree, faTv, faUndoAlt, faVideo
+} from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -105,6 +112,18 @@ export class MainView extends React.Component {
             }
         }
 
+        library.add(faTrashAlt);
+        library.add(faAngleRight);
+        library.add(faBell);
+        library.add(faTrash);
+        library.add(faCamera);
+        library.add(faExpand);
+        library.add(faCaretDown);
+        library.add(faCaretRight);
+        library.add(faCaretSquareDown);
+        library.add(faCaretSquareRight);
+        library.add(faArrowsAltH);
+        library.add(faPlus, faMinus);
         library.add(faTerminal);
         library.add(faToggleOn);
         library.add(faLocationArrow);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6f5989052..3c33d4481 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -520,11 +520,13 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
                 tab.buttonDisposer = reaction(() => ((view: Opt<DocumentView>) => view ? [view] : [])(DocumentManager.Instance.getDocumentView(doc)),
                     (views) => {
-                        ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} >
-                            <DockingViewButtonSelector views={() => views} Stack={stack} />
-                        </span>,
-                            gearSpan);
-                        tab.buttonDisposer?.();
+                        if (views.length) {
+                            ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} >
+                                <DockingViewButtonSelector views={() => views} Stack={stack} />
+                            </span>,
+                                gearSpan);
+                            tab.buttonDisposer?.();
+                        }
                     });
 
                 tab.reactComponents = [gearSpan];
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 815202cfe..92018a5b8 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,7 +1,5 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faAngleRight, faArrowsAltH, faBell, faCamera, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faExpand, faMinus, faPlus, faTrash, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction, untracked } from "mobx";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
@@ -36,7 +34,6 @@ import React = require("react");
 import { makeTemplate } from '../../util/DropConverter';
 import { TraceMobx } from '../../../new_fields/util';
 
-
 export interface TreeViewProps {
     document: Doc;
     dataDoc?: Doc;
@@ -63,24 +60,12 @@ export interface TreeViewProps {
     active: (outsideReaction?: boolean) => boolean;
     treeViewHideHeaderFields: () => boolean;
     treeViewPreventOpen: boolean;
-    renderedIds: string[];
+    renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle
     onCheckedClick?: ScriptField;
     onChildClick?: ScriptField;
     ignoreFields?: string[];
 }
 
-library.add(faTrashAlt);
-library.add(faAngleRight);
-library.add(faBell);
-library.add(faTrash);
-library.add(faCamera);
-library.add(faExpand);
-library.add(faCaretDown);
-library.add(faCaretRight);
-library.add(faCaretSquareDown);
-library.add(faCaretSquareRight);
-library.add(faArrowsAltH);
-library.add(faPlus, faMinus);
 @observer
 /**
  * Renders a treeView of a collection of documents
@@ -91,13 +76,14 @@ library.add(faPlus, faMinus);
  * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
  */
 class TreeView extends React.Component<TreeViewProps> {
+    static _editTitleScript: ScriptField | undefined;
     private _header?: React.RefObject<HTMLDivElement> = React.createRef();
     private _treedropDisposer?: DragManager.DragDropDisposer;
     private _dref = React.createRef<HTMLDivElement>();
     private _tref = React.createRef<HTMLDivElement>();
+    private _docRef = React.createRef<DocumentView>();
 
     get displayName() { return "TreeView(" + this.props.document.title + ")"; }  // this makes mobx trace() statements more descriptive
-
     get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, "fields"); }
     @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
     set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
@@ -111,7 +97,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
     childDocList(field: string) {
         const layout = Doc.LayoutField(this.props.document) instanceof Doc ? Doc.LayoutField(this.props.document) as Doc : undefined;
-        return ((this.props.dataDoc ? Cast(this.props.dataDoc[field], listSpec(Doc)) : undefined) ||
+        return ((this.props.dataDoc ? DocListCast(this.props.dataDoc[field]) : undefined) ||
             (layout ? Cast(layout[field], listSpec(Doc)) : undefined) ||
             Cast(this.props.document[field], listSpec(Doc))) as Doc[];
     }
@@ -411,7 +397,10 @@ class TreeView extends React.Component<TreeViewProps> {
     @computed
     get renderBullet() {
         const checked = this.props.document.type === DocumentType.COL ? undefined : this.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined;
-        return <div className="bullet" title="view inline" onClick={this.bulletClick} style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "inherit"), opacity: checked === "unchecked" ? undefined : 0.4 }}>
+        return <div className="bullet"
+            title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"}
+            onClick={this.bulletClick}
+            style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "inherit"), opacity: checked === "unchecked" ? undefined : 0.4 }}>
             {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down"))} />}
         </div>;
     }
@@ -425,8 +414,6 @@ class TreeView extends React.Component<TreeViewProps> {
         const focusScript = ScriptField.MakeFunction(`DocFocus(self)`);
         return [{ script: focusScript!, label: "Focus" }];
     }
-    _docRef = React.createRef<DocumentView>();
-    _editTitleScript: ScriptField | undefined;
     /**
      * Renders the EditableView title element for placement into the tree.
      */
@@ -434,8 +421,7 @@ class TreeView extends React.Component<TreeViewProps> {
     get renderTitle() {
         TraceMobx();
         const onItemDown = SetupDrag(this._tref, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId[Id], true);
-        //!this._editTitleScript && (this._editTitleScript = ScriptField.MakeFunction("setInPlace(this, 'editTitle', true)"));
-
+        (!TreeView._editTitleScript) && (TreeView._editTitleScript = ScriptField.MakeFunction("setInPlace(this, 'editTitle', true)"));
         const headerElements = (
             <>
                 <FontAwesomeIcon icon="cog" size="sm" onClick={e => this.showContextMenu(e)}></FontAwesomeIcon>
@@ -475,7 +461,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         addDocTab={this.props.addDocTab}
                         rootSelected={returnTrue}
                         pinToPres={emptyFunction}
-                        onClick={this.props.onChildClick || this._editTitleScript}
+                        onClick={this.props.onChildClick || TreeView._editTitleScript}
                         dropAction={this.props.dropAction}
                         moveDocument={this.move}
                         removeDocument={this.removeDoc}
@@ -512,12 +498,14 @@ class TreeView extends React.Component<TreeViewProps> {
                         e.stopPropagation();
                         e.preventDefault();
                     }
-                }} onPointerDown={e => {
-                    if (this.props.active(true)) {
-                        e.stopPropagation();
-                        e.preventDefault();
-                    }
-                }} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+                }}
+                    onPointerDown={e => {
+                        if (this.props.active(true)) {
+                            e.stopPropagation();
+                            e.preventDefault();
+                        }
+                    }}
+                    onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
                     {this.renderBullet}
                     {this.renderTitle}
                 </div>
@@ -790,7 +778,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         return <div id="toolbar" key="toolbar">
             <button className="toolbar-button round-button" title="Empty"
                 onClick={undoBatch(action(() => Doc.GetProto(this.props.Document)[this.props.fieldKey] = undefined))}>
-                <FontAwesomeIcon icon={faTrash} size="sm" />
+                <FontAwesomeIcon icon={"trash"} size="sm" />
             </button>
         </div >;
     }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 6e06fe931..8a0fbeabe 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -50,7 +50,7 @@ export class CurrentUserUtils {
             );
             queryTemplate.isTemplateDoc = makeTemplate(queryTemplate);
             doc["template-button-query"] = CurrentUserUtils.ficon({
-                onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'),
+                onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
                 dragFactory: new PrefetchProxy(queryTemplate) as any as Doc,
                 removeDropProperties: new List<string>(["dropAction"]), title: "query view", icon: "question-circle"
             });
@@ -66,7 +66,7 @@ export class CurrentUserUtils {
             );
             slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
             doc["template-button-slides"] = CurrentUserUtils.ficon({
-                onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'),
+                onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
                 dragFactory: new PrefetchProxy(slideTemplate) as any as Doc,
                 removeDropProperties: new List<string>(["dropAction"]), title: "presentation slide", icon: "address-card"
             });
@@ -162,7 +162,7 @@ export class CurrentUserUtils {
             long.title = "Long Description";
 
             doc["template-button-detail"] = CurrentUserUtils.ficon({
-                onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'),
+                onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
                 dragFactory: new PrefetchProxy(detailView) as any as Doc,
                 removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "window-maximize"
             });
@@ -176,7 +176,13 @@ export class CurrentUserUtils {
                 dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
             }));
         } else {
-            DocListCast(Cast(doc["template-buttons"], Doc, null)?.data); // prefetch templates
+            const curButnTypes = Cast(doc["template-buttons"], Doc, null);
+            const requiredTypes = [doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc,
+            doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc];
+            DocListCastAsync(curButnTypes.data).then(async curBtns => {
+                await Promise.all(curBtns!);
+                requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype));
+            });
         }
         return doc["template-buttons"] as Doc;
     }
-- 
cgit v1.2.3-70-g09d2


From c1f49a0974982b803c565b6ac3c8931be7c8972f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 8 May 2020 16:57:30 -0400
Subject: added " " for switching between pan/zoom & marquee.

---
 src/client/views/DocumentButtonBar.tsx             | 11 +----------
 src/client/views/GlobalKeyHandler.ts               |  5 +++++
 src/client/views/PreviewCursor.tsx                 |  2 +-
 .../views/collections/CollectionDockingView.tsx    | 23 ++++++++++++----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 14 ++++++-------
 6 files changed, 27 insertions(+), 30 deletions(-)

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

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index d58eb5875..48685302a 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -229,16 +229,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         return !targetDoc ? (null) : <div className="documentButtonBar-linker"
             title={Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}
             style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
-
-            onClick={e => {
-                if (isPinned) {
-                    DockedFrameRenderer.UnpinDoc(targetDoc);
-                }
-                else {
-                    targetDoc.sourceContext = this.view0?.props.ContainingCollectionDoc; // bcz: !! Shouldn't need this ... use search to lookup contexts dynamically
-                    DockedFrameRenderer.PinDoc(targetDoc);
-                }
-            }}>
+            onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
             <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin"
             />
         </div>;
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 6cca4d69f..e6dd014c0 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -13,6 +13,8 @@ import { InkingControl } from "./InkingControl";
 import { InkTool } from "../../new_fields/InkField";
 import { DocumentView } from "./nodes/DocumentView";
 import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
+import { CollectionFreeFormView } from "./collections/collectionFreeForm/CollectionFreeFormView";
+import { MarqueeView } from "./collections/collectionFreeForm/MarqueeView";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -65,6 +67,9 @@ export default class KeyManager {
 
     private unmodified = action((keyname: string, e: KeyboardEvent) => {
         switch (keyname) {
+            case " ":
+                MarqueeView.DragState = !MarqueeView.DragState;
+                break;
             case "escape":
                 const main = MainView.Instance;
                 InkingControl.Instance.switchTool(InkTool.None);
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index df30c1215..80c7f3d25 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -81,7 +81,7 @@ export class PreviewCursor extends React.Component<{}> {
         if (e.key !== "Escape" && e.key !== "Backspace" && e.key !== "Delete" && e.key !== "CapsLock" &&
             e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" &&
             e.key !== "Insert" && e.key !== "Home" && e.key !== "End" && e.key !== "PageUp" && e.key !== "PageDown" &&
-            e.key !== "NumLock" &&
+            e.key !== "NumLock" && e.key !== " " &&
             (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys
             !e.key.startsWith("Arrow") &&
             !e.defaultPrevented) {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 3c33d4481..c1edc0fe7 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -683,16 +683,19 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
      **/
     @undoBatch
     @action
-    public static PinDoc(doc: Doc) {
-        //add this new doc to props.Document
-        const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
-        if (curPres) {
-            const pinDoc = Doc.MakeAlias(doc);
-            pinDoc.presentationTargetDoc = doc;
-            pinDoc.presZoomButton = true;
-            Doc.AddDocToList(curPres, "data", pinDoc);
-            if (!DocumentManager.Instance.getDocumentView(curPres)) {
-                CollectionDockingView.AddRightSplit(curPres);
+    public static PinDoc(doc: Doc, unpin = false) {
+        if (unpin) DockedFrameRenderer.UnpinDoc(doc);
+        else {
+            //add this new doc to props.Document
+            const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
+            if (curPres) {
+                const pinDoc = Doc.MakeAlias(doc);
+                pinDoc.presentationTargetDoc = doc;
+                pinDoc.presZoomButton = true;
+                Doc.AddDocToList(curPres, "data", pinDoc);
+                if (!DocumentManager.Instance.getDocumentView(curPres)) {
+                    CollectionDockingView.AddRightSplit(curPres);
+                }
             }
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 17c1dd44a..5d3d8eb4f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -563,7 +563,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     document.removeEventListener("pointerup", this.onPointerUp);
                     return;
                 }
-                this.pan(e);
+                (MarqueeView.DragState || e.altKey) && this.pan(e);
             }
             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();
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index a777f6fe5..3c67f8c6a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -19,8 +19,6 @@ import React = require("react");
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { RichTextField } from "../../../../new_fields/RichTextField";
 import { CollectionView } from "../CollectionView";
-import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
-import { ScriptField } from "../../../../new_fields/ScriptField";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -39,7 +37,7 @@ interface MarqueeViewProps {
 @observer
 export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps>
 {
-    private _mainCont = React.createRef<HTMLDivElement>();
+    @observable public static DragState = true;
     @observable _lastX: number = 0;
     @observable _lastY: number = 0;
     @observable _downX: number = 0;
@@ -168,9 +166,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = this._lastX = e.clientX;
         this._downY = this._lastY = e.clientY;
-        if (e.button === 2 || (e.button === 0 && e.altKey)) {
+        if (e.button === 2 || (e.button === 0 && (e.altKey || !MarqueeView.DragState))) {
             this.setPreviewCursor(e.clientX, e.clientY, true);
-            if (e.altKey) {
+            if (e.altKey || !MarqueeView.DragState) {
                 //e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
                 e.preventDefault();
             }
@@ -195,7 +193,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         } else {
             this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this
         }
-        if (e.altKey) {
+        if (e.altKey || !MarqueeView.DragState) {
             e.preventDefault();
         }
     }
@@ -230,7 +228,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         };
         document.addEventListener("pointerdown", hideMarquee);
 
-        if (e.altKey) {
+        if (e.altKey || !MarqueeView.DragState) {
             e.preventDefault();
         }
     }
@@ -614,7 +612,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     render() {
         return <div className="marqueeView"
-            style={{ overflow: StrCast(this.props.Document._overflow), }}
+            style={{ overflow: StrCast(this.props.Document._overflow), cursor: MarqueeView.DragState ? "hand" : "crosshair" }}
             onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
             {this._visible ? this.marqueeDiv : null}
             {this.props.children}
-- 
cgit v1.2.3-70-g09d2


From 4914965affb984e04e847460ce1b5750807b316f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 9 May 2020 15:56:11 -0400
Subject: fixed missing gear in golden layout.  fixed double dragging events. 
 changed dragging to give prefernce to modifier keys over dropactions. 
 changed names of Documents to Catalog.   added document copy past with ctrl
 c/ctrl v

---
 src/client/util/DragManager.ts                         |  7 ++-----
 src/client/views/GlobalKeyHandler.ts                   | 10 ++++++++++
 src/client/views/MainView.tsx                          |  3 +--
 src/client/views/PreviewCursor.tsx                     | 13 +++++++++++++
 src/client/views/collections/CollectionDockingView.tsx |  2 +-
 src/client/views/collections/CollectionSubView.tsx     | 10 ++++++----
 src/client/views/collections/CollectionTreeView.tsx    |  3 +--
 src/client/views/collections/CollectionView.tsx        | 10 +++++-----
 src/client/views/nodes/DocumentView.tsx                | 15 ++++++++++-----
 src/server/authentication/models/current_user_utils.ts | 14 +++++++-------
 10 files changed, 56 insertions(+), 31 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index cc8cc38dd..4f547e2f7 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -176,7 +176,7 @@ export namespace DragManager {
         element: HTMLElement,
         dropFunc: (e: Event, de: DropEvent) => void,
         doc?: Doc,
-        preDropFunc?: (e: Event, de: DropEvent) => void,
+        preDropFunc?: (e: Event, de: DropEvent, targetAction: dropActionType) => void,
     ): DragDropDisposer {
         if ("canDrop" in element.dataset) {
             throw new Error(
@@ -187,10 +187,7 @@ export namespace DragManager {
         const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
         const preDropHandler = (e: Event) => {
             const de = (e as CustomEvent<DropEvent>).detail;
-            if (de.complete.docDragData && doc?.targetDropAction) {
-                de.complete.docDragData.dropAction = StrCast(doc.targetDropAction) as dropActionType;
-            }
-            preDropFunc?.(e, de);
+            preDropFunc?.(e, de, StrCast(doc?.targetDropAction) as dropActionType);
         };
         element.addEventListener("dashOnDrop", handler);
         doc && element.addEventListener("dashPreDrop", preDropHandler);
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index c73e1a66a..d9281ac24 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -15,6 +15,7 @@ import { DocumentView } from "./nodes/DocumentView";
 import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
 import { CollectionFreeFormView } from "./collections/collectionFreeForm/CollectionFreeFormView";
 import { MarqueeView } from "./collections/collectionFreeForm/MarqueeView";
+import { Id } from "../../new_fields/FieldSymbols";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -240,8 +241,17 @@ export default class KeyManager {
                 break;
             case "a":
             case "v":
+                stopPropagation = false;
+                preventDefault = false;
+                break;
             case "x":
             case "c":
+                SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText("__DashDocId:" + SelectionManager.SelectedDocuments()[0].Document[Id]);
+                // window.getSelection()?.removeAllRanges();
+                // let range = document.createRange();
+                // range.selectNode(SelectionManager.SelectedDocuments()[0].ContentDiv!);
+                // window.getSelection()?.addRange(range);
+                // document.execCommand('copy');
                 stopPropagation = false;
                 preventDefault = false;
                 break;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index fad6bf295..b6dfe60e5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -249,8 +249,7 @@ export class MainView extends React.Component {
             title: "Collection " + workspaceCount,
         };
         const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
-        Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myDocuments as Doc), "data", freeformDoc);
-        const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myDocuments as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
+        const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
 
         const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
         mainDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!]);
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 80c7f3d25..b9036bf1e 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -6,6 +6,7 @@ import "./PreviewCursor.scss";
 import { Docs } from '../documents/Documents';
 import { Doc } from '../../new_fields/Doc';
 import { Transform } from "../util/Transform";
+import { DocServer } from '../DocServer';
 
 @observer
 export class PreviewCursor extends React.Component<{}> {
@@ -49,6 +50,18 @@ export class PreviewCursor extends React.Component<{}> {
                     }));
                 }
 
+                if (e.clipboardData.getData("text/plain").includes("__DashDocId:")) {
+                    const docid = e.clipboardData.getData("text/plain").split("__DashDocId:")[1];
+                    return DocServer.GetRefField(docid).then(doc => {
+                        if (doc instanceof Doc) {
+                            const alias = Doc.MakeAlias(doc);
+                            alias.x = newPoint[0];
+                            alias.y = newPoint[1];
+                            PreviewCursor._addDocument(alias);
+                        }
+                    });
+                }
+
                 // creates text document
                 return PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument("", {
                     _width: 500,
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c1edc0fe7..6fdb96f0d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -527,7 +527,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                                 gearSpan);
                             tab.buttonDisposer?.();
                         }
-                    });
+                    }, { fireImmediately: true });
 
                 tab.reactComponents = [gearSpan];
                 tab.element.append(gearSpan);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d1c2d9a76..4fef16315 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -16,7 +16,7 @@ import { DocServer } from "../../DocServer";
 import { Docs, DocumentOptions } from "../../documents/Documents";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { Networking } from "../../Network";
-import { DragManager } from "../../util/DragManager";
+import { DragManager, dropActionType } from "../../util/DragManager";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
 import { InteractionUtils } from "../../util/InteractionUtils";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -195,10 +195,12 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
         }
 
-        protected onInternalPreDrop(e: Event, de: DragManager.DropEvent) {
+        protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetAction: dropActionType) {
             if (de.complete.docDragData) {
-                if (de.complete.docDragData.draggedDocuments.some(d => this.childDocs.includes(d))) {
-                    de.complete.docDragData.dropAction = "move";
+                // if targetDropAction is, say 'alias', but we're just dragging within a collection, we want to ignore the targetAction.
+                // otherwise, the targetAction should become the actual action (which can still be overridden by the userDropAction -eg, shift/ctrl keys)
+                if (targetAction && !de.complete.docDragData.draggedDocuments.some(d => d.context === this.props.Document && this.childDocs.includes(d))) {
+                    de.complete.docDragData.dropAction = targetAction;
                 }
                 e.stopPropagation();
             }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 58d021b5c..7086ba380 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -410,7 +410,6 @@ class TreeView extends React.Component<TreeViewProps> {
     @computed
     get renderTitle() {
         TraceMobx();
-        const onItemDown = SetupDrag(this._tref, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId[Id], true);
         (!TreeView._editTitleScript) && (TreeView._editTitleScript = ScriptField.MakeFunction("setInPlace(self, 'editTitle', true)"));
         const headerElements = (
             <>
@@ -432,7 +431,7 @@ class TreeView extends React.Component<TreeViewProps> {
             <FontAwesomeIcon title="open in pane on right" icon="angle-right" size="lg" />
         </div>);
         return <>
-            <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`} onPointerDown={onItemDown}
+            <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`}
                 style={{
                     fontWeight: this.props.document.searchMatch ? "bold" : undefined,
                     textDecoration: Doc.GetT(this.props.document, "title", "string", true) ? "underline" : undefined,
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index b96f8150d..6c96baede 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -69,15 +69,15 @@ export enum CollectionViewType {
     Pile = "pileup"
 }
 export interface CollectionViewCustomProps {
-    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)
+    filterAddDocument: (doc: 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)
     childLayoutTemplate?: () => Opt<Doc>;  // specify a layout Doc template to use for children of the collection
     childLayoutString?: string;  // specify a layout string to use for children of the collection
 }
 
 export interface CollectionRenderProps {
-    addDocument: (document: Doc) => boolean;
-    removeDocument: (document: Doc) => boolean;
-    moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+    addDocument: (document: Doc | Doc[]) => boolean;
+    removeDocument: (document: Doc | Doc[]) => boolean;
+    moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     PanelWidth: () => number;
@@ -152,7 +152,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     // otherwise, the document being moved must be able to be removed from its container before
     // moving it into the target.  
     @action.bound
-    moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => {
+    moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
         if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
             return true;
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e3b69b8f3..ebfc38a54 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym } from "../../../new_fields/Doc";
 import { Document } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { InkTool } from '../../../new_fields/InkField';
@@ -15,7 +15,7 @@ import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils";
+import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { ClientRecommender } from '../../ClientRecommender';
 import { DocServer } from "../../DocServer";
@@ -549,7 +549,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
-                    this.startDragging(this._downX, this._downY, this.props.dropAction ? this.props.dropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
+                    this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && "alias") || (this.props.dropAction || this.Document.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
@@ -721,6 +721,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         let open = cm.findByDescription("Add a Perspective...");
         const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+        openItems.push({ description: "New Echo        ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "layer-group" });
         openItems.push({ description: "Open Fields     ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
         templateDoc && openItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
         if (!open) {
@@ -864,8 +865,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
         cm.addItem({
             description: `path: ${path}`, event: () => {
-                this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
-                Doc.linkFollowHighlight(this.props.Document);
+                if (this.props.LibraryPath !== emptyPath) {
+                    this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
+                    Doc.linkFollowHighlight(this.props.Document);
+                } else {
+                    Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myCatalog as Doc), "data", this.props.Document[DataSym]);
+                }
             }, icon: "check"
         });
     }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index a776b6f10..bebf77a8c 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -507,13 +507,13 @@ export class CurrentUserUtils {
 
         return doc.myWorkspaces as Doc;
     }
-    static setupDocumentCollection(doc: Doc) {
-        if (doc.myDocuments === undefined) {
-            doc.myDocuments = new PrefetchProxy(Docs.Create.TreeDocument([], {
-                title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true,
+    static setupCatalog(doc: Doc) {
+        if (doc.myCatalog === undefined) {
+            doc.myCatalog = new PrefetchProxy(Docs.Create.TreeDocument([], {
+                title: "CATALOG", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true,
             }));
         }
-        return doc.myDocuments as Doc;
+        return doc.myCatalog as Doc;
     }
     static setupRecentlyClosed(doc: Doc) {
         // setup Recently Closed library item
@@ -533,7 +533,7 @@ export class CurrentUserUtils {
     // setup the Library button which will display the library panel.  This panel includes a collection of workspaces, documents, and recently closed views
     static setupLibraryPanel(doc: Doc, sidebarContainer: Doc) {
         const workspaces = CurrentUserUtils.setupWorkspaces(doc);
-        const documents = CurrentUserUtils.setupDocumentCollection(doc);
+        const documents = CurrentUserUtils.setupCatalog(doc);
         const recentlyClosed = CurrentUserUtils.setupRecentlyClosed(doc);
 
         if (doc["tabs-button-library"] === undefined) {
@@ -541,7 +541,7 @@ export class CurrentUserUtils {
                 _width: 50, _height: 25, title: "Library", _fontSize: 10,
                 letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
                 sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], {
-                    title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "move", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true
+                    title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true
                 })) as any as Doc,
                 targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
                 onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
-- 
cgit v1.2.3-70-g09d2


From 5726d19a615385b0bcdf1e3a944056d31074edbb Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 10 May 2020 17:02:04 -0400
Subject: made opening docs on right not create aliases.  made clicking on an
 original document trigger replacing it with an alias.

---
 .../views/collections/CollectionDockingView.tsx    | 38 +++++++++++++++++-----
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/CollectionView.tsx    |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  7 ++++
 src/client/views/nodes/DocumentView.tsx            | 13 +++++---
 5 files changed, 47 insertions(+), 14 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6fdb96f0d..6e9b2386d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -71,8 +71,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         (window as any).ReactDOM = ReactDOM;
         DragManager.StartWindowDrag = this.StartOtherDrag;
     }
-    hack: boolean = false;
-    undohack: any = null;
     public StartOtherDrag = (e: any, dragDocs: Doc[]) => {
         let config: any;
         if (dragDocs.length === 1) {
@@ -192,6 +190,29 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         return retVal;
     }
 
+    @undoBatch
+    @action
+    public static ReplaceTab(document: Doc, stack: any): Opt<Doc> {
+        if (!CollectionDockingView.Instance) return undefined;
+        const instance = CollectionDockingView.Instance;
+        const replaceTab = (doc: Doc, child: any): Opt<Doc> => {
+            for (let i = 0; i < child.contentItems.length; i++) {
+                if (child.contentItems[i].isRow || child.contentItems[i].isColumn || child.contentItems[i].isStack) {
+                    const val = replaceTab(doc, child.contentItems[i]);
+                    if (val) return val;
+                } else if (child.contentItems[i].config.component === "DocumentFrameRenderer" &&
+                    child.contentItems[i].config.props.documentId === doc[Id]) {
+                    const alias = Doc.MakeAlias(doc);
+                    child.contentItems[i].config.props.documentId = alias[Id];
+                    child.contentItems[i].config.title = alias.title;
+                    instance.stateChanged();
+                    return alias;
+                }
+            }
+            return undefined;
+        }
+        return replaceTab(document, instance._goldenLayout.root);
+    }
 
     //
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to the right of that split
@@ -455,12 +476,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         const json = JSON.stringify(this._goldenLayout.toConfig());
         this.props.Document.dockingConfig = json;
         this.updateDataField(json);
-
-        if (this.undohack && !this.hack) {
-            this.undohack.end();
-            this.undohack = undefined;
-        }
-        this.hack = false;
     }
 
     itemDropped = () => {
@@ -789,6 +804,13 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             return CollectionDockingView.AddRightSplit(doc, libraryPath);
         } else if (location === "close") {
             return CollectionDockingView.CloseRightSplit(doc);
+        } else if (location === "replace") {
+            const alias = CollectionDockingView.ReplaceTab(doc, this._stack);
+            if (alias) {
+                runInAction(() => this._document = alias);
+                return true;
+            }
+            return false;
         } else {// if (location === "inPlace") {
             return CollectionDockingView.Instance.AddTab(this._stack, doc, libraryPath);
         }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8cd34e7ed..2f332e77d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -108,7 +108,7 @@ class TreeView extends React.Component<TreeViewProps> {
             Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey]));
     }
 
-    @undoBatch openRight = () => this.props.addDocTab(this.props.dropAction === "alias" ? Doc.MakeAlias(this.props.document) : this.props.document, "onRight", this.props.libraryPath);
+    @undoBatch openRight = () => this.props.addDocTab(this.props.document, "onRight", this.props.libraryPath);
     @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
         return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 0841d9680..ac9f64d94 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -128,6 +128,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         const added = docs.filter(d => !docList.includes(d));
         if (added.length) {
             added.map(doc => doc.context = this.props.Document);
+            added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add));
             targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
             targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
         }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ed3e50f45..61081618a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -339,6 +339,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
         this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
         if (e.button === 0 && (!e.shiftKey || this._hitCluster) && !e.altKey && !e.ctrlKey && this.props.active(true)) {
+
+            if (!this.props.Document.aliasOf && !this.props.ContainingCollectionView) {
+                this.props.addDocTab(this.props.Document, "replace");
+                e.stopPropagation();
+                e.preventDefault();
+                return;
+            }
             document.removeEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ebfc38a54..d2dabe7ec 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -305,11 +305,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         }, console.log);
                         func();
                     } else {
-                        const fullScreenAlias = Doc.MakeAlias(this.props.Document);
-                        if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
-                            fullScreenAlias.layoutKey = "layout_fullScreen";
-                        }
-                        UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap");
+                        UndoManager.RunInBatch(() => {
+                            if (StrCast(this.props.Document.layoutKey) !== "layout_fullScreen" && this.props.Document.layout_fullScreen) {
+                                const fullScreenAlias = Doc.MakeAlias(this.props.Document);
+                                fullScreenAlias.layoutKey = "layout_fullScreen";
+                                this.props.addDocTab(fullScreenAlias, "inTab");
+                                this.props.addDocTab(this.props.Document, "inTab");
+                            }
+                        }, "double tap");
                         SelectionManager.DeselectAll();
                         Doc.UnBrushDoc(this.props.Document);
                     }
-- 
cgit v1.2.3-70-g09d2


From 56161b9541a8232fca11e69ffb1bf22864a941a1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 11 May 2020 01:51:12 -0400
Subject: compile error fixes. fix to freeform view zooming when zoomed way
 out.

---
 src/client/views/GlobalKeyHandler.ts                                  | 2 +-
 src/client/views/TemplateMenu.tsx                                     | 4 ++--
 src/client/views/collections/CollectionDockingView.tsx                | 2 +-
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 2 +-
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx       | 2 +-
 src/client/views/nodes/DocumentView.tsx                               | 1 +
 6 files changed, 7 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 3fd14735b..1b16b15bb 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -265,7 +265,7 @@ export default class KeyManager {
                     const bds = DocumentDecorations.Instance.Bounds;
                     const pt = [bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2];
                     const text = `__DashDocId(${pt[0]},${pt[1]}):` + SelectionManager.SelectedDocuments().map(dv => dv.Document[Id]).join(":");
-                    SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText(text);;
+                    SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText(text);
                     stopPropagation = false;
                     preventDefault = false;
                 }
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 5f300372f..43debfe99 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -113,8 +113,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     render() {
         const firstDoc = this.props.docViews[0].props.Document;
         const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", "");
-        const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null));
-        const addedTypes = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data);
+        const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data);
+        const addedTypes = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
         const layout = Doc.Layout(firstDoc);
         const templateMenu: Array<JSX.Element> = [];
         this.props.templates.forEach((checked, template) =>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6e9b2386d..296b2453b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -210,7 +210,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 }
             }
             return undefined;
-        }
+        };
         return replaceTab(document, instance._goldenLayout.root);
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 61081618a..d24ba6bb0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -724,7 +724,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const [x, y] = this.getTransform().transformPoint(pointX, pointY);
         const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
 
-        if (localTransform.Scale >= 0.15) {
+        if (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling()) {
             const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40);
             this.props.Document.scale = Math.abs(safeScale);
             this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 35d925bca..8e20b39d2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -245,7 +245,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         } else {
             this._downX = x;
             this._downY = y;
-            PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.addDocTab, this.props.nudge);
+            PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
         }
     });
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d2dabe7ec..aca0433bb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -310,6 +310,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                                 const fullScreenAlias = Doc.MakeAlias(this.props.Document);
                                 fullScreenAlias.layoutKey = "layout_fullScreen";
                                 this.props.addDocTab(fullScreenAlias, "inTab");
+                            } else {
                                 this.props.addDocTab(this.props.Document, "inTab");
                             }
                         }, "double tap");
-- 
cgit v1.2.3-70-g09d2


From a3839d4b5a2180e2e80fa79f6eb4459e2976f380 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 11 May 2020 14:59:54 -0400
Subject: changed Cors behavior for WebBox(On for everything except
 GoogldDocs).  Update Freeze to work for ctrl-resizing text, and showing full
 screen.  reorged context menus.

---
 src/client/views/DocumentButtonBar.tsx             |  2 +-
 src/client/views/DocumentDecorations.scss          |  1 +
 src/client/views/DocumentDecorations.tsx           |  1 +
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/PreviewCursor.tsx                 |  2 +-
 .../views/collections/CollectionDockingView.tsx    | 12 ++++----
 .../views/collections/CollectionStackingView.tsx   |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 27 +++++++++---------
 src/client/views/nodes/DocumentView.tsx            | 25 ++++++----------
 src/client/views/nodes/ImageBox.tsx                | 33 +++++++++++++---------
 src/client/views/nodes/WebBox.tsx                  |  8 ++----
 .../views/nodes/formattedText/FormattedTextBox.tsx | 18 +++++++++++-
 .../formattedText/FormattedTextBoxComment.tsx      |  2 +-
 src/new_fields/Doc.ts                              | 26 +++++++++++------
 .../authentication/models/current_user_utils.ts    |  2 +-
 15 files changed, 94 insertions(+), 67 deletions(-)

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

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 48685302a..10d9ec401 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -194,7 +194,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                     e.preventDefault();
                     let googleDoc = await Cast(dataDoc.googleDoc, Doc);
                     if (!googleDoc) {
-                        const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false };
+                        const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false };
                         googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
                         dataDoc.googleDoc = googleDoc;
                     }
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 4f34eb9e3..15eb537da 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -72,6 +72,7 @@ $linkGap : 3px;
         grid-column-start: 5;
         grid-column-end: 7;
         border-radius: 100%;
+        background: dimgray;
 
         .borderRadiusTooltip {
             width: 10px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 1054822c7..9669c21a9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -301,6 +301,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         }
 
         SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+            if (e.ctrlKey && !element.props.Document._nativeHeight) element.toggleNativeDimensions();
             if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
                 const doc = Document(element.rootDoc);
                 let nwidth = doc._nativeWidth || 0;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cbaae63bc..560dd5d11 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -249,6 +249,7 @@ export class MainView extends React.Component {
             _width: this._panelWidth * .7,
             _height: this._panelHeight,
             title: "Collection " + workspaceCount,
+            _LODdisable: true
         };
         const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
         const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 14b5b3fce..2ddca369a 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -46,7 +46,7 @@ export class PreviewCursor extends React.Component<{}> {
                 else if (re.test(e.clipboardData.getData("text/plain"))) {
                     const url = e.clipboardData.getData("text/plain");
                     undoBatch(() => PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
-                        title: url, _width: 500, _height: 300,
+                        title: url, _width: 500, _height: 300, UseCors: true,
                         // nativeWidth: 300, nativeHeight: 472.5,
                         x: newPoint[0], y: newPoint[1]
                     })))();
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 296b2453b..50e3b73eb 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -755,8 +755,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
 
     get layoutDoc() { return this._document && Doc.Layout(this._document); }
-    panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) : this._panelWidth;
-    panelHeight = () => this._panelHeight;
+    nativeAspect = () => this.nativeWidth() ? this.nativeWidth() / this.nativeHeight() : 0;
+    panelWidth = () => this.layoutDoc?.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) :
+        (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth);
+    panelHeight = () => this.nativeAspect() && this.nativeAspect() > this._panelWidth / this._panelHeight ? this._panelWidth / this.nativeAspect() : this._panelHeight;
 
     nativeWidth = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeWidth) || this._panelWidth : 0;
     nativeHeight = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeHeight) || this._panelHeight : 0;
@@ -794,7 +796,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         return Transform.Identity();
     }
     get previewPanelCenteringOffset() { return this.nativeWidth() ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
-    get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this.panelWidth() * 100}%` : undefined; }
+    get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this._panelWidth * 100}%` : undefined; }
 
     addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => {
         SelectionManager.DeselectAll();
@@ -832,8 +834,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             ContentScaling={this.contentScaling}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
-            NativeHeight={returnZero}
-            NativeWidth={returnZero}
+            NativeHeight={this.nativeHeight}
+            NativeWidth={this.nativeWidth}
             ScreenToLocalTransform={this.ScreenToLocalTransform}
             renderDepth={0}
             parentActive={returnTrue}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index c199988c0..979e9e3f8 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -432,6 +432,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
         if (!e.isPropagationStopped()) {
             const subItems: ContextMenuProps[] = [];
             subItems.push({ description: `${this.props.Document.fillColumn ? "Variable Size" : "Autosize"} Column`, event: () => this.props.Document.fillColumn = !this.props.Document.fillColumn, icon: "plus" });
+            subItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
             ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" });
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d24ba6bb0..129b245b8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -113,8 +113,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
         this.Document.scale || 1)
 
-    private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
-    private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
+    private centeringShiftX = () => !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / this.contentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
+    private centeringShiftY = () => !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / this.contentScaling : 0;// shift so pan position is at center of window for non-overlay collections
     private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
     private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1);
     private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
@@ -340,12 +340,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
         if (e.button === 0 && (!e.shiftKey || this._hitCluster) && !e.altKey && !e.ctrlKey && this.props.active(true)) {
 
-            if (!this.props.Document.aliasOf && !this.props.ContainingCollectionView) {
-                this.props.addDocTab(this.props.Document, "replace");
-                e.stopPropagation();
-                e.preventDefault();
-                return;
-            }
+            // if (!this.props.Document.aliasOf && !this.props.ContainingCollectionView) {
+            //     this.props.addDocTab(this.props.Document, "replace");
+            //     e.stopPropagation();
+            //     e.preventDefault();
+            //     return;
+            // }
             document.removeEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointermove", this.onPointerMove);
@@ -1068,13 +1068,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
             }));
 
-        // size contents according to dimensions of containing panel and current scale factor of collection
         if (this.props.isAnnotationOverlay) {
             this.props.Document.scale = Math.max(1, NumCast(this.props.Document.scale));
-        } else if (this.props.PanelWidth() && this.props.Document[WidthSym]() && !this.props.isAnnotationOverlay) {
-            this.props.Document.scale = this.props.PanelWidth() / this.props.Document[WidthSym]() * NumCast(this.props.Document.scale, 1);
-            this.props.Document._width = this.props.PanelWidth();
-            this.props.Document._height = this.props.PanelHeight();
         }
 
         return elements;
@@ -1129,6 +1124,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             });
         }, "arrange contents");
     }
+    @undoBatch
+    @action
+    toggleNativeDimensions = () => {
+        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight());
+    }
 
     private thumbIdentifier?: number;
 
@@ -1138,6 +1138,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
 
         optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+        optionItems.push({ description: !this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" });
         optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
         optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index aca0433bb..6c9ade83f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -648,18 +648,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch
     @action
-    public static unfreezeNativeDimensions(layoutDoc: Doc) {
-        layoutDoc._nativeWidth = undefined;
-        layoutDoc._nativeHeight = undefined;
-    }
-
     toggleNativeDimensions = () => {
-        if (this.Document._nativeWidth || this.Document._nativeHeight) {
-            DocumentView.unfreezeNativeDimensions(this.layoutDoc);
-        }
-        else {
-            Doc.freezeNativeDimensions(this.layoutDoc, this.props.PanelWidth(), this.props.PanelHeight());
-        }
+        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.PanelWidth(), this.props.PanelHeight());
     }
 
     @undoBatch
@@ -735,10 +725,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         let options = cm.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
-        optionItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
-        optionItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
-        optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
-        optionItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
         if (!options) {
             options = { description: "Options...", subitems: optionItems, icon: "compass" };
             cm.addItem(options);
@@ -757,6 +743,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
         !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
+
         const funcs: ContextMenuProps[] = [];
         if (this.Document.onDragStart) {
             funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
@@ -768,7 +755,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const more = cm.findByDescription("More...");
         const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : [];
         moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
-        moreItems.push({ description: !this.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" });
+        moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
+        moreItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+        moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
 
         if (!ClientUtils.RELEASE) {
             // let copies: ContextMenuProps[] = [];
@@ -794,6 +783,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             // a.download = `DocExport-${this.props.Document[Id]}.zip`;
             // a.click();
         });
+
         const recommender_subitems: ContextMenuProps[] = [];
 
         recommender_subitems.push({
@@ -826,6 +816,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
         moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
         !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+
+        cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
+
         runInAction(() => {
             if (!ClientUtils.RELEASE) {
                 const setWriteMode = (mode: DocServer.WriteMode) => {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 4153c184e..d85373e98 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -157,21 +157,20 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
         const field = Cast(this.dataDoc[this.fieldKey], ImageField);
         if (field) {
             const funcs: ContextMenuProps[] = [];
-            funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
             funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" });
-            funcs.push({
-                description: "Reset Native Dimensions", event: action(async () => {
-                    const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
-                    const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
-                    if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
-                        this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH;
-                        this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight();
-                    } else {
-                        this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth();
-                        this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW;
-                    }
-                }), icon: "expand-arrows-alt"
-            });
+            // funcs.push({
+            //     description: "Reset Native Dimensions", event: action(async () => {
+            //         const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
+            //         const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
+            //         if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
+            //             this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH;
+            //             this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight();
+            //         } else {
+            //             this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth();
+            //             this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW;
+            //         }
+            //     }), icon: "expand-arrows-alt"
+            // });
 
             const existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers...");
             const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
@@ -181,6 +180,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
             !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });
 
             ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+
+
+            const existingMore = ContextMenu.Instance.findByDescription("More...");
+            const mores: ContextMenuProps[] = existingMore && "subitems" in existingMore ? existingMore.subitems : [];
+            mores.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
+            !existingMore && ContextMenu.Instance.addItem({ description: "More...", subitems: mores, icon: "hand-point-right" });
         }
     }
 
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 8239d6fdf..384a6e8a5 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -129,14 +129,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         }
     }
 
-    toggleNativeDimensions = () => {
+    toggleAnnotationMode = () => {
         if (!this.layoutDoc.isAnnotating) {
-            //DocumentView.unfreezeNativeDimensions(this.layoutDoc);
             this.layoutDoc.lockedTransform = false;
             this.layoutDoc.isAnnotating = true;
         }
         else {
-            //Doc.freezeNativeDimensions(this.layoutDoc, this.props.PanelWidth(), this.props.PanelHeight());
             this.layoutDoc.lockedTransform = true;
             this.layoutDoc.isAnnotating = false;
         }
@@ -158,10 +156,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                             <FontAwesomeIcon icon="caret-up" size="2x" />
                         </button>
                         <div className="webBox-buttons" style={{ display: this._collapsed ? "none" : "flex" }}>
-                            <div className="webBox-freeze" title={"Annotate"} style={{ background: this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleNativeDimensions} >
+                            <div className="webBox-freeze" title={"Annotate"} style={{ background: this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleAnnotationMode} >
                                 <FontAwesomeIcon icon={faPen} size={"2x"} />
                             </div>
-                            <div className="webBox-freeze" title={"Select"} style={{ background: !this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleNativeDimensions} >
+                            <div className="webBox-freeze" title={"Select"} style={{ background: !this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleAnnotationMode} >
                                 <FontAwesomeIcon icon={faMousePointer} size={"2x"} />
                             </div>
                             <input className="webpage-urlInput"
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f3c277e20..2c51397eb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -405,6 +405,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this.layoutDoc._sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%";
         return false;
     }
+    @undoBatch
+    @action
+    toggleNativeDimensions = () => {
+        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight());
+    }
 
     public static get DefaultLayout(): Doc | string | undefined {
         return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null);
@@ -438,6 +443,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
             }, icon: "eye"
         });
+        //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
+        funcs.push({ description: !this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" });
         funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
         funcs.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
         funcs.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
@@ -649,10 +656,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }
             }
         );
-        this._disposers.height = reaction(
+        this._disposers.autoHeight = reaction(
             () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight],
             () => this.tryUpdateHeight()
         );
+        this._disposers.height = reaction(
+            () => this.layoutDoc[HeightSym](),
+            height => height <= 20 && (this.layoutDoc._autoHeight = true)
+        );
 
         this.setupEditor(this.config, this.props.fieldKey);
 
@@ -1206,6 +1217,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     render() {
         TraceMobx();
         const style: { [key: string]: any } = {};
+        const scale = this.props.ContentScaling() * NumCast(this.layoutDoc.scale, 1);
         const divKeys = ["width", "height", "background"];
         const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string:  { script }  into a value
             return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ self: this.rootDoc, this: this.layoutDoc }).result as string || "";
@@ -1258,6 +1270,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
                     <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
                         style={{
+                            transform: `scale(${scale})`,
+                            transformOrigin: "top left",
+                            width: `${100 / scale}%`,
+                            height: `${100 / scale}%`,
                             padding: `${NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0)}px`,
                             pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
                         }} />
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 9ad5aafb8..a33717855 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -91,7 +91,7 @@ export class FormattedTextBoxComment {
                             (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
                     }
                 } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
-                    textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), "onRight");
+                    textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, UseCors: true }), "onRight");
                 }
                 keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
                     FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index e0627fb37..a1e1e11b1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -20,7 +20,6 @@ import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, u
 import { Docs, DocumentOptions } from "../client/documents/Documents";
 import { PdfField, VideoField, AudioField, ImageField } from "./URLField";
 import { LinkManager } from "../client/util/LinkManager";
-import { string32 } from "../../deploy/assets/pdf.worker";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -936,19 +935,28 @@ export namespace Doc {
             }
         }
     }
-
-    export function freezeNativeDimensions(layoutDoc: Doc, width: number, height: number): void {
-        layoutDoc._autoHeight = false;
-        if (!layoutDoc._nativeWidth) {
-            layoutDoc._nativeWidth = NumCast(layoutDoc._width, width);
-            layoutDoc._nativeHeight = NumCast(layoutDoc._height, height);
-        }
-    }
     export function assignDocToField(doc: Doc, field: string, id: string) {
         DocServer.GetRefField(id).then(layout => layout instanceof Doc && (doc[field] = layout));
         return id;
     }
 
+    export function toggleNativeDimensions(layoutDoc: Doc, contentScale: number, panelWidth: number, panelHeight: number) {
+        runInAction(() => {
+            if (layoutDoc._nativeWidth || layoutDoc._nativeHeight) {
+                layoutDoc.scale = NumCast(layoutDoc.scale, 1) * contentScale;
+                layoutDoc._nativeWidth = undefined;
+                layoutDoc._nativeHeight = undefined;
+            }
+            else {
+                layoutDoc._autoHeight = false;
+                if (!layoutDoc._nativeWidth) {
+                    layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth);
+                    layoutDoc._nativeHeight = NumCast(layoutDoc._height, panelHeight);
+                }
+            }
+        });
+    }
+
     export function isDocPinned(doc: Doc) {
         //add this new doc to props.Document
         const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index bebf77a8c..b6ff10bab 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -315,7 +315,7 @@ export class CurrentUserUtils {
                 { _width: 250, _height: 250, title: "container" });
         }
         if (doc.emptyWebpage === undefined) {
-            doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600 });
+            doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600, UseCors: true });
         }
         return [
             { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
-- 
cgit v1.2.3-70-g09d2


From f1473fc5bf7f2b3109be358ae14d28725240284c Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 11 May 2020 22:30:33 -0400
Subject: cleaned up menu items.  improved snapping with fixed aspect items,
 but not perfect.

---
 src/client/documents/Documents.ts                  |  1 +
 src/client/util/DragManager.ts                     | 14 ++--
 src/client/views/DocumentDecorations.tsx           | 41 ++++++++++--
 src/client/views/MainView.tsx                      |  4 +-
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 src/client/views/collections/CollectionView.tsx    |  1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 21 +++---
 src/client/views/nodes/DocumentView.tsx            | 75 ++++++++--------------
 src/client/views/nodes/ImageBox.tsx                |  4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 37 ++++-------
 10 files changed, 101 insertions(+), 99 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a87a77e1d..95b8daafc 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -150,6 +150,7 @@ export interface DocumentOptions {
     dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
     onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
     clipboard?: Doc;
+    UseCors?: boolean;
     icon?: string;
     sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
     targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4f547e2f7..1ee4c57a2 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -256,7 +256,8 @@ export namespace DragManager {
         SnappingManager.setSnapLines(horizLines, vertLines);
     }
 
-    export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) {
+    // snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line 
+    export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number, oneAxis: boolean = false) {
         const snapThreshold = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10);
         const snapVal = (pts: number[], drag: number, snapLines: number[]) => {
             if (snapLines.length) {
@@ -265,14 +266,15 @@ export namespace DragManager {
                 const closestPts = rangePts.map(pt => snapLines.reduce((nearest, curr) => Math.abs(nearest - pt) > Math.abs(curr - pt) ? curr : nearest));
                 const closestDists = rangePts.map((pt, i) => Math.abs(pt - closestPts[i]));
                 const minIndex = closestDists[0] < closestDists[1] && closestDists[0] < closestDists[2] ? 0 : closestDists[1] < closestDists[2] ? 1 : 2;
-                return closestDists[minIndex] < snapThreshold ? closestPts[minIndex] + offs[minIndex] : drag;
+                return closestDists[minIndex] < snapThreshold ? [closestDists[minIndex], closestPts[minIndex] + offs[minIndex]] : [Number.MAX_VALUE, drag];
             }
-            return drag;
+            return [Number.MAX_VALUE, drag];
         };
-
+        const xsnap = snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines());
+        const ysnap = snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines());
         return {
-            thisX: snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines()),
-            thisY: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines())
+            thisX: !oneAxis || xsnap[0] < ysnap[0] ? xsnap[1] : e.pageX,
+            thisY: !oneAxis || xsnap[0] > ysnap[0] ? ysnap[1] : e.pageY
         };
     }
     export let docsBeingDragged: Doc[] = [];
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index a3c476125..b939d96b6 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -20,7 +20,6 @@ import React = require("react");
 import { Id } from '../../new_fields/FieldSymbols';
 import e = require('express');
 import { CollectionDockingView } from './collections/CollectionDockingView';
-import { MainView } from './MainView';
 import { SnappingManager } from '../util/SnappingManager';
 
 library.add(faCaretUp);
@@ -238,6 +237,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
 
     _initialAutoHeight = false;
+    _dragHeights = new Map<Doc, number>();
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, (e) => { });
@@ -253,17 +253,38 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         this._snapX = e.pageX;
         this._snapY = e.pageY;
         this._initialAutoHeight = true;
+        DragManager.docsBeingDragged = SelectionManager.SelectedDocuments().map(dv => dv.rootDoc);
+        SelectionManager.SelectedDocuments().map(dv => {
+            this._dragHeights.set(dv.layoutDoc, NumCast(dv.layoutDoc._height));
+            dv.layoutDoc._delayAutoHeight = dv.layoutDoc._height;
+        });
     }
 
     onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
-        const { thisX, thisY } = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY);
+        const first = SelectionManager.SelectedDocuments()[0];
+        const fixedAspect = NumCast(first.layoutDoc._nativeWidth) !== 0;
+        let { thisX, thisY } = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY, fixedAspect);
+        if (fixedAspect) {  // if aspect is set, then any snapped movement must be coerced to match the aspect ratio
+            const aspect = NumCast(first.layoutDoc._nativeWidth) / NumCast(first.layoutDoc._nativeHeight);
+            const deltaX = thisX - this._snapX;
+            const deltaY = thisY - this._snapY;
+            if (thisX !== e.pageX) {
+                const snapY = deltaX / aspect + this._snapY;
+                thisY = Math.abs(deltaX / aspect) < 10 ? snapY : thisY;
+            } else {
+                const snapX = deltaY * aspect + this._snapX;
+                thisX = Math.abs(deltaY * aspect) < 10 ? snapX : thisX;
+            }
+        }
         move[0] = thisX - this._snapX;
         move[1] = thisY - this._snapY;
         this._snapX = thisX;
         this._snapY = thisY;
 
         let dX = 0, dY = 0, dW = 0, dH = 0;
-
+        const unfreeze = () =>
+            SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) =>
+                (element.rootDoc.type === DocumentType.RTF && element.layoutDoc._nativeHeight) && element.toggleNativeDimensions()));
         switch (this._resizeHdlId) {
             case "": break;
             case "documentDecorations-topLeftResizer":
@@ -278,6 +299,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 dH = -move[1];
                 break;
             case "documentDecorations-topResizer":
+                unfreeze();
                 dY = -1;
                 dH = -move[1];
                 break;
@@ -291,13 +313,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 dH = move[1];
                 break;
             case "documentDecorations-bottomResizer":
+                unfreeze();
                 dH = move[1];
                 break;
             case "documentDecorations-leftResizer":
+                unfreeze();
                 dX = -1;
                 dW = -move[0];
                 break;
             case "documentDecorations-rightResizer":
+                unfreeze();
                 dW = move[0];
                 break;
         }
@@ -309,9 +334,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 let nwidth = doc._nativeWidth || 0;
                 let nheight = doc._nativeHeight || 0;
                 const width = (doc._width || 0);
-                const height = (doc._height || (nheight / nwidth * width));
+                let height = (doc._height || (nheight / nwidth * width));
                 const scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling();
                 if (nwidth && nheight) {
+                    if (nwidth / nheight !== width / height) {
+                        height = nheight / nwidth * width;
+                    }
                     if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
                     else dW = dH * nwidth / nheight;
                 }
@@ -365,7 +393,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @action
     onPointerUp = (e: PointerEvent): void => {
         SelectionManager.SelectedDocuments().map(dv => {
-            dv.layoutDoc._delayAutoHeight && (dv.layoutDoc._autoHeight = true);
+            if (NumCast(dv.layoutDoc._delayAutoHeight) < this._dragHeights.get(dv.layoutDoc)!) {
+                dv.nativeWidth > 0 && Doc.toggleNativeDimensions(dv.layoutDoc, dv.props.ContentScaling(), dv.panelWidth(), dv.panelHeight());
+                dv.layoutDoc._autoHeight = true;
+            }
             dv.layoutDoc._delayAutoHeight = undefined;
         });
         this._resizeHdlId = "";
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 62496b01f..9bfef06b4 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -83,14 +83,14 @@ export class MainView extends React.Component {
         firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
         window.removeEventListener("keydown", KeyManager.Instance.handle);
         window.addEventListener("keydown", KeyManager.Instance.handle);
-        window.addEventListener("paste", KeyManager.Instance.paste);
+        window.addEventListener("paste", KeyManager.Instance.paste as any);
     }
 
     componentWillUnMount() {
         window.removeEventListener("keydown", KeyManager.Instance.handle);
         window.removeEventListener("pointerdown", this.globalPointerDown);
         window.removeEventListener("pointerup", this.globalPointerUp);
-        window.removeEventListener("paste", KeyManager.Instance.paste);
+        window.removeEventListener("paste", KeyManager.Instance.paste as any);
     }
 
     constructor(props: Readonly<{}>) {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 50e3b73eb..2e24cbb12 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -757,7 +757,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     get layoutDoc() { return this._document && Doc.Layout(this._document); }
     nativeAspect = () => this.nativeWidth() ? this.nativeWidth() / this.nativeHeight() : 0;
     panelWidth = () => this.layoutDoc?.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) :
-        (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth);
+        (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth)
     panelHeight = () => this.nativeAspect() && this.nativeAspect() > this._panelWidth / this._panelHeight ? this._panelWidth / this.nativeAspect() : this._panelHeight;
 
     nativeWidth = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeWidth) || this._panelWidth : 0;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ac9f64d94..0f239d385 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -234,7 +234,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     onContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
 
-            this.setupViewTypes("Change Perspective...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true);
             this.setupViewTypes("Add a Perspective...", vtype => {
                 const newRendition = Doc.MakeAlias(this.props.Document);
                 newRendition._viewType = vtype;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 129b245b8..6caee960d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1133,19 +1133,25 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private thumbIdentifier?: number;
 
     onContextMenu = (e: React.MouseEvent) => {
-        if (this.props.children && this.props.annotationsKey) return;
+        if (this.props.annotationsKey) return;
+
+        ContextMenu.Instance.addItem({
+            description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
+                this._timelineVisible = !this._timelineVisible;
+            }), icon: this._timelineVisible ? faEyeSlash : faEye
+        });
+
         const options = ContextMenu.Instance.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
 
         optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
-        optionItems.push({ description: !this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" });
-        optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+        optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
+        optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
         optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
         this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
         optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
-        optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document._jitterRotation = (this.props.Document._jitterRotation ? 0 : 10)), icon: "paint-brush" });
         optionItems.push({
             description: "Import document", icon: "upload", event: ({ x, y }) => {
                 const input = document.createElement("input");
@@ -1173,14 +1179,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 input.click();
             }
         });
+        optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
         ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
 
-
-        ContextMenu.Instance.addItem({
-            description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
-                this._timelineVisible = !this._timelineVisible;
-            }), icon: this._timelineVisible ? faEyeSlash : faEye
-        });
     }
     @observable _timelineVisible = false;
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5cccec776..1bf297796 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -704,7 +704,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
 
         const cm = ContextMenu.Instance;
-        const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
 
         const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
         Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
@@ -713,25 +712,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
 
 
-        let open = cm.findByDescription("Add a Perspective...");
-        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
-        openItems.push({ description: "New Echo        ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "layer-group" });
-        openItems.push({ description: "Open Fields     ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
-        templateDoc && openItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
-        if (!open) {
-            open = { description: "Add a Perspective....", subitems: openItems, icon: "external-link-alt" };
-            cm.addItem(open);
-        }
-
         let options = cm.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+        const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
+        optionItems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
+        templateDoc && optionItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
         if (!options) {
             options = { description: "Options...", subitems: optionItems, icon: "compass" };
             cm.addItem(options);
         }
 
-        cm.moveAfter(options, open);
-
         const existingOnClick = cm.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
@@ -764,9 +754,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
             // cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" });
         }
-        if (Cast(this.props.Document.data, ImageField)) {
-            moreItems.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
-        }
         if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
             moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
             moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
@@ -820,47 +807,38 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
 
         runInAction(() => {
-            if (!ClientUtils.RELEASE) {
-                const setWriteMode = (mode: DocServer.WriteMode) => {
-                    DocServer.AclsMode = mode;
-                    const mode1 = mode;
-                    const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
-                    DocServer.setFieldWriteMode("x", mode1);
-                    DocServer.setFieldWriteMode("y", mode1);
-                    DocServer.setFieldWriteMode("_width", mode1);
-                    DocServer.setFieldWriteMode("_height", mode1);
-
-                    DocServer.setFieldWriteMode("_panX", mode2);
-                    DocServer.setFieldWriteMode("_panY", mode2);
-                    DocServer.setFieldWriteMode("scale", mode2);
-                    DocServer.setFieldWriteMode("_viewType", mode2);
-                };
-                const aclsMenu: ContextMenuProps[] = [];
-                aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
-                aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
-                aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
-                aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
-                cm.addItem({ description: "Collaboration ACLs...", subitems: aclsMenu, icon: "share" });
-            }
+            const setWriteMode = (mode: DocServer.WriteMode) => {
+                DocServer.AclsMode = mode;
+                const mode1 = mode;
+                const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
+                DocServer.setFieldWriteMode("x", mode1);
+                DocServer.setFieldWriteMode("y", mode1);
+                DocServer.setFieldWriteMode("_width", mode1);
+                DocServer.setFieldWriteMode("_height", mode1);
+
+                DocServer.setFieldWriteMode("_panX", mode2);
+                DocServer.setFieldWriteMode("_panY", mode2);
+                DocServer.setFieldWriteMode("scale", mode2);
+                DocServer.setFieldWriteMode("_viewType", mode2);
+            };
+            const aclsMenu: ContextMenuProps[] = [];
+            aclsMenu.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
+            aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
+            aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
+            aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
+            aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
+            cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" });
         });
         runInAction(() => {
-            cm.addItem({
-                description: "Share",
-                event: () => SharingManager.Instance.open(this),
-                icon: "external-link-alt"
-            });
-
             if (!this.topMost && !(e instanceof Touch)) {
                 // DocumentViews should stop propagation of this event
                 e.stopPropagation();
             }
             ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
-            if (!SelectionManager.IsSelected(this, true)) {
-                SelectionManager.SelectDoc(this, false);
-            }
+            !SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false);
         });
         const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
-        cm.addItem({
+        const item = ({
             description: `path: ${path}`, event: () => {
                 if (this.props.LibraryPath !== emptyPath) {
                     this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
@@ -870,6 +848,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 }
             }, icon: "check"
         });
+        //cm.addItem(item);
     }
 
     recommender = async () => {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d85373e98..aaebceaa2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -29,6 +29,7 @@ import FaceRectangles from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
+import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 const requestImageSize = require('../../util/request-image-size');
 const path = require('path');
 const { Howl } = require('howler');
@@ -158,6 +159,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
         if (field) {
             const funcs: ContextMenuProps[] = [];
             funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" });
+            funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
+            funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
             // funcs.push({
             //     description: "Reset Native Dimensions", event: action(async () => {
             //         const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
@@ -184,7 +187,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
 
             const existingMore = ContextMenu.Instance.findByDescription("More...");
             const mores: ContextMenuProps[] = existingMore && "subitems" in existingMore ? existingMore.subitems : [];
-            mores.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
             !existingMore && ContextMenu.Instance.addItem({ description: "More...", subitems: mores, icon: "hand-point-right" });
         }
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3a586ff66..23bf86a32 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -419,9 +419,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
         const funcs: ContextMenuProps[] = [];
         this.rootDoc.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
-        funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
         !this.layoutDoc.isTemplateDoc && funcs.push({
-            description: "Make Template", event: () => {
+            description: "Convert to use as a style", event: () => {
                 this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc);
                 Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
             }, icon: "eye"
@@ -444,11 +443,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }, icon: "eye"
         });
         //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
-        funcs.push({ description: !this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" });
+        funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
         funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
+
+        const uicontrols: ContextMenuProps[] = [];
+        uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
+        uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
+        uicontrols.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
+
+        funcs.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
 
         const highlighting: ContextMenuProps[] = [];
         ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
@@ -481,19 +484,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         });
         changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
         !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
-
-        const open = cm.findByDescription("Add a Perspective...");
-        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
-
-        openItems.push({
-            description: "FreeForm", event: undoBatch(() => {
-                const alias = Doc.MakeAlias(this.rootDoc);
-                Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
-                this.props.addDocTab(alias, "onRight");
-            }), icon: "eye"
-        });
-        !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" });
-
     }
 
     recordDictation = () => {
@@ -663,11 +653,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this._disposers.height = reaction(
             () => this.layoutDoc[HeightSym](),
             action(height => {
-                if (height <= 20) {
-                    if (this.layoutDoc._nativeWidth || this.layoutDoc._nativeHeight) {
-                        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.PanelWidth(), this.props.PanelHeight());
-                    }
-                    this.layoutDoc._delayAutoHeight = true;
+                if (height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) {
+                    this.layoutDoc._delayAutoHeight = height;
                 }
             })
         );
@@ -1202,7 +1189,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const dh = NumCast(this.rootDoc._height, 0);
             const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
             if (Math.abs(newHeight - dh) > 1) { // bcz: Argh!  without this, we get into a React crash if the same document is opened in a freeform view and in the treeview.  no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
-                if (this.rootDoc !== this.layoutDoc && !this.layoutDoc.resolvedDataDoc) {
+                if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) {
                     // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template.  So set a timeout and hope its arrived...
                     console.log("Delayed height adjustment...");
                     setTimeout(() => {
-- 
cgit v1.2.3-70-g09d2


From 3911cf85eaf57c133166a6bd08732193ac74d885 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 12 May 2020 12:10:21 -0400
Subject: changed isBackground setting to reset nativeW/H to W/H.  fixed
 presBox reordering.  fixed resizing of fixedAspect suff from sides.

---
 src/client/views/DocumentDecorations.tsx                | 2 +-
 src/client/views/collections/CollectionDockingView.tsx  | 1 +
 src/client/views/collections/CollectionStackingView.tsx | 2 +-
 src/client/views/nodes/DocumentView.tsx                 | 6 +++++-
 src/client/views/nodes/PresBox.tsx                      | 6 +++---
 5 files changed, 11 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3cd7f8da3..313d8be23 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -264,7 +264,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         const first = SelectionManager.SelectedDocuments()[0];
         let thisPt = { thisX: e.clientX - this._offX, thisY: e.clientY - this._offY };
         const fixedAspect = first.layoutDoc._nativeWidth ? NumCast(first.layoutDoc._nativeWidth) / NumCast(first.layoutDoc._nativeHeight) : 0;
-        if (fixedAspect) { // need to generalize for bl and tr drag handles
+        if (fixedAspect && (this._resizeHdlId === "documentDecorations-bottomRightResizer" || this._resizeHdlId === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
             const project = (p: number[], a: number[], b: number[]) => {
                 var atob = [b[0] - a[0], b[1] - a[1]];
                 var atop = [p[0] - a[0], p[1] - a[1]];
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 2e24cbb12..581625222 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -707,6 +707,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 const pinDoc = Doc.MakeAlias(doc);
                 pinDoc.presentationTargetDoc = doc;
                 pinDoc.presZoomButton = true;
+                pinDoc.context = curPres;
                 Doc.AddDocToList(curPres, "data", pinDoc);
                 if (!DocumentManager.Instance.getDocumentView(curPres)) {
                     CollectionDockingView.AddRightSplit(curPres);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 979e9e3f8..97459d910 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -73,7 +73,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
             const width = () => this.getDocWidth(d);
             const dref = React.createRef<HTMLDivElement>();
             const dxf = () => this.getDocTransform(d, dref.current!);
-            this._docXfs.push({ dxf: dxf, width: width, height: height });
+            this._docXfs.push({ dxf, width, height });
             const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
             const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
             return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1bf297796..91275d89b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -669,7 +669,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     toggleBackground = (temporary: boolean): void => {
         this.Document._overflow = temporary ? "visible" : "hidden";
         this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true);
-        this.Document.isBackground && this.props.bringToFront(this.Document, true);
+        if (this.Document.isBackground) {
+            this.props.bringToFront(this.props.Document, true);
+            this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeWidth"] = this.Document[WidthSym]();
+            this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeHeight"] = this.Document[HeightSym]();
+        }
     }
 
     @undoBatch
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 49862d39a..5ff71e922 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -44,7 +44,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
             // stored on each pres element.  
             (this.presElement as Doc).lookupField = ScriptField.MakeScript(
                 `if (field === 'indexInPres') return docList(container[container.presentationFieldKey]).indexOf(data);` +
-                "if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 50 : 46;" +
+                `if (field === 'presCollapsedHeight') return container._viewType === '${CollectionViewType.Stacking}' ? 50 : 46;` +
                 "return undefined;", { field: "string", data: Doc.name, container: Doc.name });
         }
         this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox
@@ -271,8 +271,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
     });
 
     whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
-    addDocumentFilter = (doc: Doc|Doc[]) => {
-        const docs = doc instanceof Doc ? [doc]: doc;
+    addDocumentFilter = (doc: Doc | Doc[]) => {
+        const docs = doc instanceof Doc ? [doc] : doc;
         docs.forEach(doc => {
             doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf);
             !this.childDocs.includes(doc) && (doc.presZoomButton = true);
-- 
cgit v1.2.3-70-g09d2

