From 1c0e229f9c298281cdeab64532334e0b875cd2e4 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 27 Apr 2020 16:21:56 -0400
Subject: cleaned up masonry events a bit.  changed buxton template to have 3
 sections.

---
 .../collections/CollectionMasonryViewFieldRow.tsx  | 133 ++++++++-------------
 1 file changed, 49 insertions(+), 84 deletions(-)

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

diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 42f0c4311..3bada43f0 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -2,13 +2,13 @@ import React = require("react");
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faPalette } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../../new_fields/Doc";
 import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { StrCast, NumCast } from "../../../new_fields/Types";
-import { numberRange } from "../../../Utils";
+import { numberRange, setupMoveUpEvents, emptyFunction } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { CompileScript } from "../../util/Scripting";
@@ -44,37 +44,44 @@ interface CMVFieldRowProps {
 export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowProps> {
     @observable private _background = "inherit";
     @observable private _createAliasSelected: boolean = false;
-    @observable private _collapsed: boolean = false;
-    @observable private _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
-    @observable private _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
+    @observable private heading: string = "";
+    @observable private color: string = "#f1efeb";
+    @observable private collapsed: boolean = false;
+    private set _heading(value: string) { runInAction(() => this.props.headingObject && (this.props.headingObject.heading = this.heading = value)); }
+    private set _color(value: string) { runInAction(() => this.props.headingObject && (this.props.headingObject.color = this.color = value)); }
+    private set _collapsed(value: boolean) { runInAction(() => this.props.headingObject && (this.props.headingObject.collapsed = this.collapsed = value)); }
 
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
-    private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 };
     private _contRef: React.RefObject<HTMLDivElement> = React.createRef();
-    private _sensitivity: number = 16;
     private _ele: any;
 
     createRowDropRef = (ele: HTMLDivElement | null) => {
-        this._dropDisposer && this._dropDisposer();
+        this._dropDisposer?.();
         if (ele) {
             this._ele = ele;
             this.props.observeHeight(ele);
             this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
         }
     }
+    @action
+    componentDidMount() {
+        this.heading = this.props.headingObject?.heading || "";
+        this.color = this.props.headingObject?.color || "#f1efeb";
+        this.collapsed = this.props.headingObject?.collapsed || false;
+    }
     componentWillUnmount() {
         this.props.unobserveHeight(this._ele);
     }
 
     getTrueHeight = () => {
-        if (this._collapsed) {
-            this.props.setDocHeight(this._heading, 20);
+        if (this.collapsed) {
+            this.props.setDocHeight(this.heading, 20);
         } else {
             const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; //+ 15 accounts for the group header
             const transformScale = this.props.screenToLocalTransform().Scale;
             const trueHeight = rawHeight * transformScale;
-            this.props.setDocHeight(this._heading, trueHeight);
+            this.props.setDocHeight(this.heading, trueHeight);
         }
     }
 
@@ -86,7 +93,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
             (this.props.parent.Document.dropConverter instanceof ScriptField) &&
                 this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData });
             const key = StrCast(this.props.parent.props.Document._pivotField);
-            const castedValue = this.getValue(this._heading);
+            const castedValue = this.getValue(this.heading);
             de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue);
             this.props.parent.onInternalDrop(e, de);
             e.stopPropagation();
@@ -113,10 +120,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
                 }
             }
             this.props.docList.forEach(d => d[key] = castedValue);
-            if (this.props.headingObject) {
-                this.props.headingObject.setHeading(castedValue.toString());
-                this._heading = this.props.headingObject.heading;
-            }
+            this._heading = castedValue.toString();
             return true;
         }
         return false;
@@ -125,10 +129,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     @action
     changeColumnColor = (color: string) => {
         this._createAliasSelected = false;
-        if (this.props.headingObject) {
-            this.props.headingObject.setColor(color);
-            this._color = color;
-        }
+        this._color = color;
     }
 
     pointerEnteredRow = action(() => SelectionManager.GetIsDragging() && (this._background = "#b4b4b4"));
