From ff7c7d40b1fcdf74b539c7d97f36707ff1521d2e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 1 May 2020 01:46:07 -0400
Subject: fixed presentations to allow drag and drop.  fixed pres box to use
 RenderData instead of modifying presentation elements with unnecessary info
 like their containing PresBox and their presentation index position. 
 COnverted COntentFIttingDocumentView to use DocumentView's props

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

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d938bd7ad..71358a8ec 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -353,27 +353,31 @@ class TreeView extends React.Component<TreeViewProps> {
             return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.props.document[Id] + this.props.document.title}>
                 <ContentFittingDocumentView
                     Document={layoutDoc}
-                    DataDocument={this.templateDataDoc}
+                    DataDoc={this.templateDataDoc}
                     LibraryPath={emptyPath}
                     renderDepth={this.props.renderDepth + 1}
                     rootSelected={returnTrue}
                     backgroundColor={this.props.backgroundColor}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
                     FreezeDimensions={true}
-                    NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
-                    NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined}
+                    NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : returnZero}
+                    NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : returnZero}
                     PanelWidth={panelWidth}
                     PanelHeight={panelHeight}
-                    getTransform={this.docTransform}
-                    CollectionDoc={this.props.containingCollection}
-                    CollectionView={undefined}
+                    focus={returnFalse}
+                    ScreenToLocalTransform={this.docTransform}
+                    ContainingCollectionDoc={this.props.containingCollection}
+                    ContainingCollectionView={undefined}
                     addDocument={returnFalse}
                     moveDocument={this.props.moveDocument}
                     removeDocument={returnFalse}
-                    active={this.props.active}
+                    parentActive={this.props.active}
                     whenActiveChanged={emptyFunction}
                     addDocTab={this.props.addDocTab}
-                    pinToPres={this.props.pinToPres} />
+                    pinToPres={this.props.pinToPres}
+                    bringToFront={returnFalse}
+                    ContentScaling={returnOne}
+                />
             </div>;
         }
     }
-- 
cgit v1.2.3-70-g09d2


From 5e6352c78be5b2a9fe791bd87da9b2415ced4839 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 1 May 2020 19:35:37 -0400
Subject: added childLayoutTemplate to render collection children w/o modifying
 them. fixed docComponent's layoutDoc to use LayoutDoc prop.  change buxton
 template to use new template mechanisms. fixed   fixed lint errors.

---
 src/client/documents/Documents.ts                  |  5 +-
 src/client/util/DragManager.ts                     |  2 +-
 src/client/views/DocComponent.tsx                  |  2 +-
 src/client/views/animationtimeline/Timeline.tsx    |  4 +-
 .../views/animationtimeline/TimelineMenu.tsx       | 81 +++++++++++-----------
 src/client/views/animationtimeline/Track.tsx       | 50 ++++++-------
 .../views/collections/CollectionStackingView.tsx   |  2 +-
 src/client/views/collections/CollectionSubView.tsx | 17 -----
 .../views/collections/CollectionTreeView.tsx       | 36 ++++------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 11 ++-
 src/client/views/nodes/PresBox.tsx                 |  2 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |  2 +-
 src/new_fields/documentSchemas.ts                  |  2 +-
 13 files changed, 94 insertions(+), 122 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a8b10bc7b..434b26312 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -93,7 +93,8 @@ export interface DocumentOptions {
     scale?: number;
     isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
     forceActive?: boolean;
-    layout?: string | Doc;
+    layout?: string | Doc; // default layout string for a document
+    childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view)
     hideFilterView?: boolean; // whether to hide the filter popout on collections
     hideHeadings?: boolean; // whether stacking view column headings should be hidden
     isTemplateForField?: string; // the field key for which the containing document is a rendering template
@@ -123,7 +124,7 @@ export interface DocumentOptions {
     borderRounding?: string;
     boxShadow?: string;
     dontRegisterChildren?: boolean;
-    "onDoubleClick-rawScript"?: string // onDoubleClick script in raw text form
+    "onDoubleClick-rawScript"?: string; // onDoubleClick script in raw text form
     "onClick-rawScript"?: string; // onClick script in raw text form
     "onCheckedClick-rawScript"?: string; // onChecked script in raw text form
     "onCheckedClick-params"?: List<string>; // parameter list for onChecked treeview functions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index c48611eff..c06ad3d60 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -316,7 +316,7 @@ export namespace DragManager {
                 return closestDists[minIndex] < snapThreshold ? closestPts[minIndex] + offs[minIndex] : drag;
             }
             return drag;
-        }
+        };
 
         return { thisX: snapVal([xFromLeft, xFromRight], e.pageX, vertSnapLines), thisY: snapVal([yFromTop, yFromBottom], e.pageY, horizSnapLines) };
     }
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 0a8f0c9a7..629b0f447 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -20,7 +20,7 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D
         // This is the "The Document" -- it encapsulates, data, layout, and any templates
         @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
         // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
-        @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
+        @computed get layoutDoc() { return Doc.Layout(this.props.Document, this.props.LayoutDoc?.()); }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
 
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index 77656b85f..466cbb867 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -525,8 +525,8 @@ export class Timeline extends React.Component<FieldViewProps> {
     @action.bound
     changeLengths() {
         if (this._infoContainer.current) {
-            this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see)
-            this._visibleStart = this._infoContainer.current!.scrollLeft; //where the div starts
+            this._visibleLength = this._infoContainer.current.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see)
+            this._visibleStart = this._infoContainer.current.scrollLeft; //where the div starts
         }
     }
 
diff --git a/src/client/views/animationtimeline/TimelineMenu.tsx b/src/client/views/animationtimeline/TimelineMenu.tsx
index 59c25596e..53ca9acad 100644
--- a/src/client/views/animationtimeline/TimelineMenu.tsx
+++ b/src/client/views/animationtimeline/TimelineMenu.tsx
@@ -1,7 +1,7 @@
 import * as React from "react";
-import {observable, action, runInAction} from "mobx"; 
-import {observer} from "mobx-react"; 
-import "./TimelineMenu.scss"; 
+import { observable, action, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import "./TimelineMenu.scss";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faChartLine, faRoad, faClipboard, faPen, faTrash, faTable } from "@fortawesome/free-solid-svg-icons";
 import { Utils } from "../../../Utils";
