From 37324d134c8d788a73b8b1d35afa7ea6b0e88d37 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 1 May 2020 12:47:51 -0400
Subject: cleaned up buxton template.  fixed adding docs to collections.

---
 src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 323d7be25..8a9539eb0 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -322,11 +322,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown}
                     title={evContents === `NO ${key.toUpperCase()} VALUE` ?
                         `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
-                    style={{
-                        width: "100%",
-                        background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "inherit",
-                        color: "grey"
-                    }}>
+                    style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "inherit" }}>
                     <EditableView {...headerEditableViewProps} />
                     {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
                         <div className="collectionStackingView-sectionColor">
-- 
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/CollectionStackingViewFieldColumn.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/CollectionStackingViewFieldColumn.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