@@ -137,7 +138,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     pointerLeaveRow = () => {
         this._createAliasSelected = false;
         this._background = "inherit";
-        document.removeEventListener("pointermove", this.startDrag);
     }
 
     @action
@@ -161,65 +161,34 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     }));
 
     @action
-    collapseSection = () => {
+    collapseSection = (e: any) => {
         this._createAliasSelected = false;
-        if (this.props.headingObject) {
-            this.props.headingObject.setCollapsed(!this.props.headingObject.collapsed);
-            this.toggleVisibility();
-        }
-    }
-
-    startDrag = (e: PointerEvent) => {
-        const [dx, dy] = this.props.screenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y);
-        if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
-            const alias = Doc.MakeAlias(this.props.parent.props.Document);
-            const key = StrCast(this.props.parent.props.Document._pivotField);
-            let value = this.getValue(this._heading);
-            value = typeof value === "string" ? `"${value}"` : value;
-            const script = `return doc.${key} === ${value}`;
-            const compiled = CompileScript(script, { params: { doc: Doc.name } });
-            if (compiled.compiled) {
-                alias.viewSpecScript = new ScriptField(compiled);
-                DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY);
-            }
-
-            e.stopPropagation();
-            document.removeEventListener("pointermove", this.startDrag);
-            document.removeEventListener("pointerup", this.pointerUp);
-        }
-    }
-
-    pointerUp = (e: PointerEvent) => {
+        this.toggleVisibility();
         e.stopPropagation();
-        e.preventDefault();
+    }
 
-        if (this.props.parent.props.Document._chromeStatus === "disabled") {
-            this.collapseSection();
+    headerMove = (e: PointerEvent) => {
+        const alias = Doc.MakeAlias(this.props.parent.props.Document);
+        const key = StrCast(this.props.parent.props.Document._pivotField);
+        let value = this.getValue(this.heading);
+        value = typeof value === "string" ? `"${value}"` : value;
+        const script = `return doc.${key} === ${value}`;
+        const compiled = CompileScript(script, { params: { doc: Doc.name } });
+        if (compiled.compiled) {
+            alias.viewSpecScript = new ScriptField(compiled);
+            DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY);
         }
-
-        document.removeEventListener("pointermove", this.startDrag);
-        document.removeEventListener("pointerup", this.pointerUp);
+        return true;
     }
 
     @action
     headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
-        e.stopPropagation();
-        e.preventDefault();
-
-        const [dx, dy] = this.props.screenToLocalTransform().transformDirection(e.clientX, e.clientY);
-        this._startDragPosition = { x: dx, y: dy };
-
-        if (this.props.parent.props.Document._chromeStatus === "disabled" || this._createAliasSelected) {
-            document.removeEventListener("pointermove", this.startDrag);
-            document.addEventListener("pointermove", this.startDrag);
-            document.removeEventListener("pointerup", this.pointerUp);
-            document.addEventListener("pointerup", this.pointerUp);
-        }
+        setupMoveUpEvents(this, e, this.headerMove, emptyFunction, () => (this.props.parent.props.Document._chromeStatus === "disabled") && this.collapseSection(e));
         this._createAliasSelected = false;
     }
 
     renderColorPicker = () => {
-        const selected = this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
+        const selected = this.color;
 
         const pink = PastelSchemaPalette.get("pink2");
         const purple = PastelSchemaPalette.get("purple4");
@@ -249,7 +218,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     }
 
     toggleAlias = action(() => this._createAliasSelected = true);
-    toggleVisibility = action(() => this._collapsed = !this._collapsed);
+    toggleVisibility = () => this._collapsed = !this.collapsed;
 
     renderMenu = () => {
         const selected = this._createAliasSelected;
@@ -264,7 +233,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     @computed get contentLayout() {
         const rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap))));
         const style = this.props.parent;