@@ -9,67 +9,66 @@ import { Utils } from "../../../Utils";
 
 @observer
 export class TimelineMenu extends React.Component {
-    public static Instance:TimelineMenu; 
+    public static Instance: TimelineMenu;
 
     @observable private _opacity = 0;
-    @observable private _x = 0; 
-    @observable private _y = 0; 
-    @observable private _currentMenu:JSX.Element[] = []; 
+    @observable private _x = 0;
+    @observable private _y = 0;
+    @observable private _currentMenu: JSX.Element[] = [];
 
-    constructor (props:Readonly<{}>){
-        super(props); 
-        TimelineMenu.Instance = this; 
+    constructor(props: Readonly<{}>) {
+        super(props);
+        TimelineMenu.Instance = this;
     }
-    
+
     @action
-    openMenu = (x?:number, y?:number) => {
-        this._opacity = 1; 
-        x ? this._x = x : this._x = 0; 
-        y ? this._y = y : this._y = 0; 
+    openMenu = (x?: number, y?: number) => {
+        this._opacity = 1;
+        x ? this._x = x : this._x = 0;
+        y ? this._y = y : this._y = 0;
     }
 
     @action
     closeMenu = () => {
-        this._opacity = 0; 
-        this._currentMenu = []; 
-        this._x = -1000000; 
-        this._y = -1000000; 
+        this._opacity = 0;
+        this._currentMenu = [];
+        this._x = -1000000;
+        this._y = -1000000;
     }
 
     @action
-    addItem = (type: "input" | "button", title: string, event: (e:any, ...args:any[]) => void) => {
-        if (type === "input"){
-            let inputRef = React.createRef<HTMLInputElement>(); 
-            let text = ""; 
-            this._currentMenu.push(<div key={Utils.GenerateGuid()} className="timeline-menu-item"><FontAwesomeIcon icon={faClipboard} size="lg"/><input className="timeline-menu-input" ref = {inputRef} placeholder={title} onChange={(e) => {
+    addItem = (type: "input" | "button", title: string, event: (e: any, ...args: any[]) => void) => {
+        if (type === "input") {
+            const inputRef = React.createRef<HTMLInputElement>();
+            let text = "";
+            this._currentMenu.push(<div key={Utils.GenerateGuid()} className="timeline-menu-item"><FontAwesomeIcon icon={faClipboard} size="lg" /><input className="timeline-menu-input" ref={inputRef} placeholder={title} onChange={(e) => {
                 e.stopPropagation();
                 text = e.target.value;
             }} onKeyDown={(e) => {
                 if (e.keyCode === 13) {
-                    event(text); 
-                    this.closeMenu();                    
-                     e.stopPropagation(); 
-                } 
-            }}/></div>); 
+                    event(text);
+                    this.closeMenu();
+                    e.stopPropagation();
+                }
+            }} /></div>);
         } else if (type === "button") {
-            let buttonRef = React.createRef<HTMLDivElement>(); 
-            this._currentMenu.push( <div key={Utils.GenerateGuid()} className="timeline-menu-item"><FontAwesomeIcon icon={faChartLine}size="lg"/><p className="timeline-menu-desc" onClick={(e) => {
-                e.preventDefault(); 
-                e.stopPropagation(); 
-                event(e); 
-                this.closeMenu(); 
-            }}>{title}</p></div>); 
-        }  
+            this._currentMenu.push(<div key={Utils.GenerateGuid()} className="timeline-menu-item"><FontAwesomeIcon icon={faChartLine} size="lg" /><p className="timeline-menu-desc" onClick={(e) => {
+                e.preventDefault();
+                e.stopPropagation();
+                event(e);
+                this.closeMenu();
+            }}>{title}</p></div>);
+        }
     }
 
-    @action 
-    addMenu = (title:string) => {
-        this._currentMenu.unshift(<div key={Utils.GenerateGuid()} className="timeline-menu-header"><p className="timeline-menu-header-desc">{title}</p></div>);     
+    @action
+    addMenu = (title: string) => {
+        this._currentMenu.unshift(<div key={Utils.GenerateGuid()} className="timeline-menu-header"><p className="timeline-menu-header-desc">{title}</p></div>);
     }
 
     render() {
         return (
-            <div key={Utils.GenerateGuid()} className="timeline-menu-container" style={{opacity: this._opacity, left: this._x, top: this._y}} >
+            <div key={Utils.GenerateGuid()} className="timeline-menu-container" style={{ opacity: this._opacity, left: this._x, top: this._y }} >
                 {this._currentMenu}
             </div>
         );
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 79eb60fae..461db4858 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -90,9 +90,9 @@ export class Track extends React.Component<IProps> {
      */
     @action
     saveKeyframe = async () => {
-        let keyframes = Cast(this.saveStateRegion?.keyframes, listSpec(Doc)) as List<Doc>;
-        let kfIndex = keyframes.indexOf(this.saveStateKf!);
-        let kf = keyframes[kfIndex] as Doc; //index in the keyframe
+        const keyframes = Cast(this.saveStateRegion?.keyframes, listSpec(Doc)) as List<Doc>;
+        const kfIndex = keyframes.indexOf(this.saveStateKf!);
+        const kf = keyframes[kfIndex] as Doc; //index in the keyframe
         if (this._newKeyframe) {
             DocListCast(this.saveStateRegion?.keyframes).forEach((kf, index) => {
                 this.copyDocDataToKeyFrame(kf);
@@ -103,17 +103,17 @@ export class Track extends React.Component<IProps> {
         if (!kf) return;
         if (kf.type === KeyframeFunc.KeyframeType.default) { // only save for non-fades
             this.copyDocDataToKeyFrame(kf);
-            let leftkf = KeyframeFunc.calcMinLeft(this.saveStateRegion!, this.time, kf); // lef keyframe, if it exists
-            let rightkf = KeyframeFunc.calcMinRight(this.saveStateRegion!, this.time, kf); //right keyframe, if it exists 
+            const leftkf = KeyframeFunc.calcMinLeft(this.saveStateRegion!, this.time, kf); // lef keyframe, if it exists
+            const rightkf = KeyframeFunc.calcMinRight(this.saveStateRegion!, this.time, kf); //right keyframe, if it exists 
             if (leftkf?.type === KeyframeFunc.KeyframeType.fade) { //replicating this keyframe to fades
-                let edge = KeyframeFunc.calcMinLeft(this.saveStateRegion!, this.time, leftkf);
+                const edge = KeyframeFunc.calcMinLeft(this.saveStateRegion!, this.time, leftkf);
                 edge && this.copyDocDataToKeyFrame(edge);
                 leftkf && this.copyDocDataToKeyFrame(leftkf);
-                edge && (edge!.opacity = 0.1);
-                leftkf && (leftkf!.opacity = 1);
+                edge && (edge.opacity = 0.1);
+                leftkf && (leftkf.opacity = 1);
             }
             if (rightkf?.type === KeyframeFunc.KeyframeType.fade) {
-                let edge = KeyframeFunc.calcMinRight(this.saveStateRegion!, this.time, rightkf);
+                const edge = KeyframeFunc.calcMinRight(this.saveStateRegion!, this.time, rightkf);
                 edge && this.copyDocDataToKeyFrame(edge);
                 rightkf && this.copyDocDataToKeyFrame(rightkf);
                 edge && (edge.opacity = 0.1);
@@ -142,7 +142,7 @@ export class Track extends React.Component<IProps> {
             //check for region 
             const region = this.findRegion(this.time);
             if (region !== undefined) { //if region at scrub time exist
-                let r = region as RegionData; //for some region is returning undefined... which is not the case
+                const r = region as RegionData; //for some region is returning undefined... which is not the case
                 if (DocListCast(r.keyframes).find(kf => kf.time === this.time) === undefined) { //basically when there is no additional keyframe at that timespot 
                     this.makeKeyData(r, this.time, KeyframeFunc.KeyframeType.default);
                 }
@@ -222,11 +222,11 @@ export class Track extends React.Component<IProps> {
         } else if (this._newKeyframe) {
             await this.saveKeyframe();
         }
-        let regiondata = await this.findRegion(Math.round(this.time)); //finds a region that the scrubber is on
+        const regiondata = await this.findRegion(Math.round(this.time)); //finds a region that the scrubber is on
         if (regiondata) {
-            let leftkf: (Doc | undefined) = await KeyframeFunc.calcMinLeft(regiondata, this.time); // lef keyframe, if it exists
-            let rightkf: (Doc | undefined) = await KeyframeFunc.calcMinRight(regiondata, this.time); //right keyframe, if it exists        
-            let currentkf: (Doc | undefined) = await this.calcCurrent(regiondata); //if the scrubber is on top of the keyframe
+            const leftkf: (Doc | undefined) = await KeyframeFunc.calcMinLeft(regiondata, this.time); // lef keyframe, if it exists
+            const rightkf: (Doc | undefined) = await KeyframeFunc.calcMinRight(regiondata, this.time); //right keyframe, if it exists        
+            const currentkf: (Doc | undefined) = await this.calcCurrent(regiondata); //if the scrubber is on top of the keyframe
             if (currentkf) {
                 console.log("is current");
                 await this.applyKeys(currentkf);
@@ -248,7 +248,7 @@ export class Track extends React.Component<IProps> {
             if (!kf[key]) {
                 this.props.node[key] = undefined;
             } else {
-                let stored = kf[key];
+                const stored = kf[key];
                 this.props.node[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
             }
         });
@@ -261,7 +261,7 @@ export class Track extends React.Component<IProps> {
     @action
     calcCurrent = (region: Doc) => {
         let currentkf: (Doc | undefined) = undefined;
-        let keyframes = DocListCast(region.keyframes!);
+        const keyframes = DocListCast(region.keyframes!);
         keyframes.forEach((kf) => {
             if (NumCast(kf.time) === Math.round(this.time)) currentkf = kf;
         });
@@ -276,12 +276,12 @@ export class Track extends React.Component<IProps> {
     interpolate = async (left: Doc, right: Doc) => {
         this.primitiveWhitelist.forEach(key => {
             if (left[key] && right[key] && typeof (left[key]) === "number" && typeof (right[key]) === "number") { //if it is number, interpolate
-                let dif = NumCast(right[key]) - NumCast(left[key]);
-                let deltaLeft = this.time - NumCast(left.time);
-                let ratio = deltaLeft / (NumCast(right.time) - NumCast(left.time));
+                const dif = NumCast(right[key]) - NumCast(left[key]);
+                const deltaLeft = this.time - NumCast(left.time);
+                const ratio = deltaLeft / (NumCast(right.time) - NumCast(left.time));
                 this.props.node[key] = NumCast(left[key]) + (dif * ratio);
             } else { // case data 
-                let stored = left[key];
+                const stored = left[key];
                 this.props.node[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
             }
         });
@@ -301,8 +301,8 @@ export class Track extends React.Component<IProps> {
      */
     @action
     onInnerDoubleClick = (e: React.MouseEvent) => {
-        let inner = this._inner.current!;
-        let offsetX = Math.round((e.clientX - inner.getBoundingClientRect().left) * this.props.transform.Scale);
+        const inner = this._inner.current!;
+        const offsetX = Math.round((e.clientX - inner.getBoundingClientRect().left) * this.props.transform.Scale);
         this.createRegion(KeyframeFunc.convertPixelTime(offsetX, "mili", "time", this.props.tickSpacing, this.props.tickIncrement));
     }
 
@@ -313,10 +313,10 @@ export class Track extends React.Component<IProps> {
     @action
     createRegion = (time: number) => {
         if (this.findRegion(time) === undefined) {  //check if there is a region where double clicking (prevents phantom regions)
-            let regiondata = KeyframeFunc.defaultKeyframe(); //create keyframe data
+            const regiondata = KeyframeFunc.defaultKeyframe(); //create keyframe data
 
             regiondata.position = time; //set position
-            let rightRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, regiondata, this.regions);
+            const rightRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, regiondata, this.regions);
 
             if (rightRegion && rightRegion.position - regiondata.position <= 4000) { //edge case when there is less than default 4000 duration space between this and right region
                 regiondata.duration = rightRegion.position - regiondata.position;
@@ -332,7 +332,7 @@ export class Track extends React.Component<IProps> {
 
     @action
     makeKeyData = (regiondata: RegionData, time: number, type: KeyframeFunc.KeyframeType = KeyframeFunc.KeyframeType.default) => { //Kfpos is mouse offsetX, representing time 
-        const trackKeyFrames = DocListCast(regiondata.keyframes)!;
+        const trackKeyFrames = DocListCast(regiondata.keyframes);
         const existingkf = trackKeyFrames.find(TK => TK.time === time);
         if (existingkf) return existingkf;
         //else creates a new doc. 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6c230d5b1..01766f65f 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -128,7 +128,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return layoutDoc._fitWidth ? wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1) : layoutDoc[HeightSym]();
     }
     componentDidMount() {
-        super.componentDidMount();
+        super.componentDidMount?.();
 
         // reset section headers when a new filter is inputted
         this._pivotFieldDisposer = reaction(
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8cc1af55b..e44bbae78 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -58,7 +58,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
-        private _childLayoutDisposer?: IReactionDisposer;
         protected _mainCont?: HTMLDivElement;
         protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer?.();
@@ -75,25 +74,9 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             this.createDashEventsTarget(ele);
         }
 
-        componentDidMount() {
-            this._childLayoutDisposer = reaction(() => ({ childDocs: this.childDocs, childLayout: Cast(this.props.Document.childLayout, Doc) }),
-                ({ childDocs, childLayout }) => {
-                    if (childLayout instanceof Doc) {
-                        childDocs.map(doc => {
-                            doc.layout_fromParent = childLayout;
-                            doc.layoutKey = "layout_fromParent";
-                        });
-                    }
-                    else if (!(childLayout instanceof Promise)) {
-                        childDocs.filter(d => !d.isTemplateForField).map(doc => doc.layoutKey === "layout_fromParent" && (doc.layoutKey = "layout"));
-                    }
-                }, { fireImmediately: true });
-
-        }
         componentWillUnmount() {
             this.gestureDisposer?.();
             this.multiTouchDisposer?.();
-            this._childLayoutDisposer?.();
         }
 
         @computed get dataDoc() {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 71358a8ec..296c1a39c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -725,14 +725,6 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         }
         ContextMenu.Instance.addItem({
             description: "Buxton Layout", icon: "eye", event: () => {
-                DocListCast(this.dataDoc[this.props.fieldKey]).map(d => {
-                    DocListCast(d.data).map((img, i) => {
-                        const caption = (d.captions as any)[i];
-                        if (caption) {
-                            Doc.GetProto(img).caption = caption;
-                        }
-                    });
-                });
                 const { ImageDocument } = Docs.Create;
                 const { Document } = this.props;
                 const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif";
@@ -742,21 +734,19 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
                 heroView._showTitle = "title";
                 heroView._showTitleHover = "titlehover";
 
-                Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data",
-                    Docs.Create.FontIconDocument({
-                        title: "hero view", _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias",
-                        dragFactory: heroView, removeDropProperties: new List<string>(["dropAction"]), icon: "portrait",
-                        onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
-                    }));
-
-                Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data",
-                    Docs.Create.FontIconDocument({
-                        title: "detail view", _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias",
-                        dragFactory: detailView, removeDropProperties: new List<string>(["dropAction"]), icon: "file-alt",
-                        onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
-                    }));
-
-                Document.childLayout = heroView;
+                const doubleClickView = ImageDocument("http://cs.brown.edu/~bcz/face.gif", { _width: 400 });  // replace with desired double click target
+                DocListCast(this.dataDoc[this.props.fieldKey]).map(d => {
+                    DocListCast(d.data).map((img, i) => {
+                        const caption = (d.captions as any)[i];
+                        if (caption) {
+                            Doc.GetProto(img).caption = caption;
+                            Doc.GetProto(img).doubleClickView = doubleClickView;
+                        }
+                    });
+                    d.layout = ImageBox.LayoutString("hero");
+                });
+
+                Document.childLayoutTemplate = heroView;
                 Document.childDetailView = detailView;
                 Document._viewType = CollectionViewType.Time;
                 Document._forceActive = true;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b4eb22444..45ef0455e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -855,9 +855,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
     @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
     @computed get onChildDoubleClickHandler() { return this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
-    backgroundHalo = () => BoolCast(this.Document.useClusters);
     @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
+    backgroundHalo = () => BoolCast(this.Document.useClusters);
     parentActive = () => this.props.active() || this.backgroundActive ? true : false;
+    childLayoutFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
         return {
             ...this.props,
@@ -867,12 +868,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             DataDoc: childData,
             Document: childLayout,
             LibraryPath: this.libraryPath,
+            LayoutDoc: this.childLayoutFunc,
             FreezeDimensions: this.props.freezeChildDimensions,
             layoutKey: undefined,
             setupDragLines: this.setupDragLines,
             rootSelected: childData ? this.rootSelected : returnFalse,
             dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
-            //onClick: undefined, // this.props.onClick,  // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
             onClick: this.onChildClickHandler,
             onDoubleClick: this.onChildDoubleClickHandler,
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
@@ -999,7 +1000,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return { newPool, computedElementData: this.doFreeformLayout(newPool) };
     }
 
-    childLayoutDocFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
     get doLayoutComputation() {
         const { newPool, computedElementData } = this.doInternalLayoutComputation;
         runInAction(() =>
@@ -1025,7 +1025,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     replica={entry[1].replica}
                     dataProvider={this.childDataProvider}
                     sizeProvider={this.childSizeProvider}
-                    LayoutDoc={this.childLayoutDocFunc}
                     pointerEvents={
                         this.backgroundActive ?
                             true :
@@ -1042,7 +1041,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     componentDidMount() {
-        super.componentDidMount();
+        super.componentDidMount?.();
         this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
             (elements) => this._layoutElements = elements || [],
             { fireImmediately: true, name: "doLayout" });
@@ -1156,7 +1155,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             if (this.intersectRect(docDims(doc), rect)) {
                 snappableDocs.push(doc);
             }
-        }
+        };
         const snappableDocs: Doc[] = [];  // the set of documents in the visible viewport that we will try to snap to;
         const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) };
         this.getActiveDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => isDocInView(doc, selRect));  // first see if there are any foreground docs to snap to
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 72bbc9e4b..6e3420f22 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -260,7 +260,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
     panelHeight = () => this.props.PanelHeight() - 20;
 
     active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.layoutDoc.isBackground) &&
-        (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false);
+        (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
 
     whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
 
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 1b22ed4cd..d87d6e424 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -104,7 +104,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                     r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true));
                 }} >
                     {strVal}
-                </span>
+                </span>;
             }
         }
     }
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 7bf1c03c8..fd9a304f9 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -78,7 +78,7 @@ export const positionSchema = createSchema({
 });
 
 export const collectionSchema = createSchema({
-    childLayout: Doc, // layout template for children of a collecion
+    childLayoutTemplate: Doc, // layout template for children of a collecion
     childDetailView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to use this field)
     onChildClick: ScriptField, // script to run for each child when its clicked
     onChildDoubleClick: ScriptField, // script to run for each child when its clicked
-- 
cgit v1.2.3-70-g09d2


From 40d5c3acab6dbdc67f6d4bfd15c802da9fe08ca0 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 2 May 2020 00:17:21 -0400
Subject: cleaned up a lot of layoutTemplate/String props.  fixed link drawing.

---
 src/client/documents/Documents.ts                  |  7 +-
 src/client/util/DocumentManager.ts                 |  2 +-
 src/client/views/DocComponent.tsx                  |  4 +-
 src/client/views/DocumentDecorations.scss          | 24 +++---
 src/client/views/DocumentDecorations.tsx           |  4 +-
 src/client/views/MainView.scss                     |  6 +-
 .../views/collections/CollectionCarouselView.tsx   | 11 +--
 .../views/collections/CollectionStackingView.tsx   | 29 ++++---
 src/client/views/collections/CollectionSubView.tsx |  2 +
 .../views/collections/CollectionTimeView.tsx       |  4 +-
 .../views/collections/CollectionTreeView.tsx       |  8 +-
 src/client/views/collections/CollectionView.tsx    | 14 +++-
 .../views/collections/CollectionViewChromes.tsx    |  6 +-
 .../CollectionFreeFormLinkView.tsx                 |  2 +-
 .../CollectionFreeFormLinksView.tsx                |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 10 +--
 .../CollectionMulticolumnView.tsx                  |  5 +-
 .../CollectionMultirowView.tsx                     |  5 +-
 src/client/views/nodes/AudioBox.tsx                |  4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  4 +-
 .../views/nodes/ContentFittingDocumentView.tsx     |  5 +-
 src/client/views/nodes/DocumentBox.tsx             | 94 ++++++++++++++-------
 src/client/views/nodes/DocumentContentsView.tsx    | 44 +++++-----
 src/client/views/nodes/DocumentView.tsx            | 29 +++----
 src/client/views/nodes/FieldView.tsx               |  3 +-
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |  1 -
 src/client/views/nodes/ScreenshotBox.tsx           |  6 +-
 src/client/views/nodes/VideoBox.tsx                |  6 +-
 src/new_fields/Doc.ts                              |  2 +-
 src/new_fields/documentSchemas.ts                  | 95 ++++++++++++----------
 .../authentication/models/current_user_utils.ts    |  2 +-
 32 files changed, 247 insertions(+), 195 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 434b26312..672f94f75 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -95,6 +95,7 @@ export interface DocumentOptions {
     forceActive?: boolean;
     layout?: string | Doc; // default layout string for a document
     childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view)
+    childLayoutString?: string; // template string for collection to use to render its children
     hideFilterView?: boolean; // whether to hide the filter popout on collections
     hideHeadings?: boolean; // whether stacking view column headings should be hidden
     isTemplateForField?: string; // the field key for which the containing document is a rendering template
@@ -123,7 +124,7 @@ export interface DocumentOptions {
     displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
     borderRounding?: string;
     boxShadow?: string;
-    dontRegisterChildren?: boolean;
+    dontRegisterChildViews?: boolean;
     "onDoubleClick-rawScript"?: string; // onDoubleClick script in raw text form
     "onClick-rawScript"?: string; // onClick script in raw text form
     "onCheckedClick-rawScript"?: string; // onChecked script in raw text form
@@ -592,9 +593,7 @@ export namespace Docs {
             linkDocProto.anchor1_timecode = source.doc.currentTimecode || source.doc.displayTimecode;
             linkDocProto.anchor2_timecode = target.doc.currentTimecode || target.doc.displayTimecode;
 
-            if (linkDocProto.layout_key1 === undefined) {
-                Cast(linkDocProto.proto, Doc, null).layout_key1 = LinkAnchorBox.LayoutString("anchor1");
-                Cast(linkDocProto.proto, Doc, null).layout_key2 = LinkAnchorBox.LayoutString("anchor2");
+            if (linkDocProto.linkBoxExcludedKeys === undefined) {
                 Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "lastOpened", "creationDate", "author"]);
                 Cast(linkDocProto.proto, Doc, null).layoutKey = undefined;
             }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 4683e77a8..1ba6f0248 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -117,7 +117,7 @@ export class DocumentManager {
                 pairs.push(...linksList.reduce((pairs, link) => {
                     const linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document);
                     linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
-                        if (dv.props.Document.type !== DocumentType.LINK || dv.props.layoutKey !== docView1.props.layoutKey) {
+                        if (dv.props.Document.type !== DocumentType.LINK || dv.props.LayoutTemplateString !== docView1.props.LayoutTemplateString) {
                             pairs.push({ a: dv, b: docView1, l: link });
                         }
                     });
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 629b0f447..fd0d2bdbb 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -11,7 +11,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
 interface DocComponentProps {
     Document: Doc;
-    LayoutDoc?: () => Opt<Doc>;
+    LayoutTemplate?: () => Opt<Doc>;
 }
 export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends Touchable<P> {
@@ -20,7 +20,7 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D
         // This is the "The Document" -- it encapsulates, data, layout, and any templates
         @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
         // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
-        @computed get layoutDoc() { return Doc.Layout(this.props.Document, this.props.LayoutDoc?.()); }
+        @computed get layoutDoc() { return Doc.Layout(this.props.Document, this.props.LayoutTemplate?.()); }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
 
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 61d517d43..4f34eb9e3 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -44,7 +44,6 @@ $linkGap : 3px;
 
     .documentDecorations-radius {
         pointer-events: auto;
-        background: black;
         opacity: 1;
         transform: translate(10px, 10px);
         grid-row: 4;
@@ -88,12 +87,12 @@ $linkGap : 3px;
         opacity: 1;
     }
     #documentDecorations-topLeftResizer {
-        border-left: black 2px solid;
-        border-top: black solid 2px;
+        border-left:  2px solid;
+        border-top:  solid 2px;
     }
     #documentDecorations-bottomRightResizer {
-        border-right: black 2px solid;
-        border-bottom: black solid 2px;
+        border-right:  2px solid;
+        border-bottom:  solid 2px;
     }
     #documentDecorations-topLeftResizer:hover,
     #documentDecorations-bottomRightResizer:hover {
@@ -111,12 +110,12 @@ $linkGap : 3px;
         opacity: 1;
     }
     #documentDecorations-topRightResizer {
-        border-right: black 2px solid;
-        border-top: black 2px solid;
+        border-right:  2px solid;
+        border-top:  2px solid;
     }
     #documentDecorations-bottomLeftResizer {
-        border-left: black 2px solid;
-        border-bottom: black 2px solid;
+        border-left:  2px solid;
+        border-bottom:  2px solid;
     }
     #documentDecorations-topRightResizer:hover,
     #documentDecorations-bottomLeftResizer:hover {
@@ -136,7 +135,6 @@ $linkGap : 3px;
     }
 
     .documentDecorations-contextMenu {
-        background: $alt-accent;
         width: 25px;
         height: calc(100% + 8px); // 8px for the height of the top resizer bar
         grid-column-start: 1;
@@ -144,14 +142,14 @@ $linkGap : 3px;
         pointer-events: all;
     }
     .documentDecorations-title {
-        background: $alt-accent;
         opacity: 1;
         grid-column-start: 3;
         grid-column-end: 4;
         pointer-events: auto;
         overflow: hidden;
         text-align: center;
-        display:flex;
+        display: flex;    
+        border-bottom: solid 1px;
     }
     .publishBox {
         width: 20px;
@@ -168,7 +166,6 @@ $linkGap : 3px;
 
 
 .documentDecorations-closeButton {
-    background: $alt-accent;
     opacity: 1;
     grid-column-start: 4;
     grid-column-end: 6;
@@ -178,7 +175,6 @@ $linkGap : 3px;
 }
 
 .documentDecorations-minimizeButton {
-    background: $alt-accent;
     opacity: 1;
     grid-column-start: 1;
     grid-column-end: 3;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e2759291a..396fe12b5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DataSym, Field } from "../../new_fields/Doc";
-import { PositionDocument } from '../../new_fields/documentSchemas';
+import { Document } from '../../new_fields/documentSchemas';
 import { ScriptField } from '../../new_fields/ScriptField';
 import { Cast, StrCast, NumCast } from "../../new_fields/Types";
 import { Utils, setupMoveUpEvents, emptyFunction, returnFalse, simulateMouseClick } from "../../Utils";
@@ -301,7 +301,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
         SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
             if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
-                const doc = PositionDocument(element.rootDoc);
+                const doc = Document(element.rootDoc);
                 let nwidth = doc._nativeWidth || 0;
                 let nheight = doc._nativeHeight || 0;
                 const width = (doc._width || 0);
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 81d427f64..04288a9e1 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -39,7 +39,12 @@
     }
 }
 
+.mainView-container {
+    color:dimgray;
+}
+
 .mainView-container-dark {
+    color: lightgray;
     .lm_goldenlayout {
         background: dimgray;
     }
@@ -54,7 +59,6 @@
     }
     .contextMenu-cont, .contextMenu-item {
         background: dimGray;
-        color: lightgray;
     }
     .contextMenu-item:hover {
         background: gray;
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 4086294ad..769b323ae 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -2,9 +2,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { observable, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { documentSchema } from '../../../new_fields/documentSchemas';
+import { documentSchema, collectionSchema } from '../../../new_fields/documentSchemas';
 import { makeInterface } from '../../../new_fields/Schema';
-import { NumCast, StrCast, ScriptCast } from '../../../new_fields/Types';
+import { NumCast, StrCast, ScriptCast, Cast } from '../../../new_fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
 import "./CollectionCarouselView.scss";
@@ -16,8 +16,8 @@ import { ContextMenu } from '../ContextMenu';
 import { ObjectField } from '../../../new_fields/ObjectField';
 import { returnFalse } from '../../../Utils';
 
-type CarouselDocument = makeInterface<[typeof documentSchema,]>;
-const CarouselDocument = makeInterface(documentSchema);
+type CarouselDocument = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
+const CarouselDocument = makeInterface(documentSchema, collectionSchema);
 
 @observer
 export class CollectionCarouselView extends CollectionSubView(CarouselDocument) {
@@ -40,7 +40,6 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
         e.stopPropagation();
         this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
     }
-
     panelHeight = () => this.props.PanelHeight() - 50;
     @computed get content() {
         const index = NumCast(this.layoutDoc._itemIndex);
@@ -51,6 +50,8 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
                         onDoubleClick={ScriptCast(this.layoutDoc.onChildDoubleClick)}
                         onClick={ScriptCast(this.layoutDoc.onChildClick)}
                         renderDepth={this.props.renderDepth + 1}
+                        LayoutTemplate={this.props.ChildLayoutTemplate}
+                        LayoutTemplateString={this.props.ChildLayoutString}
                         Document={this.childLayoutPairs[index].layout}
                         DataDoc={this.childLayoutPairs[index].data}
                         PanelHeight={this.panelHeight}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 01766f65f..eb70cec9d 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,15 +4,17 @@ import { CursorProperty } from "csstype";
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import Switch from 'rc-switch';
-import { Doc, HeightSym, WidthSym, DataSym } from "../../../new_fields/Doc";
+import { DataSym, Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
+import { collectionSchema, documentSchema } from "../../../new_fields/documentSchemas";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
+import { listSpec, makeInterface } from "../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from "../../../new_fields/util";
-import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils";
+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";
@@ -24,11 +26,14 @@ import "./CollectionStackingView.scss";
 import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
 import { CollectionSubView } from "./CollectionSubView";
 import { CollectionViewType } from "./CollectionView";
-import { SelectionManager } from "../../util/SelectionManager";
+import { ScriptField } from "../../../new_fields/ScriptField";
 const _global = (window /* browser */ || global /* node */) as any;
 
+type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>;
+const StackingDocument = makeInterface(collectionSchema, documentSchema);
+
 @observer
-export class CollectionStackingView extends CollectionSubView(doc => doc) {
+export class CollectionStackingView extends CollectionSubView(StackingDocument) {
     _masonryGridRef: HTMLDivElement | null = null;
     _draggerRef = React.createRef<HTMLDivElement>();
     _pivotFieldDisposer?: IReactionDisposer;
@@ -116,7 +121,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
 
     getSimpleDocHeight(d?: Doc) {
         if (!d) return 0;
-        const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+        const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.());
         const nw = NumCast(layoutDoc._nativeWidth);
         const nh = NumCast(layoutDoc._nativeHeight);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
@@ -160,14 +165,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         }
         return this.props.addDocTab(doc, where);
     }
+
     getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
-        const layoutDoc = Doc.Layout(doc, this.props.childLayoutTemplate?.());
+        const layoutDoc = Doc.Layout(doc, this.props.ChildLayoutTemplate?.());
         const height = () => this.getDocHeight(doc);
         return <ContentFittingDocumentView
             Document={doc}
             DataDoc={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
             backgroundColor={this.props.backgroundColor}
-            LayoutDoc={this.props.childLayoutTemplate}
+            LayoutTemplate={this.props.ChildLayoutTemplate}
+            LayoutTemplateString={this.props.ChildLayoutString}
             LibraryPath={this.props.LibraryPath}
             FreezeDimensions={this.props.freezeChildDimensions}
             renderDepth={this.props.renderDepth + 1}
@@ -176,7 +183,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             PanelHeight={height}
             NativeHeight={returnZero}
             NativeWidth={returnZero}
-            fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+            fitToBox={false}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
@@ -199,13 +206,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
 
     getDocWidth(d?: Doc) {
         if (!d) return 0;
-        const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+        const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.());
         const nw = NumCast(layoutDoc._nativeWidth);
         return Math.min(nw && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
     }
     getDocHeight(d?: Doc) {
         if (!d) return 0;
-        const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+        const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.());
         const nw = NumCast(layoutDoc._nativeWidth);
         const nh = NumCast(layoutDoc._nativeHeight);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e44bbae78..aaea13ded 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -43,6 +43,8 @@ export interface CollectionViewProps extends FieldViewProps {
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
     children?: never | (() => JSX.Element[]) | React.ReactNode;
+    ChildLayoutTemplate?: () => Doc;
+    ChildLayoutString?: string;
     childClickScript?: ScriptField;
     childDoubleClickScript?: ScriptField;
     freezeChildDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 045134225..a2d4774c8 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -28,7 +28,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     @observable _childClickedScript: Opt<ScriptField>;
     @observable _viewDefDivClick: Opt<ScriptField>;
     async componentDidMount() {
-        const detailView = (await DocCastAsync(this.props.Document.childDetailView)) || Doc.findTemplate("detailView", StrCast(this.props.Document.type), "");
+        const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || Doc.findTemplate("detailView", StrCast(this.props.Document.type), "");
         const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
         runInAction(() => {
             this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! });
@@ -84,7 +84,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 
     @computed get contents() {
         return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%", pointerEvents: this.props.active() ? undefined : "none" }} onPointerDown={this.contentsDown}>
-            <CollectionFreeFormView {...this.props} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} fitToBox={true} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
+            <CollectionFreeFormView {...this.props} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} fitToBox={true} freezeChildDimensions={true} layoutEngine={this.layoutEngine} />
         </div>;
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 296c1a39c..dc9348664 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -6,7 +6,6 @@ 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 { RichTextField } from '../../../new_fields/RichTextField';
 import { Document, listSpec } from '../../../new_fields/Schema';
 import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types';
@@ -15,7 +14,6 @@ import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from "../../documents/DocumentTypes";
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
-import { makeTemplate } from '../../util/DropConverter';
 import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from '../../util/SelectionManager';
 import { Transform } from '../../util/Transform';
@@ -481,7 +479,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         parentActive={returnTrue}
                         whenActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
-                        dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildren)}
+                        dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildViews)}
                         ContainingCollectionView={undefined}
                         ContainingCollectionDoc={this.props.containingCollection}
                     />}
@@ -743,11 +741,11 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
                             Doc.GetProto(img).doubleClickView = doubleClickView;
                         }
                     });
-                    d.layout = ImageBox.LayoutString("hero");
+                    Doc.GetProto(d).layout = ImageBox.LayoutString("hero");
                 });
 
                 Document.childLayoutTemplate = heroView;
-                Document.childDetailView = detailView;
+                Document.childClickedOpenTemplateView = detailView;
                 Document._viewType = CollectionViewType.Time;
                 Document._forceActive = true;
                 Document._pivotField = "company";
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 91018980c..d2afb4855 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -72,7 +72,8 @@ export enum CollectionViewType {
     Pile = "pileup"
 }
 export interface CollectionViewCustomProps {
-    filterAddDocument: (doc: Doc) => boolean;
+    filterAddDocument: (doc: Doc) => boolean;  // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
+    childLayoutTemplate?: () => Opt<Doc>;  // specify a layout Doc template to use for children of the collection
 }
 
 export interface CollectionRenderProps {
@@ -82,6 +83,8 @@ export interface CollectionRenderProps {
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     PanelWidth: () => number;
+    ChildLayoutTemplate?: () => Doc;
+    ChildLayoutString?: string;
 }
 
 @observer
@@ -245,8 +248,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             if (this.props.Document.childLayout instanceof Doc) {
                 layoutItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
             }
-            if (this.props.Document.childDetailView instanceof Doc) {
-                layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailView as Doc, "onRight"), icon: "project-diagram" });
+            if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
+                layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
             }
             layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
@@ -474,6 +477,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 </div>
             </div>;
     }
+    childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
 
     render() {
         TraceMobx();
@@ -483,7 +487,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             moveDocument: this.moveDocument,
             active: this.active,
             whenActiveChanged: this.whenActiveChanged,
-            PanelWidth: this.bodyPanelWidth
+            PanelWidth: this.bodyPanelWidth,
+            ChildLayoutTemplate: this.childLayoutTemplate,
+            ChildLayoutString: StrCast(this.props.Document.childLayoutString),
         };
         return (<div className={"collectionView"}
             style={{
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index d26e3a38b..62b03bbdc 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -44,9 +44,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         initialize: emptyFunction,
     };
     _narrativeCommand = {
-        params: ["target", "source"], title: "=> click item view",
-        script: "this.target.childDetailView = getDocTemplate(this.source?.[0])",
-        immediate: (source: Doc[]) => this.target.childDetailView = Doc.getDocTemplate(source?.[0]),
+        params: ["target", "source"], title: "=> click clicked open view",
+        script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
+        immediate: (source: Doc[]) => this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]),
         initialize: emptyFunction,
     };
     _contentCommand = {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index cf12ef382..d67d1993e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -40,7 +40,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                 const bpt = Utils.closestPtBetweenRectangles(bbounds.left, bbounds.top, bbounds.width, bbounds.height,
                     abounds.left, abounds.top, abounds.width, abounds.height,
                     apt.point.x, apt.point.y);
-                const afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
+                const afield = this.props.A.props.LayoutTemplateString?.indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
                 const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
 
                 // really hacky stuff to make the LinkAnchorBox display where we want it to:
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 4b5e977df..7df1e909f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -30,7 +30,7 @@ export class CollectionFreeFormLinksView extends React.Component {
             return drawnPairs;
         }, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]);
         return connections.filter(c =>
-            c.a.props.layoutKey && c.b.props.layoutKey && c.a.props.Document.type === DocumentType.LINK &&
+            c.a.props.Document.type === DocumentType.LINK &&
             c.a.props.bringToFront !== emptyFunction && c.b.props.bringToFront !== emptyFunction // bcz: this prevents links to be drawn to anchors in CollectionTree views -- this is a hack that should be fixed
         ).map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 45ef0455e..707e103fb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -5,7 +5,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
 import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
-import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
+import { documentSchema, collectionSchema } from "../../../../new_fields/documentSchemas";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkData, InkField, InkTool, PointData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
@@ -66,8 +66,8 @@ export const panZoomSchema = createSchema({
     fitH: "number"
 });
 
-type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
-const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
+type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof collectionSchema, typeof documentSchema, typeof pageSchema]>;
+const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentSchema, pageSchema);
 export type collectionFreeformViewProps = {
     forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
     viewDefDivClick?: ScriptField;
@@ -858,7 +858,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     backgroundHalo = () => BoolCast(this.Document.useClusters);
     parentActive = () => this.props.active() || this.backgroundActive ? true : false;
-    childLayoutFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
         return {
             ...this.props,
@@ -868,7 +867,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             DataDoc: childData,
             Document: childLayout,
             LibraryPath: this.libraryPath,
-            LayoutDoc: this.childLayoutFunc,
+            LayoutTemplate: this.props.ChildLayoutTemplate,
+            LayoutTemplateString: this.props.ChildLayoutString,
             FreezeDimensions: this.props.freezeChildDimensions,
             layoutKey: undefined,
             setupDragLines: this.setupDragLines,
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index b3a6a9deb..38b16e184 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -218,7 +218,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             Document={layout}
             DataDoc={layout.resolvedDataDoc as Doc}
             backgroundColor={this.props.backgroundColor}
-            LayoutDoc={this.props.childLayoutTemplate}
+            LayoutTemplate={this.props.ChildLayoutTemplate}
+            LayoutTemplateString={this.props.ChildLayoutString}
             LibraryPath={this.props.LibraryPath}
             FreezeDimensions={this.props.freezeChildDimensions}
             renderDepth={this.props.renderDepth + 1}
@@ -226,7 +227,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             PanelHeight={height}
             NativeHeight={returnZero}
             NativeWidth={returnZero}
-            fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+            fitToBox={false}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 0fb29ca61..b55b67a9e 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -217,7 +217,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             Document={layout}
             DataDoc={layout.resolvedDataDoc as Doc}
             backgroundColor={this.props.backgroundColor}
-            LayoutDoc={this.props.childLayoutTemplate}
+            LayoutTemplate={this.props.ChildLayoutTemplate}
+            LayoutTemplateString={this.props.ChildLayoutString}
             LibraryPath={this.props.LibraryPath}
             FreezeDimensions={this.props.freezeChildDimensions}
             renderDepth={this.props.renderDepth + 1}
@@ -225,7 +226,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             PanelHeight={height}
             NativeHeight={returnZero}
             NativeWidth={returnZero}
-            fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+            fitToBox={false}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 6ff6d1b42..3feb533a0 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -21,6 +21,7 @@ import { Docs } from "../../documents/Documents";
 import { ComputedField } from "../../../new_fields/ScriptField";
 import { Networking } from "../../Network";
 import { Upload } from "../../../server/SharedMediaTypes";
+import { LinkAnchorBox } from "./LinkAnchorBox";
 
 // testing testing 
 
@@ -266,7 +267,8 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
                                                 NativeHeight={returnZero}
                                                 NativeWidth={returnZero}
                                                 rootSelected={returnFalse}
-                                                layoutKey={Doc.LinkEndpoint(l, la2)}
+                                                LayoutTemplate={undefined}
+                                                LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(l, la2)}`)}
                                                 ContainingCollectionDoc={this.props.Document}
                                                 parentActive={returnTrue}
                                                 bringToFront={emptyFunction}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 24468dcc1..2894d9b65 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -7,7 +7,7 @@ import { DocComponent } from "../DocComponent";
 import "./CollectionFreeFormDocumentView.scss";
 import { DocumentView, DocumentViewProps } from "./DocumentView";
 import React = require("react");
-import { PositionDocument } from "../../../new_fields/documentSchemas";
+import { Document } from "../../../new_fields/documentSchemas";
 import { TraceMobx } from "../../../new_fields/util";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 
@@ -28,7 +28,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 }
 
 @observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, Document>(Document) {
     @observable _animPos: number[] | undefined = undefined;
     random(min: number, max: number) { // min should not be equal to max
         const mseed = Math.abs(this.X * this.Y);
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 637fd5acc..3c2c6c87e 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -14,7 +14,7 @@ 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.LayoutDoc?.() || Doc.Layout(this.props.Document); }
+    private get layoutDoc() { return this.props.LayoutTemplate?.() || 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()));
@@ -56,7 +56,8 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
                     <DocumentView {...this.props}
                         Document={this.props.Document}
                         DataDoc={this.props.DataDoc}
-                        LayoutDoc={this.props.LayoutDoc}
+                        LayoutTemplate={this.props.LayoutTemplate}
+                        LayoutTemplateString={this.props.LayoutTemplateString}
                         LibraryPath={this.props.LibraryPath}
                         NativeWidth={this.nativeWidth}
                         NativeHeight={this.nativeHeight}
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 88eeb1137..8d422fe67 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { IReactionDisposer, reaction, computed } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, Field } from "../../../new_fields/Doc";
-import { documentSchema } from "../../../new_fields/documentSchemas";
+import { documentSchema, collectionSchema } from "../../../new_fields/documentSchemas";
 import { makeInterface } from "../../../new_fields/Schema";
 import { ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
@@ -16,9 +16,11 @@ 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]>;
-const DocHolderBoxDocument = makeInterface(documentSchema);
+type DocHolderBoxSchema = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
+const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema);
 
 @observer
 export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) {
@@ -43,7 +45,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
         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.childTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childTemplateName = this.layoutDoc.childTemplateName ? undefined : "keyValue", 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" });
     }
@@ -103,6 +105,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
     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;
     get renderContents() {
         const containedDoc = Cast(this.dataDoc[this.props.fieldKey], Doc, null);
         const childTemplateName = StrCast(this.layoutDoc.childTemplateName);
@@ -112,33 +115,62 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
                 Doc.expandTemplateLayout(Cast(containedDoc["layout_" + childTemplateName], Doc, null), containedDoc, undefined);
             }, 0);
         }
-        const contents = !(containedDoc instanceof Doc) ? (null) : <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}
-            layoutKey={childTemplateName ? "layout_" + childTemplateName : "layout"}
-            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.props.active}
-            dontRegisterView={!this.isSelectionLocked()}
-            whenActiveChanged={this.props.whenActiveChanged}
-            bringToFront={returnFalse}
-            ContentScaling={returnOne}
-        />;
+        const contents = !(containedDoc instanceof Doc) ? (null) : this.layoutDoc.childLayoutString ?
+            <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={StrCast(this.layoutDoc.childLayoutString)}
+                //layoutKey={childTemplateName ? "layout_" + childTemplateName : "layout"}
+                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={!this.isSelectionLocked()}
+                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={StrCast(this.layoutDoc.childLayoutString)}
+                //layoutKey={childTemplateName ? "layout_" + childTemplateName : "layout"}
+                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={!this.isSelectionLocked()}
+                whenActiveChanged={this.props.whenActiveChanged}
+                bringToFront={returnFalse}
+                ContentScaling={returnOne}
+            />;
         return contents;
     }
     render() {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 4d20d3e2c..749fb98be 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -96,8 +96,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     isSelected: (outsideReaction: boolean) => boolean,
     select: (ctrl: boolean) => void,
     layoutKey: string,
-    forceLayout?: string,
-    forceFieldKey?: string,
     hideOnLeave?: boolean,
     makeLink?: () => Opt<Doc>,  // function to call when a link is made
 }> {
@@ -105,6 +103,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         TraceMobx();
         if (!this.layoutDoc) return "<p>awaiting layout</p>";
         // const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, this.layoutDoc === this.props.Document ? this.props.layoutKey : "layout")], "string");  // bcz: replaced this with below... is it right?
+        if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString;
         const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, "layout")], "string");
         if (this.props.layoutKey === "layout_keyValue") {
             return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString("data"));
@@ -127,8 +126,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     get layoutDoc() {
         const params = StrCast(this.props.Document.PARAMS);
         // bcz: replaced this with below : is it correct?  change was made to accommodate passing fieldKey's from a layout script
-        // const template: Doc = this.props.LayoutDoc?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
-        const template: Doc = this.props.LayoutDoc?.() ||
+        // const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
+        const template: Doc = this.props.LayoutTemplate?.() || (this.props.LayoutTemplateString && this.props.Document) ||
             (this.props.layoutKey && StrCast(this.props.Document[this.props.layoutKey]) && this.props.Document) ||
             Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
         return Doc.expandTemplateLayout(template, this.props.Document, params ? "(" + params + ")" : this.props.layoutKey);
@@ -186,25 +185,22 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         //  layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
 
         return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc) ? (null) :
-            this.props.forceLayout === "FormattedTextBox" && this.props.forceFieldKey ?
-                <FormattedTextBox {...bindings.props} fieldKey={this.props.forceFieldKey} />
-                :
-                <ObserverJsxParser
-                    key={42}
-                    blacklistedAttrs={[]}
-                    renderInWrapper={false}
-                    components={{
-                        FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
-                        CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
-                        PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
-                        ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
-                        RecommendationsBox, ScreenshotBox, HTMLtag
-                    }}
-                    bindings={bindings}
-                    jsx={layoutFrame}
-                    showWarnings={true}
-
-                    onError={(test: any) => { console.log(test); }}
-                />;
+            <ObserverJsxParser
+                key={42}
+                blacklistedAttrs={[]}
+                renderInWrapper={false}
+                components={{
+                    FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
+                    CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
+                    PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
+                    ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
+                    RecommendationsBox, ScreenshotBox, HTMLtag
+                }}
+                bindings={bindings}
+                jsx={layoutFrame}
+                showWarnings={true}
+
+                onError={(test: any) => { console.log(test); }}
+            />;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f1efa48f4..7c7c03db2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -5,22 +5,21 @@ import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
-import { Document, PositionDocument } from '../../../new_fields/documentSchemas';
+import { Document } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { InkTool } from '../../../new_fields/InkField';
-import { RichTextField } from '../../../new_fields/RichTextField';
 import { listSpec } from "../../../new_fields/Schema";
 import { SchemaHeaderField } from '../../../new_fields/SchemaHeaderField';
 import { ScriptField } from '../../../new_fields/ScriptField';
 import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { AudioField, ImageField, PdfField, VideoField } from '../../../new_fields/URLField';
+import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { ClientRecommender } from '../../ClientRecommender';
 import { DocServer } from "../../DocServer";
-import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
+import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from '../../documents/DocumentTypes';
 import { ClientUtils } from '../../util/ClientUtils';
 import { DocumentManager } from "../../util/DocumentManager";
@@ -42,6 +41,7 @@ import { InkingControl } from '../InkingControl';
 import { KeyphraseQueryView } from '../KeyphraseQueryView';
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
+import { LinkAnchorBox } from './LinkAnchorBox';
 import { RadialMenu } from './RadialMenu';
 import React = require("react");
 
@@ -58,7 +58,8 @@ export interface DocumentViewProps {
     NativeHeight: () => number;
     Document: Doc;
     DataDoc?: Doc;
-    LayoutDoc?: () => Opt<Doc>;
+    LayoutTemplateString?: string;
+    LayoutTemplate?: () => Opt<Doc>;
     LibraryPath: Doc[];
     fitToBox?: boolean;
     contextMenuItems?: () => { script: ScriptField, label: string }[];
@@ -454,8 +455,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             const dY = -1 * Math.sign(dH);
 
             if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
-                const doc = PositionDocument(this.props.Document);
-                const layoutDoc = PositionDocument(Doc.Layout(this.props.Document));
+                const doc = Document(this.props.Document);
+                const layoutDoc = Document(Doc.Layout(this.props.Document));
                 let nwidth = layoutDoc._nativeWidth || 0;
                 let nheight = layoutDoc._nativeHeight || 0;
                 const width = (layoutDoc._width || 0);
@@ -984,13 +985,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get contents() {
         TraceMobx();
         return (<>
-            <DocumentContentsView key={1} ContainingCollectionView={this.props.ContainingCollectionView}
+            <DocumentContentsView key={1}
+                ContainingCollectionView={this.props.ContainingCollectionView}
                 ContainingCollectionDoc={this.props.ContainingCollectionDoc}
                 NativeWidth={this.NativeWidth}
                 NativeHeight={this.NativeHeight}
                 Document={this.props.Document}
                 DataDoc={this.props.DataDoc}
-                LayoutDoc={this.props.LayoutDoc}
+                LayoutTemplateString={this.props.LayoutTemplateString}
+                LayoutTemplate={this.props.LayoutTemplate}
                 makeLink={this.makeLink}
                 rootSelected={this.rootSelected}
                 RenderData={this.props.RenderData}
@@ -1021,7 +1024,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         </>
         );
     }
-    linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document);
 
     // used to decide whether a link anchor view should be created or not.
     // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
@@ -1049,12 +1051,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection  Need a better prop for passing the containing document to the LinkAnchorBox
                 PanelWidth={this.anchorPanelWidth}
                 PanelHeight={this.anchorPanelHeight}
-                layoutKey={this.linkEndpoint(d)}
                 ContentScaling={returnOne}
                 backgroundColor={returnTransparent}
                 removeDocument={this.hideLinkAnchor}
                 pointerEvents={false}
-                LayoutDoc={undefined}
+                LayoutTemplate={undefined}
+                LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)}
             />);
     }
     @computed get innards() {
@@ -1073,8 +1075,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             <div className="documentView-captionWrapper" style={{ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]), color: StrCast(this.layoutDoc["caption-color"]) }}>
                 <DocumentContentsView {...OmitKeys(this.props, ['children']).omit}
                     hideOnLeave={true}
-                    forceLayout={"FormattedTextBox"}
-                    forceFieldKey={showCaption}
+                    layoutTemplateString={`<FormattedTextBox {...props} fieldKey={'${showCaption}'}/>`}
                     ContentScaling={this.childScaling}
                     ChromeHeight={this.chromeHeight}
                     isSelected={this.isSelected}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 1efee4f5a..40d55ce38 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -50,13 +50,12 @@ export interface FieldViewProps {
     setVideoBox?: (player: VideoBox) => void;
     ContentScaling: () => number;
     ChromeHeight?: () => number;
-    childLayoutTemplate?: () => Opt<Doc>;
+    RenderData?: () => Doc;
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
     height?: number;
     width?: number;
     background?: string;
     color?: string;
-    RenderData?: () => Doc;
 }
 
 @observer
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 2970674a2..d43936949 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -36,7 +36,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     @observable private _keyInput: string = "";
     @observable private _valueInput: string = "";
     @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
-    get fieldDocToLayout() { return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document; }
+    get fieldDocToLayout() { return this.props.Document; }
 
     @action
     onEnterKey = (e: React.KeyboardEvent): void => {
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 6c50abf21..bc36e056e 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -17,7 +17,6 @@ import { LinkEditor } from "../linking/LinkEditor";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { SelectionManager } from "../../util/SelectionManager";
 import { TraceMobx } from "../../../new_fields/util";
-import { DocumentView } from "./DocumentView";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 125690dc7..a0ecc9ff5 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, IReactionDisposer, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from 'request-promise';
-import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas";
+import { documentSchema } from "../../../new_fields/documentSchemas";
 import { makeInterface } from "../../../new_fields/Schema";
 import { Cast, NumCast } from "../../../new_fields/Types";
 import { VideoField } from "../../../new_fields/URLField";
@@ -20,8 +20,8 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./ScreenshotBox.scss";
 const path = require('path');
 
-type ScreenshotDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>;
-const ScreenshotDocument = makeInterface(documentSchema, positionSchema);
+type ScreenshotDocument = makeInterface<[typeof documentSchema]>;
+const ScreenshotDocument = makeInterface(documentSchema);
 
 library.add(faVideo);
 
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 613929bca..266b7f43f 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -21,14 +21,14 @@ import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./VideoBox.scss";
-import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas";
+import { documentSchema } from "../../../new_fields/documentSchemas";
 const path = require('path');
 
 export const timeSchema = createSchema({
     currentTimecode: "number",  // the current time of a video or other linear, time-based document.  Note, should really get set on an extension field, but that's more complicated when it needs to be set since the extension doc needs to be found first
 });
-type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>;
-const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
+type VideoDocument = makeInterface<[typeof documentSchema, typeof timeSchema]>;
+const VideoDocument = makeInterface(documentSchema, timeSchema);
 
 library.add(faVideo);
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 71325d94f..9256f82c2 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -772,7 +772,7 @@ export namespace Doc {
 
 
     export function LinkOtherAnchor(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? Cast(linkDoc.anchor2, Doc) as Doc : Cast(linkDoc.anchor1, Doc) as Doc; }
-    export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layout_key1" : "layout_key2"; }
+    export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "1" : "2"; }
 
     export function linkFollowUnhighlight() {
         Doc.UnhighlightAll();
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index fd9a304f9..e71fc27f3 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -4,13 +4,25 @@ import { Doc } from "./Doc";
 import { DateField } from "./DateField";
 
 export const documentSchema = createSchema({
+    // content properties
     type: "string",             // enumerated type of document -- should be template-specific (ie, start with an '_')
-    layout: "string",           // this is the native layout string for the document.  templates can be added using other fields and setting layoutKey below
-    layoutKey: "string",        // holds the field key for the field that actually holds the current lyoat
     title: "string",            // document title (can be on either data document or layout)
-    dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
-    targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move")
-    childDropAction: "string",  // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
+    isTemplateForField: "string",// if specified, it indicates the document is a template that renders the specified field
+    creationDate: DateField,    // when the document was created
+    links: listSpec(Doc),       // computed (readonly) list of links associated with this document
+
+    // "Location" properties in a very general sense
+    currentTimecode: "number",  // current play back time of a temporal document (video / audio)
+    displayTimecode: "number",  // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
+    inOverlay: "boolean",       // whether the document is rendered in an OverlayView which handles selection/dragging differently
+    x: "number",                // x coordinate when in a freeform view 
+    y: "number",                // y coordinate when in a freeform view 
+    z: "number",                // z "coordinate" - non-zero specifies the overlay layer of a freeformview
+    zIndex: "number",           // zIndex of a document in a freeform view
+    scrollY: "number",          // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
+    scrollTop: "number",        // scroll position of a scrollable document (pdf, text, web)
+
+    // appearance properties on the layout
     _autoHeight: "boolean",     // whether the height of the document should be computed automatically based on its contents
     _nativeWidth: "number",     // native width of document which determines how much document contents are scaled when the document's width is set
     _nativeHeight: "number",    // "
@@ -27,59 +39,57 @@ export const documentSchema = createSchema({
     _showAudio: "boolean",      // whether to show the audio record icon on documents
     _freeformLayoutEngine: "string",// the string ID for the layout engine to use to layout freeform view documents
     _LODdisable: "boolean",     // whether to disbale LOD switching for CollectionFreeFormViews
-    _pivotField: "string",      // specifies which field should be used as the timeline/pivot axis
+    _pivotField: "string",      // specifies which field key should be used as the timeline/pivot axis
     _replacedChrome: "string",  // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's.
     _chromeStatus: "string",    // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed'
-    _freezeChildDimensions: "boolean", // freezes child document dimensions (e.g., used by time/pivot view to make sure all children will be scaled to fit their display rectangle)
     _fontSize: "number",
     _fontFamily: "string",
-    isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
-    color: "string",            // foreground color of document
+
+    // appearance properties on the data document
     backgroundColor: "string",  // background color of document
+    borderRounding: "string",   // border radius rounding of document
+    boxShadow: "string",        // the amount of shadow around the perimeter of a document
+    color: "string",            // foreground color of document
+    fitToBox: "boolean",        // whether freeform view contents should be zoomed/panned to fill the area of the document view
+    fontSize: "string",
+    layout: "string",           // this is the native layout string for the document.  templates can be added using other fields and setting layoutKey below
+    layoutKey: "string",        // holds the field key for the field that actually holds the current lyoat
+    letterSpacing: "string",
     opacity: "number",          // opacity of document
-    creationDate: DateField,    // when the document was created
-    links: listSpec(Doc),       // computed (readonly) list of links associated with this document
+    strokeWidth: "number",
+    textTransform: "string",
+    treeViewOpen: "boolean",    //  flag denoting whether the documents sub-tree (contents) is visible or hidden
+    treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
+    treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+
+    // interaction and linking properties
+    ignoreClick: "boolean",     // whether documents ignores input clicks (but does not ignore manipulation and other events) 
     onClick: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
     onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
     onPointerUp: ScriptField,   // script to run when document is clicked (can be overriden by an onClick prop)
     onDragStart: ScriptField,   // script to run when document is dragged (without being selected).  the script should return the Doc to be dropped.
-    dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
-    removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
-    isTemplateForField: "string",// when specifies a field key, then the containing document is a template that renders the specified field
-    isBackground: "boolean",    // whether document is a background element and ignores input events (can only select with marquee)
-    treeViewOpen: "boolean",    //  flag denoting whether the documents sub-tree (contents) is visible or hidden
-    treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
-    treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
-    currentTimecode: "number",  // current play back time of a temporal document (video / audio)
     followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, ) 
+    isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
+    isLinkButton: "boolean",    // whether document functions as a link follow button to follow the first link on the document when clicked   
+    isBackground: "boolean",    // whether document is a background element and ignores input events (can only select with marquee)
     lockedPosition: "boolean",  // whether the document can be moved (dragged)
     lockedTransform: "boolean", // whether the document can be panned/zoomed
-    inOverlay: "boolean",       // whether the document is rendered in an OverlayView which handles selection/dragging differently
-    borderRounding: "string",   // border radius rounding of document
-    heading: "number",          // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc)
-    isLinkButton: "boolean",    // whether document functions as a link follow button to follow the first link on the document when clicked   
-    ignoreClick: "boolean",     // whether documents ignores input clicks (but does not ignore manipulation and other events) 
-    scrollToLinkID: "string",   // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc.
-    scrollY: "number",          // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
-    scrollTop: "number",        // scroll position of a scrollable document (pdf, text, web)
-    strokeWidth: "number",
-    fontSize: "string",
-    fitToBox: "boolean",        // whether freeform view contents should be zoomed/panned to fill the area of the document view
-    letterSpacing: "string",
-    textTransform: "string",
-    childTemplateName: "string" // the name of a template to use to override the layoutKey when rendering a document in DocHolderBox 
-});
 
-export const positionSchema = createSchema({
-    zIndex: "number",
-    x: "number",
-    y: "number",
-    z: "number",
+    // drag drop properties
+    dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
+    dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
+    targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move")
+    childDropAction: "string",  // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
+    removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
 });
 
+
 export const collectionSchema = createSchema({
-    childLayoutTemplate: Doc, // layout template for children of a collecion
-    childDetailView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to use this field)
+    childLayoutTemplateName: "string", // the name of a template to use to override the layoutKey when rendering a document -- ONLY used in DocHolderBox 
+    childLayoutTemplate: Doc, // layout template to use to render children of a collecion
+    childLayoutString: "string", //layout string to use to render children of a collection
+    childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template)
+    dontRegisterChildViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links scrollToLinkID: "string",   // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc.
     onChildClick: ScriptField, // script to run for each child when its clicked
     onChildDoubleClick: ScriptField, // script to run for each child when its clicked
     onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view
@@ -87,6 +97,3 @@ export const collectionSchema = createSchema({
 
 export type Document = makeInterface<[typeof documentSchema]>;
 export const Document = makeInterface(documentSchema);
-
-export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>;
-export const PositionDocument = makeInterface(documentSchema, positionSchema);
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 2ae42bf52..b9de93559 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -485,7 +485,7 @@ export class CurrentUserUtils {
                 _width: 50, _height: 25, title: "Library", _fontSize: 10,
                 letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
                 sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], {
-                    title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "move", lockedPosition: true, boxShadow: "0 0", dontRegisterChildren: true
+                    title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "move", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true
                 })) as any as Doc,
                 targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
                 onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
-- 
cgit v1.2.3-70-g09d2


From 952bc0d744833ab79f69f2f13abde1e4cee68408 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 2 May 2020 00:21:25 -0400
Subject: prefetch buxton double click view

---
 src/client/views/collections/CollectionTreeView.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index dc9348664..835dfc637 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -31,6 +31,7 @@ import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import { CollectionViewType } from './CollectionView';
 import React = require("react");
+import { PrefetchProxy } from '../../../new_fields/Proxy';
 
 
 export interface TreeViewProps {
@@ -745,7 +746,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
                 });
 
                 Document.childLayoutTemplate = heroView;
-                Document.childClickedOpenTemplateView = detailView;
+                Document.childClickedOpenTemplateView = new PrefetchProxy(detailView);
                 Document._viewType = CollectionViewType.Time;
                 Document._forceActive = true;
                 Document._pivotField = "company";
-- 
cgit v1.2.3-70-g09d2


From 06bba6d058794c9750e4a61653e89580b351ea2c Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 3 May 2020 11:41:13 -0400
Subject: added DelegateDocument creator.  switched default templates to
 delegates.

---
 src/client/documents/Documents.ts                         |  5 +++++
 src/client/util/DropConverter.ts                          |  2 +-
 src/client/views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 11 +++++++----
 src/server/authentication/models/current_user_utils.ts    | 14 +++++++-------
 5 files changed, 21 insertions(+), 13 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 672f94f75..8ea8ded0f 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -746,6 +746,10 @@ export namespace Docs {
             };
             return DockDocument(configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id);
         }
+
+        export function DelegateDocument(proto: Doc, options: DocumentOptions = {}) {
+            return InstanceFromProto(proto, undefined, options);
+        }
     }
 
     export namespace Get {
@@ -1052,4 +1056,5 @@ export namespace DocUtils {
 }
 
 Scripting.addGlobal("Docs", Docs);
+Scripting.addGlobal(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; });
 
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 60a6bbb3c..b1993d401 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -68,7 +68,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
             });
             dbox.dragFactory = layoutDoc;
             dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
-            dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
+            dbox.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory)');
         } else if (doc.isButtonBar) {
             dbox.ignoreClick = true;
         }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 835dfc637..6d6f8d316 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -742,7 +742,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
                             Doc.GetProto(img).doubleClickView = doubleClickView;
                         }
                     });
-                    Doc.GetProto(d).layout = ImageBox.LayoutString("hero");
+                    Doc.GetProto(d).proto = heroView; // all devices "are" heroViews that share the same layout & defaults. Seems better than making them all be independent and copy a layout string  // .layout = ImageBox.LayoutString("hero");
                 });
 
                 Document.childLayoutTemplate = heroView;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 09047a07a..30c4a45ac 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -199,14 +199,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             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);
-            if (!this._applyingChange) {
+            const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null);
+            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 || 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.stringify(state.toJSON()), curText);
+                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
                 } else { // if we've deleted all the text in a note driven by a template, then restore the template data
-                    this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(curTemp.Data)));
+                    this.dataDoc[this.props.fieldKey] = undefined;
+                    this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
                     this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
                 }
                 this._applyingChange = false;
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index a5b84a0a1..b63a3dbbf 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -49,7 +49,7 @@ export class CurrentUserUtils {
             );
             queryTemplate.isTemplateDoc = makeTemplate(queryTemplate);
             doc["template-button-query"] = CurrentUserUtils.ficon({
-                onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+                onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'),
                 dragFactory: new PrefetchProxy(queryTemplate) as any as Doc,
                 removeDropProperties: new List<string>(["dropAction"]), title: "query view", icon: "question-circle"
             });
@@ -65,7 +65,7 @@ export class CurrentUserUtils {
             );
             slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
             doc["template-button-slides"] = CurrentUserUtils.ficon({
-                onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+                onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'),
                 dragFactory: new PrefetchProxy(slideTemplate) as any as Doc,
                 removeDropProperties: new List<string>(["dropAction"]), title: "presentation slide", icon: "address-card"
             });
@@ -74,12 +74,12 @@ export class CurrentUserUtils {
         if (doc["template-button-description"] === undefined) {
             const descriptionTemplate = Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header"); // text needs to be a space to allow templateText to be created
             Doc.GetProto(descriptionTemplate).layout =
-                "<div><FormattedTextBox {...props} height='{this._headerHeight||75}`px`' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" +
-                "<FormattedTextBox {...props} height='calc(100% - {self._headerHeight||75}`px`)' fieldKey={'text'}/></div>";
+                "<div><FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" +
+                "<FormattedTextBox {...props} height='calc(100% - {this._headerHeight||75}px)' fieldKey={'text'}/></div>";
             descriptionTemplate.isTemplateDoc = makeTemplate(descriptionTemplate, true, "descriptionView");
 
             doc["template-button-description"] = CurrentUserUtils.ficon({
-                onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+                onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory)'),
                 dragFactory: new PrefetchProxy(descriptionTemplate) as any as Doc,
                 removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "window-maximize"
             });
@@ -129,7 +129,7 @@ export class CurrentUserUtils {
             long.title = "Long Description";
 
             doc["template-button-detail"] = CurrentUserUtils.ficon({
-                onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+                onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'),
                 dragFactory: new PrefetchProxy(detailView) as any as Doc,
                 removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "window-maximize"
             });
@@ -138,7 +138,7 @@ export class CurrentUserUtils {
         if (doc["template-buttons"] === undefined) {
             doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument([doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc,
             doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc], {
-                title: "Compound Item Creators", _xMargin: 0, _showTitle: "title",
+                title: "Document Prototypes", _xMargin: 0, _showTitle: "title",
                 _autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled",
                 dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
             }));
-- 
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/CollectionTreeView.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