-        const collapsed = this._collapsed;
         const chromeStatus = this.props.parent.props.Document._chromeStatus;
         const newEditableViewProps = {
             GetValue: () => "",
@@ -272,9 +240,9 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
             contents: "+ NEW",
             HeadingObject: this.props.headingObject,
             toggle: this.toggleVisibility,
-            color: this._color
+            color: this.color
         };
-        return collapsed ? (null) :
+        return this.collapsed ? (null) :
             <div style={{ position: "relative" }}>
                 {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ?
                     <div className="collectionStackingView-addDocumentButton"
@@ -300,10 +268,9 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     }
 
     @computed get headingView() {
-        const heading = this._heading;
         const noChrome = this.props.parent.props.Document._chromeStatus === "disabled";
         const key = StrCast(this.props.parent.props.Document._pivotField);
-        const evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`;
+        const evContents = this.heading ? this.heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`;
         const headerEditableViewProps = {
             GetValue: () => evContents,
             SetValue: this.headingChanged,
@@ -311,7 +278,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
             oneLine: true,
             HeadingObject: this.props.headingObject,
             toggle: this.toggleVisibility,
-            color: this._color
+            color: this.color
         };
         return this.props.parent.props.Document.miniHeaders ?
             <div className="collectionStackingView-miniHeader">
@@ -319,11 +286,11 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
             </div> :
             !this.props.headingObject ? (null) :
                 <div className="collectionStackingView-sectionHeader" ref={this._headerRef} >
-                    <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown} onClick={noChrome ? undefined : this.collapseSection}
+                    <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={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "lightgrey" }}>
-                        <EditableView {...headerEditableViewProps} />
+                        style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this.color : "lightgrey" }}>
+                        {noChrome ? evContents : <EditableView {...headerEditableViewProps} />}
                         {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
                             <div className="collectionStackingView-sectionColor">
                                 <Flyout anchorPoint={anchorPoints.CENTER_RIGHT} content={this.renderColorPicker()}>
@@ -334,7 +301,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
                             </div>
                         }
                         {noChrome ? (null) : <button className="collectionStackingView-sectionDelete" onClick={noChrome ? undefined : this.collapseSection}>
-                            <FontAwesomeIcon icon={this._collapsed ? "chevron-down" : "chevron-up"} size="lg" />
+                            <FontAwesomeIcon icon={this.collapsed ? "chevron-down" : "chevron-up"} size="lg" />
                         </button>}
                         {noChrome || evContents === `NO  ${key.toUpperCase()} VALUE` ? (null) :
                             <div className="collectionStackingView-sectionOptions">
@@ -349,17 +316,15 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
                 </div>;
     }
     render() {
-        const background = this._background; //to account for observables in Measure
-        const contentlayout = this.contentLayout;
-        const headingview = this.headingView;
+        const background = this._background;
         return <div className="collectionStackingView-masonrySection"
             style={{ width: this.props.parent.NodeWidth, background }}
             ref={this.createRowDropRef}
             onPointerEnter={this.pointerEnteredRow}
             onPointerLeave={this.pointerLeaveRow}
         >
-            {headingview}
-            {contentlayout}
+            {this.headingView}
+            {this.contentLayout}
         </div >;
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 6b2896756c55727ed397c223187cb03fe8a51a59 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 28 Apr 2020 15:51:34 -0400
Subject: ignore rightlick events on masonry header to allow for context menu
 from doc decorations

---
 src/client/views/collections/CollectionMasonryViewFieldRow.tsx | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 3bada43f0..7ad15ef41 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -183,8 +183,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
 
     @action
     headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
-        setupMoveUpEvents(this, e, this.headerMove, emptyFunction, () => (this.props.parent.props.Document._chromeStatus === "disabled") && this.collapseSection(e));
-        this._createAliasSelected = false;
+        if (e.button === 0 && !e.ctrlKey) {
+            setupMoveUpEvents(this, e, this.headerMove, emptyFunction, () => (this.props.parent.props.Document._chromeStatus === "disabled") && this.collapseSection(e));
+            this._createAliasSelected = false;
+        }
     }
 
     renderColorPicker = () => {
-- 
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/CollectionMasonryViewFieldRow.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

