From 988822bdeeed82b0c1ef611f2f7a3111d029d564 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 12 Oct 2019 19:37:06 -0400
Subject: a bunch of fixes to clean up drag box stuff.

---
 src/client/views/nodes/FontIconBox.tsx | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
 create mode 100644 src/client/views/nodes/FontIconBox.tsx

(limited to 'src/client/views/nodes/FontIconBox.tsx')

diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
new file mode 100644
index 000000000..3b580d851
--- /dev/null
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -0,0 +1,21 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { createSchema, makeInterface } from '../../../new_fields/Schema';
+import { DocComponent } from '../DocComponent';
+import './FontIconBox.scss';
+import { FieldView, FieldViewProps } from './FieldView';
+const FontIconSchema = createSchema({
+    icon: "string"
+});
+
+type FontIconDocument = makeInterface<[typeof FontIconSchema]>;
+const FontIconDocument = makeInterface(FontIconSchema);
+@observer
+export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(FontIconDocument) {
+    public static LayoutString() { return FieldView.LayoutString(FontIconBox); }
+
+    render() {
+        return <div className="fontIconBox-outerDiv" >  <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} size="lg" color="white" />  </div>;
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From a01d6183eeb6b895c50b3d09ea2113068bc11669 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 13 Oct 2019 19:17:45 -0400
Subject: minor cleanups

---
 src/client/util/TooltipTextMenu.tsx                |   2 +-
 src/client/util/UndoManager.ts                     |   4 +-
 src/client/views/CollectionLinearView.scss         | 118 ++++++++++-----------
 src/client/views/CollectionLinearView.tsx          | 103 ++++++++++--------
 src/client/views/DocumentDecorations.tsx           |   8 +-
 src/client/views/MainView.tsx                      |   3 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/FontIconBox.tsx             |   2 +-
 src/new_fields/Doc.ts                              |   5 +-
 .../authentication/models/current_user_utils.ts    |  21 +++-
 10 files changed, 150 insertions(+), 118 deletions(-)

(limited to 'src/client/views/nodes/FontIconBox.tsx')

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index c82d3bc63..31d98887f 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -850,7 +850,7 @@ export class TooltipTextMenu {
         }
         this.view = view;
         let state = view.state;
-        DocumentDecorations.Instance.TextBar && DocumentDecorations.Instance.setTextBar(DocumentDecorations.Instance.TextBar);
+        DocumentDecorations.Instance.showTextBar();
         props && (this.editorProps = props);
         // Don't do anything if the document/selection didn't change
         if (lastState && lastState.doc.eq(state.doc) &&
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 7abb9d1ee..472afac1d 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -73,8 +73,8 @@ export namespace UndoManager {
     }
     type UndoBatch = UndoEvent[];
 
-    let undoStack: UndoBatch[] = observable([]);
-    let redoStack: UndoBatch[] = observable([]);
+    export let undoStack: UndoBatch[] = observable([]);
+    export let redoStack: UndoBatch[] = observable([]);
     let currentBatch: UndoBatch | undefined;
     let batchCounter = 0;
     let undoing = false;
diff --git a/src/client/views/CollectionLinearView.scss b/src/client/views/CollectionLinearView.scss
index 46a226eef..1e6bb5922 100644
--- a/src/client/views/CollectionLinearView.scss
+++ b/src/client/views/CollectionLinearView.scss
@@ -5,73 +5,69 @@
     overflow: hidden;
     height:100%;
     padding:5px;
-}
-.collectionLinearView {
-    display:flex;
-    >label {
-        background: $dark-color;
-        color: $light-color;
-        display: inline-block;
-        border-radius: 18px;
-        font-size: 25px;
-        width: 36px;
-        height: 36px;
-        margin-right: 10px;
-        cursor: pointer;
-        transition: transform 0.2s;
-    }
+    .collectionLinearView {
+        display:flex;
+        >label {
+            background: $dark-color;
+            color: $light-color;
+            display: inline-block;
+            border-radius: 18px;
+            font-size: 25px;
+            width: 36px;
+            height: 36px;
+            margin-right: 10px;
+            cursor: pointer;
+            transition: transform 0.2s;
+        }
 
-    label p {
-        padding-left: 10.5px;
-        width: 500px;
-        height: 500px;
-    }
+        label p {
+            padding-left: 10.5px;
+            width: 500px;
+            height: 500px;
+        }
 
-    label:hover {
-        background: $main-accent;
-        transform: scale(1.15);
-    }
+        label:hover {
+            background: $main-accent;
+            transform: scale(1.15);
+        }
 
-    >input {
-        display: none;
-    }
-    >input:not(:checked)~.collectionLinearView-content {
-        display: none;
-    }
+        >input {
+            display: none;
+        }
+        >input:not(:checked)~.collectionLinearView-content {
+            display: none;
+        }
 
-    >input:checked~label {
-        transform: rotate(45deg);
-        transition: transform 0.5s;
-        cursor: pointer;
-    }
+        >input:checked~label {
+            transform: rotate(45deg);
+            transition: transform 0.5s;
+            cursor: pointer;
+        }
 
-    .collectionLinearView-content {
-        display: flex;
-        opacity: 1;
-        padding: 0;
-        position: relative;
-        .collectionFreeFormDocumentView-container {
+        .collectionLinearView-content {
+            display: flex;
+            opacity: 1;
+            padding: 0;
             position: relative;
-        } 
-        .collectionLinearView-docBtn {
-            position:relative;
-            margin-right: 10px;
-        }
-        .collectionLinearView-round-button {
-            width: 36px;
-            height: 36px;
-            border-radius: 18px;
-            font-size: 15px;
-        }
-        
-        .collectionLinearView-round-button:hover {
-            transform: scale(1.15);
+
+            .collectionLinearView-docBtn {
+                position:relative;
+                margin-right: 10px;
+            }
+            .collectionLinearView-docBtn:hover {
+                transform: scale(1.15);
+            }
+
+            .collectionLinearView-round-button {
+                width: 36px;
+                height: 36px;
+                border-radius: 18px;
+                font-size: 15px;
+            }
+            
+            .collectionLinearView-round-button:hover {
+                transform: scale(1.15);
+            }
         }
-        
-    }
-    
-    .collectionLinearView-add-button {
-        position: relative;
-        margin-right: 10px;
     }
 }
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 692f940f8..49c67a448 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -1,8 +1,8 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, computed } from 'mobx';
+import { action, observable, computed, IReactionDisposer, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, Opt } from '../../new_fields/Doc';
+import { Doc, DocListCast, Opt, HeightSym } from '../../new_fields/Doc';
 import { InkTool } from '../../new_fields/InkField';
 import { ObjectField } from '../../new_fields/ObjectField';
 import { ScriptField } from '../../new_fields/ScriptField';
@@ -25,12 +25,26 @@ const LinearDocument = makeInterface(documentSchema);
 @observer
 export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     @observable public addMenuToggle = React.createRef<HTMLInputElement>();
+    @observable private _checked = false;
     private _dropDisposer?: DragManager.DragDropDisposer;
+    private _heightDisposer?: IReactionDisposer;
 
     componentWillUnmount() {
         this._dropDisposer && this._dropDisposer();
+        this._heightDisposer && this._heightDisposer();
     }
 
+    componentDidMount() {
+        // is there any reason this needs to exist? -syip.  yes, it handles autoHeight for stacking views (masonry isn't yet supported).
+        this._heightDisposer = reaction(() => NumCast(this.props.Document.height, 0) + this.childDocs.length + (this._checked ? 1 : 0),
+            () => {
+                if (true || this.props.Document.fitWidth) {
+                    this.props.Document.width = 36 + (this._checked ? this.childDocs.length * (this.props.Document[HeightSym]() + 10) : 10);
+                }
+            },
+            { fireImmediately: true }
+        );
+    }
     protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
         this._dropDisposer && this._dropDisposer();
         if (ele) {
@@ -66,50 +80,49 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     dimension = () => NumCast(this.props.Document.height) - 5;
     render() {
         let guid = Utils.GenerateGuid();
-        return <div className="collectionLinearView-outer"><div className="collectionLinearView" ref={this.createDropTarget} >
-            <input id={`${guid}`} type="checkbox" ref={this.addMenuToggle} />
-            <label htmlFor={`${guid}`} style={{ marginTop: (this.dimension() - 36) / 2, marginBottom: "auto" }} title="Close Menu"><p>+</p></label>
-
-            <div className="collectionLinearView-content">
-                {this.props.showHiddenControls ? <button key="undo" className="collectionLinearView-add-button collectionLinearView-round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button> : (null)}
-                {this.props.showHiddenControls ? <button key="redo" className="collectionLinearView-add-button collectionLinearView-round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button> : (null)}
+        return <div className="collectionLinearView-outer">
+            <div className="collectionLinearView" ref={this.createDropTarget} >
+                <input id={`${guid}`} type="checkbox" ref={this.addMenuToggle} onChange={action((e: any) => this._checked = this.addMenuToggle.current!.checked)} />
+                <label htmlFor={`${guid}`} style={{ marginTop: (this.dimension() - 36) / 2, marginBottom: "auto" }} title="Close Menu"><p>+</p></label>
 
-                {this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair =>
-                    <div className="collectionLinearView-docBtn" style={{ width: this.dimension(), height: this.dimension() }} key={StrCast(pair.layout.title)} >
-                        <DocumentView
-                            Document={pair.layout}
-                            DataDoc={pair.data}
-                            addDocument={this.props.addDocument}
-                            addDocTab={returnFalse}
-                            pinToPres={emptyFunction}
-                            removeDocument={this.props.removeDocument}
-                            ruleProvider={undefined}
-                            onClick={undefined}
-                            ScreenToLocalTransform={Transform.Identity}
-                            ContentScaling={() => this.dimension() / (10 + NumCast(pair.layout.nativeWidth, this.dimension()))} // ugh - need to get rid of this inline function to avoid recomputing
-                            PanelWidth={this.dimension}
-                            PanelHeight={this.dimension}
-                            renderDepth={this.props.renderDepth + 1}
-                            focus={emptyFunction}
-                            backgroundColor={returnEmptyString}
-                            parentActive={returnTrue}
-                            whenActiveChanged={emptyFunction}
-                            bringToFront={emptyFunction}
-                            ContainingCollectionView={undefined}
-                            ContainingCollectionDoc={undefined}
-                            zoomToScale={emptyFunction}
-                            getScale={returnOne}>
-                        </DocumentView>
-                    </div>)}
-                {/* <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li> */}
-                {this.props.showHiddenControls ? <>
-                    <button className="collectionLinearView-toolbar-button collectionLinearView-round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /> </button>
-                    <button key="pen" onClick={() => InkingControl.Instance.switchTool(InkTool.Pen)} title="Pen" style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" /></button>
-                    <button key="marker" onClick={() => InkingControl.Instance.switchTool(InkTool.Highlighter)} title="Highlighter" style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" /></button>
-                    <button key="eraser" onClick={() => InkingControl.Instance.switchTool(InkTool.Eraser)} title="Eraser" style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" /></button>
-                    <InkingControl />
-                </> : (null)}
+                <div className="collectionLinearView-content">
+                    {this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair =>
+                        <div className="collectionLinearView-docBtn" style={{ width: this.dimension(), height: this.dimension() }} key={StrCast(pair.layout.title)} >
+                            <DocumentView
+                                Document={pair.layout}
+                                DataDoc={pair.data}
+                                addDocument={this.props.addDocument}
+                                addDocTab={this.props.addDocTab}
+                                pinToPres={emptyFunction}
+                                removeDocument={this.props.removeDocument}
+                                ruleProvider={undefined}
+                                onClick={undefined}
+                                ScreenToLocalTransform={Transform.Identity}
+                                ContentScaling={() => this.dimension() / (10 + NumCast(pair.layout.nativeWidth, this.dimension()))} // ugh - need to get rid of this inline function to avoid recomputing
+                                PanelWidth={this.dimension}
+                                PanelHeight={this.dimension}
+                                renderDepth={this.props.renderDepth + 1}
+                                focus={emptyFunction}
+                                backgroundColor={returnEmptyString}
+                                parentActive={returnTrue}
+                                whenActiveChanged={emptyFunction}
+                                bringToFront={emptyFunction}
+                                ContainingCollectionView={undefined}
+                                ContainingCollectionDoc={undefined}
+                                zoomToScale={emptyFunction}
+                                getScale={returnOne}>
+                            </DocumentView>
+                        </div>)}
+                    {/* <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li> */}
+                    {this.props.showHiddenControls ? <>
+                        <button className="collectionLinearView-toolbar-button collectionLinearView-round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /> </button>
+                        <button key="pen" onClick={() => InkingControl.Instance.switchTool(InkTool.Pen)} title="Pen" style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" /></button>
+                        <button key="marker" onClick={() => InkingControl.Instance.switchTool(InkTool.Highlighter)} title="Highlighter" style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" /></button>
+                        <button key="eraser" onClick={() => InkingControl.Instance.switchTool(InkTool.Eraser)} title="Eraser" style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" /></button>
+                        <InkingControl />
+                    </> : (null)}
+                </div>
             </div>
-        </div></div>;
+        </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 1d9f0c74b..3d73f048d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -547,10 +547,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
 
     TextBar: HTMLDivElement | undefined;
-    setTextBar = (ele: HTMLDivElement) => {
+    private setTextBar = (ele: HTMLDivElement) => {
         if (ele) {
             this.TextBar = ele;
-            TooltipTextMenu.Toolbar && Array.from(ele.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && ele.appendChild(TooltipTextMenu.Toolbar);
+        }
+    }
+    public showTextBar = () => {
+        if (this.TextBar) {
+            TooltipTextMenu.Toolbar && Array.from(this.TextBar.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && this.TextBar.appendChild(TooltipTextMenu.Toolbar);
         }
     }
     render() {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 5756c1510..39585113b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -334,6 +334,7 @@ export class MainView extends React.Component {
             return CollectionDockingView.AddRightSplit(doc, undefined);
         }
     }
+    mainContainerXf = () => new Transform(0, -this._buttonBarHeight, 1);
     @computed
     get flyout() {
         let sidebarContent = this.userDoc && this.userDoc.sidebarContainer;
@@ -379,7 +380,7 @@ export class MainView extends React.Component {
                     removeDocument={returnFalse}
                     ruleProvider={undefined}
                     onClick={undefined}
-                    ScreenToLocalTransform={Transform.Identity}
+                    ScreenToLocalTransform={this.mainContainerXf}
                     ContentScaling={returnOne}
                     PanelWidth={this.flyoutWidthFunc}
                     PanelHeight={this.getContentsHeight}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7334de92c..188c18ba7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -185,7 +185,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             e.stopPropagation();
             let preventDefault = true;
-            if (this._doubleTap && this.props.renderDepth) {
+            if (this._doubleTap && this.props.renderDepth && (!this.onClickHandler || !this.onClickHandler.script)) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 let fullScreenAlias = Doc.MakeAlias(this.props.Document);
                 let layoutNative = await PromiseValue(Cast(this.props.Document.layoutNative, Doc));
                 if (layoutNative && fullScreenAlias.layout === layoutNative.layout) {
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 3b580d851..3f5afb6d1 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -16,6 +16,6 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
     public static LayoutString() { return FieldView.LayoutString(FontIconBox); }
 
     render() {
-        return <div className="fontIconBox-outerDiv" >  <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} size="lg" color="white" />  </div>;
+        return <button className="fontIconBox-outerDiv" >  <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} size="sm" color="white" />  </button>;
     }
 }
\ No newline at end of file
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 66036f673..4bed113e3 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -14,6 +14,7 @@ import { ComputedField } from "./ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast, ToConstructor } from "./Types";
 import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
 import { intersectRect } from "../Utils";
+import { UndoManager } from "../client/util/UndoManager";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -733,4 +734,6 @@ Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
 Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return Doc.MakeCopy(doc, copyProto); });
 Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); });
 Scripting.addGlobal(function aliasDocs(field: any) { return new List<Doc>(field.map((d: any) => Doc.MakeAlias(d))); });
-Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
\ No newline at end of file
+Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
+Scripting.addGlobal(function undo() { return UndoManager.Undo(); });
+Scripting.addGlobal(function redo() { return UndoManager.Redo(); });
\ No newline at end of file
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 3858907ba..73cac879e 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -1,4 +1,4 @@
-import { action, computed, observable, runInAction } from "mobx";
+import { action, computed, observable, runInAction, reaction } from "mobx";
 import * as rp from 'request-promise';
 import { DocServer } from "../../../client/DocServer";
 import { Docs } from "../../../client/documents/Documents";
@@ -13,6 +13,8 @@ import { Cast, StrCast, PromiseValue } from "../../../new_fields/Types";
 import { Utils } from "../../../Utils";
 import { RouteStore } from "../../RouteStore";
 import { ScriptField } from "../../../new_fields/ScriptField";
+import { ButtonBox } from "../../../client/views/nodes/ButtonBox";
+import { UndoManager } from "../../../client/util/UndoManager";
 
 export class CurrentUserUtils {
     private static curr_id: string;
@@ -32,7 +34,6 @@ export class CurrentUserUtils {
         doc.viewType = CollectionViewType.Tree;
         doc.layout = CollectionView.LayoutString();
         doc.title = Doc.CurrentUserEmail;
-        this.updateUserDocument(doc);
         doc.data = new List<Doc>();
         doc.gridGap = 5;
         doc.xMargin = 5;
@@ -41,11 +42,21 @@ export class CurrentUserUtils {
         doc.boxShadow = "0 0";
         doc.convertToButtons = true; // for CollectionLinearView used as the docButton layout 
         doc.optionalRightCollection = Docs.Create.StackingDocument([], { title: "New mobile uploads" });
-        return doc;
+        return this.updateUserDocument(doc);// this should be the last 
     }
 
     static updateUserDocument(doc: Doc) {
 
+        if (doc.undoBtn === undefined) {
+            doc.undoBtn = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Collection", icon: "undo-alt" });
+            (doc.undoBtn as Doc).onClick = ScriptField.MakeScript('undo()');
+            Doc.AddDocToList(doc, "docButtons", doc.undoBtn as Doc);
+        }
+        if (doc.redoBtn === undefined) {
+            doc.redoBtn = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Collection", icon: "redo-alt" });
+            (doc.redoBtn as Doc).onClick = ScriptField.MakeScript('redo()');
+            Doc.AddDocToList(doc, "docButtons", doc.redoBtn as Doc);
+        }
         // setup workspaces library item
         if (doc.workspaces === undefined) {
             const workspaces = Docs.Create.TreeDocument([], { title: "WORKSPACES", height: 100 });
@@ -181,6 +192,10 @@ export class CurrentUserUtils {
         doc.preventTreeViewOpen = true;
         doc.forceActive = true;
         doc.lockedPosition = true;
+        doc.undoBtn && reaction(() => UndoManager.undoStack.slice(), () => (doc.undoBtn as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
+        doc.redoBtn && reaction(() => UndoManager.redoStack.slice(), () => (doc.redoBtn as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
+
+        return doc;
     }
 
     public static loadCurrentUser() {
-- 
cgit v1.2.3-70-g09d2


From 03f86e3b7b450699073c47aa43af23d31e0765d4 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 15 Oct 2019 13:42:13 -0400
Subject: extensions for linearViews or nesting.

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/views/CollectionLinearView.scss         | 24 +++---
 src/client/views/CollectionLinearView.tsx          | 88 +++++++++++-----------
 src/client/views/DocComponent.tsx                  |  2 +-
 src/client/views/InkingControl.tsx                 | 58 +++++---------
 src/client/views/MainView.tsx                      | 80 ++++++++++++--------
 src/client/views/collections/CollectionSubView.tsx |  1 -
 src/client/views/nodes/DocumentView.tsx            | 15 ++--
 src/client/views/nodes/FontIconBox.tsx             |  5 +-
 .../authentication/models/current_user_utils.ts    | 26 ++++---
 10 files changed, 154 insertions(+), 147 deletions(-)

(limited to 'src/client/views/nodes/FontIconBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 5794a6bee..6df172f46 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -471,7 +471,7 @@ export namespace Docs {
         }
 
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Linear }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Linear }, id);
         }
 
         export function SchemaDocument(schemaColumns: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) {
diff --git a/src/client/views/CollectionLinearView.scss b/src/client/views/CollectionLinearView.scss
index 4bfd88b69..4423a7020 100644
--- a/src/client/views/CollectionLinearView.scss
+++ b/src/client/views/CollectionLinearView.scss
@@ -4,26 +4,25 @@
 .collectionLinearView-outer{
     overflow: hidden;
     height:100%;
-    padding:5px;
     .collectionLinearView {
         display:flex;
+        height: 100%;
         >label {
             background: $dark-color;
             color: $light-color;
             display: inline-block;
             border-radius: 18px;
-            font-size: 25px;
-            width: 36px;
-            height: 36px;
-            margin-right: 10px;
+            font-size: 12.5px;
+            width: 18px;
+            height: 18px;
+            margin-top:auto;
+            margin-bottom:auto;
             cursor: pointer;
             transition: transform 0.2s;
         }
 
         label p {
-            padding-left: 10.5px;
-            width: 500px;
-            height: 500px;
+            padding-left:5px;
         }
 
         label:hover {
@@ -47,20 +46,21 @@
         .collectionLinearView-content {
             display: flex;
             opacity: 1;
-            padding: 0;
             position: relative;
+            margin-top: auto;
 
             .collectionLinearView-docBtn, .collectionLinearView-docBtn-scalable {
                 position:relative;
-                margin-right: 10px;
+                margin-top: auto;
+                margin-bottom: auto;
             }
             .collectionLinearView-docBtn-scalable:hover {
                 transform: scale(1.15);
             }
 
             .collectionLinearView-round-button {
-                width: 36px;
-                height: 36px;
+                width: 18px;
+                height: 18px;
                 border-radius: 18px;
                 font-size: 15px;
             }
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 5c793f784..9d1dd7749 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -1,23 +1,20 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, computed, IReactionDisposer, reaction } from 'mobx';
+import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, Opt, HeightSym } from '../../new_fields/Doc';
-import { InkTool } from '../../new_fields/InkField';
+import { Doc, HeightSym, WidthSym } from '../../new_fields/Doc';
 import { ObjectField } from '../../new_fields/ObjectField';
+import { makeInterface } from '../../new_fields/Schema';
 import { ScriptField } from '../../new_fields/ScriptField';
-import { NumCast, StrCast } from '../../new_fields/Types';
-import { emptyFunction, returnEmptyString, returnOne, returnTrue, returnFalse, Utils } from '../../Utils';
+import { BoolCast, NumCast, StrCast } from '../../new_fields/Types';
+import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from '../../Utils';
 import { Docs } from '../documents/Documents';
 import { DragManager } from '../util/DragManager';
 import { Transform } from '../util/Transform';
-import { UndoManager } from '../util/UndoManager';
-import { InkingControl } from './InkingControl';
-import { DocumentView, documentSchema } from './nodes/DocumentView';
 import "./CollectionLinearView.scss";
-import { makeInterface } from '../../new_fields/Schema';
+import { CollectionViewType } from './collections/CollectionBaseView';
 import { CollectionSubView } from './collections/CollectionSubView';
-import { DocumentType } from '../documents/DocumentTypes';
+import { documentSchema, DocumentView } from './nodes/DocumentView';
+import { translate } from 'googleapis/build/src/apis/translate';
 
 
 type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -26,7 +23,6 @@ const LinearDocument = makeInterface(documentSchema);
 @observer
 export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     @observable public addMenuToggle = React.createRef<HTMLInputElement>();
-    @observable private _checked = false;
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _heightDisposer?: IReactionDisposer;
 
@@ -37,12 +33,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
 
     componentDidMount() {
         // is there any reason this needs to exist? -syip.  yes, it handles autoHeight for stacking views (masonry isn't yet supported).
-        this._heightDisposer = reaction(() => NumCast(this.props.Document.height, 0) + this.childDocs.length + (this._checked ? 1 : 0),
-            () => {
-                if (true || this.props.Document.fitWidth) {
-                    this.props.Document.width = 36 + (this._checked ? this.childDocs.length * (this.props.Document[HeightSym]() + 10) : 10);
-                }
-            },
+        this._heightDisposer = reaction(() => NumCast(this.props.Document.height, 0) + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
+            () => this.props.Document.width = 18 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
             { fireImmediately: true }
         );
     }
@@ -56,11 +48,13 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     drop = action((e: Event, de: DragManager.DropEvent) => {
         (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => {
             let dbox = doc;
-            if (!doc.onDragStart && this.props.Document.convertToButtons) {
+            if (!doc.onDragStart && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) {
                 dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: "bolt" });
                 dbox.dragFactory = doc;
                 dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
                 dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
+            } else if (doc.viewType === CollectionViewType.Linear) {
+                dbox.ignoreClick = true;
             }
             (de.data as DragManager.DocumentDragData).droppedDocuments[i] = dbox;
         });
@@ -68,40 +62,51 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         return super.drop(e, de);
     });
 
-    selected = (tool: InkTool) => {
-        if (!InkingControl.Instance || InkingControl.Instance.selectedTool === InkTool.None) return { display: "none" };
-        if (InkingControl.Instance.selectedTool === tool) {
-            return { color: "#61aaa3", fontSize: "50%" };
-        }
-        return { fontSize: "50%" };
-    }
-
     public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
 
-    dimension = () => NumCast(this.props.Document.height) - 5;
+    dimension = () => NumCast(this.props.Document.height); // 2 * the padding
+    getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
+        if (!ele.current) return Transform.Identity();
+        let { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
+        return new Transform(-translateX, -translateY, 1 / scale);
+    };
+    _spacing = 20;
     render() {
         let guid = Utils.GenerateGuid();
         return <div className="collectionLinearView-outer">
             <div className="collectionLinearView" ref={this.createDropTarget} >
-                <input id={`${guid}`} type="checkbox" ref={this.addMenuToggle} onChange={action((e: any) => this._checked = this.addMenuToggle.current!.checked)} />
-                <label htmlFor={`${guid}`} style={{ marginTop: (this.dimension() - 36) / 2, marginBottom: "auto" }} title="Close Menu"><p>+</p></label>
+                <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.isExpanded)} ref={this.addMenuToggle}
+                    onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} />
+                <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
 
                 <div className="collectionLinearView-content">
-                    {this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair =>
-                        <div className={`collectionLinearView-docBtn` + (pair.layout.onClick ? "-scalable" : "")} style={{ width: this.dimension(), height: this.dimension() }} key={StrCast(pair.layout.title)} >
+                    {this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => {
+                        let nested = pair.layout.viewType === CollectionViewType.Linear;
+                        let dref = React.createRef<HTMLDivElement>();
+                        let nativeWidth = NumCast(pair.layout.nativeWidth, this.dimension());
+                        let scalingContent = nested ? 1 : this.dimension() / (this._spacing + nativeWidth);
+                        let scalingBox = nested ? 1 : this.dimension() / nativeWidth;
+                        let deltaSize = nativeWidth * scalingBox - nativeWidth * scalingContent;
+                        return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={StrCast(pair.layout.title)} ref={dref}
+                            style={{
+                                width: nested ? pair.layout[WidthSym]() : this.dimension(),
+                                height: nested ? pair.layout[HeightSym]() : this.dimension(),
+                                transform: nested ? undefined : `translate(${deltaSize / 2}px, ${deltaSize / 2}px)`
+                            }}  >
                             <DocumentView
                                 Document={pair.layout}
                                 DataDoc={pair.data}
                                 addDocument={this.props.addDocument}
+                                moveDocument={this.props.moveDocument}
                                 addDocTab={this.props.addDocTab}
                                 pinToPres={emptyFunction}
                                 removeDocument={this.props.removeDocument}
                                 ruleProvider={undefined}
                                 onClick={undefined}
-                                ScreenToLocalTransform={Transform.Identity}
-                                ContentScaling={() => this.dimension() / (10 + NumCast(pair.layout.nativeWidth, this.dimension()))} // ugh - need to get rid of this inline function to avoid recomputing
-                                PanelWidth={this.dimension}
-                                PanelHeight={this.dimension}
+                                ScreenToLocalTransform={this.getTransform(dref)}
+                                ContentScaling={() => scalingContent} // ugh - need to get rid of this inline function to avoid recomputing
+                                PanelWidth={() => nested ? pair.layout[WidthSym]() : this.dimension()}
+                                PanelHeight={() => nested ? pair.layout[HeightSym]() : this.dimension()}
                                 renderDepth={this.props.renderDepth + 1}
                                 focus={emptyFunction}
                                 backgroundColor={returnEmptyString}
@@ -113,15 +118,10 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 zoomToScale={emptyFunction}
                                 getScale={returnOne}>
                             </DocumentView>
-                        </div>)}
+                        </div>
+                    })}
                     {/* <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li> */}
-                    {this.props.showHiddenControls ? <>
-                        <button className="collectionLinearView-toolbar-button collectionLinearView-round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /> </button>
-                        <button key="pen" onClick={() => InkingControl.Instance.switchTool(InkTool.Pen)} title="Pen" style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" /></button>
-                        <button key="marker" onClick={() => InkingControl.Instance.switchTool(InkTool.Highlighter)} title="Highlighter" style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" /></button>
-                        <button key="eraser" onClick={() => InkingControl.Instance.switchTool(InkTool.Eraser)} title="Eraser" style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" /></button>
-                        <InkingControl />
-                    </> : (null)}
+
                 </div>
             </div>
         </div>;
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index b05966bb5..fbc27192c 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -36,7 +36,7 @@ export function DocStaticComponent<P extends DocStaticProps, T>(schemaCtor: (doc
         get Document(): T {
             return schemaCtor(this.props.Document);
         }
-        active = () => (this.props.Document.forceActive || this.props.isSelected() || this.props.renderDepth === 0) && !InkingControl.Instance.selectedTool;
+        active = () => (this.props.Document.forceActive || this.props.isSelected() || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
     }
     return Component;
 }
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index d42d8d2d9..41dec9f83 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,38 +1,30 @@
-import { observable, action, computed, runInAction } from "mobx";
+import { action, computed, observable } from "mobx";
 import { ColorResult } from 'react-color';
-import React = require("react");
-import { observer } from "mobx-react";
-import "./InkingControl.scss";
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
-import { SelectionManager } from "../util/SelectionManager";
-import { InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
-import { undoBatch, UndoManager } from "../util/UndoManager";
-import { StrCast, NumCast, Cast } from "../../new_fields/Types";
-import { listSpec } from "../../new_fields/Schema";
+import { InkTool } from "../../new_fields/InkField";
 import { List } from "../../new_fields/List";
+import { listSpec } from "../../new_fields/Schema";
+import { Cast, NumCast, StrCast } from "../../new_fields/Types";
 import { Utils } from "../../Utils";
+import { Scripting } from "../util/Scripting";
+import { SelectionManager } from "../util/SelectionManager";
+import { undoBatch, UndoManager } from "../util/UndoManager";
 
-library.add(faPen, faHighlighter, faEraser, faBan);
 
-@observer
-export class InkingControl extends React.Component {
+export class InkingControl {
     @observable static Instance: InkingControl;
     @observable private _selectedTool: InkTool = InkTool.None;
     @observable private _selectedColor: string = "rgb(244, 67, 54)";
     @observable private _selectedWidth: string = "5";
     @observable public _open: boolean = false;
 
-    constructor(props: Readonly<{}>) {
-        super(props);
-        runInAction(() => InkingControl.Instance = this);
+    constructor() {
+        InkingControl.Instance = this;
     }
 
-    @action
-    switchTool = (tool: InkTool): void => {
+    switchTool = action((tool: InkTool): void => {
         this._selectedTool = tool;
-    }
+    })
     decimalToHexString(number: number) {
         if (number < 0) {
             number = 0xFFFFFFFF + number + 1;
@@ -124,22 +116,10 @@ export class InkingControl extends React.Component {
         return this._selectedWidth;
     }
 
-    @action
-    toggleDisplay = () => {
-        this._open = !this._open;
-        this.switchTool(this._open ? InkTool.Pen : InkTool.None);
-    }
-    render() {
-        return (
-            <ul className="inking-control" style={this._open ? { display: "flex" } : { display: "none" }}>
-                <li className="ink-size ink-panel">
-                    <label htmlFor="stroke-width">SIZE: </label>
-                    <input type="text" min="1" max="100" value={this._selectedWidth} name="stroke-width"
-                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} />
-                    <input type="range" min="1" max="100" value={this._selectedWidth} name="stroke-width"
-                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} />
-                </li>
-            </ul >
-        );
-    }
-}
\ No newline at end of file
+}
+Scripting.addGlobal(function activatePen() { return InkingControl.Instance.switchTool(InkTool.Pen); });
+Scripting.addGlobal(function activateBrush() { return InkingControl.Instance.switchTool(InkTool.Highlighter); });
+Scripting.addGlobal(function activateEraser() { return InkingControl.Instance.switchTool(InkTool.Eraser); });
+Scripting.addGlobal(function deactivateInk() { return InkingControl.Instance.switchTool(InkTool.None); });
+Scripting.addGlobal(function setInkWidth(width: any) { return InkingControl.Instance.switchWidth(width); });
+Scripting.addGlobal(function setInkColor(color: any) { return InkingControl.Instance.updateSelectedColor(color); });
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 701f094e2..4e06763a4 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,8 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
+import {
+    faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
+    faMusic, faObjectGroup, faPause, faPenNib, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt
+} from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -99,6 +102,8 @@ export class MainView extends React.Component {
         library.add(faGlobeAsia);
         library.add(faUndoAlt);
         library.add(faRedoAlt);
+        library.add(faPen);
+        library.add(faEraser);
         library.add(faPenNib);
         library.add(faFilm);
         library.add(faMusic);
@@ -450,37 +455,50 @@ export class MainView extends React.Component {
         Doc.RemoveDocFromList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc);
         return true;
     }
+    @action
+    moveButtonDoc = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => {
+        return this.remButtonDoc(doc) && addDocument(doc);
+    }
+    buttonBarXf = () => {
+        if (!this._docBtnRef.current) return Transform.Identity();
+        let { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
+        return new Transform(-translateX, -translateY, 1 / scale);
+    }
+    _docBtnRef = React.createRef<HTMLDivElement>();
     @computed get docButtons() {
-        return <div className="mainView-docButtons" style={{ left: (this._flyoutTranslate ? this.flyoutWidth : 0) + 20 }} >
-            <MainViewNotifs />
-            <CollectionLinearView 
-                Document={CurrentUserUtils.UserDocument.expandingButtons as Doc} 
-                DataDoc={undefined}
-                fieldKey={"data"}
-                fieldExt={""}
-                showHiddenControls={true}
-                select={emptyFunction}
-                chromeCollapsed={true}
-                active={returnFalse}
-                isSelected={returnFalse}
-                moveDocument={returnFalse}
-                CollectionView={undefined}
-                addDocument={this.addButtonDoc}
-                addDocTab={this.addDocTabFunc}
-                pinToPres={emptyFunction}
-                removeDocument={this.remButtonDoc}
-                ruleProvider={undefined}
-                onClick={undefined}
-                ScreenToLocalTransform={Transform.Identity}
-                ContentScaling={returnOne}
-                PanelWidth={this.flyoutWidthFunc}
-                PanelHeight={this.getContentsHeight}
-                renderDepth={0}
-                focus={emptyFunction}
-                whenActiveChanged={emptyFunction}
-                ContainingCollectionView={undefined}
-                ContainingCollectionDoc={undefined} />
-        </div>;
+        console.log("stuff = " + this.flyoutWidthFunc() + " " + this.getContentsHeight());
+        if (CurrentUserUtils.UserDocument.expandingButtons instanceof Doc) {
+            return <div className="mainView-docButtons" ref={this._docBtnRef} style={{ left: (this._flyoutTranslate ? this.flyoutWidth : 0) + 20, height: "42px" }} >
+                <MainViewNotifs />
+                <CollectionLinearView
+                    Document={CurrentUserUtils.UserDocument.expandingButtons as Doc}
+                    DataDoc={undefined}
+                    fieldKey={"data"}
+                    fieldExt={""}
+                    select={emptyFunction}
+                    chromeCollapsed={true}
+                    active={returnFalse}
+                    isSelected={returnFalse}
+                    moveDocument={this.moveButtonDoc}
+                    CollectionView={undefined}
+                    addDocument={this.addButtonDoc}
+                    addDocTab={this.addDocTabFunc}
+                    pinToPres={emptyFunction}
+                    removeDocument={this.remButtonDoc}
+                    ruleProvider={undefined}
+                    onClick={undefined}
+                    ScreenToLocalTransform={this.buttonBarXf}
+                    ContentScaling={returnOne}
+                    PanelWidth={this.flyoutWidthFunc}
+                    PanelHeight={this.getContentsHeight}
+                    renderDepth={0}
+                    focus={emptyFunction}
+                    whenActiveChanged={emptyFunction}
+                    ContainingCollectionView={undefined}
+                    ContainingCollectionDoc={undefined} />
+            </div>;
+        }
+        return (null);
     }
 
     render() {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 6e8e4fa12..fdbe5339d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -33,7 +33,6 @@ export interface CollectionViewProps extends FieldViewProps {
     VisibleHeight?: () => number;
     chromeCollapsed: boolean;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
-    showHiddenControls?: boolean; // hack for showing the undo/redo/ink controls in a linear view -- needs to be redone
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 62264ea38..c0e5185c1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -39,6 +39,7 @@ import { ImageField } from '../../../new_fields/URLField';
 import SharingManager from '../../util/SharingManager';
 import { Scripting } from '../../util/Scripting';
 import { DictationOverlay } from '../DictationOverlay';
+import { CollectionViewType } from '../collections/CollectionBaseView';
 
 library.add(fa.faEdit);
 library.add(fa.faTrash);
@@ -105,7 +106,7 @@ export const documentSchema = createSchema({
     dropAction: "string",       // override specifying what should happen when this document is 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
     onClick: 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 an Doc to drag.
+    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
     ignoreAspect: "boolean",    // whether aspect ratio should be ignored when laying out or manipulating the document
     autoHeight: "boolean",      // whether the height of the document should be computed automatically based on its contents
@@ -167,7 +168,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (this._mainCont.current) {
             let dragData = new DragManager.DocumentDragData([this.props.Document]);
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
-            dragData.offset = this.Document.onDragStart ? [0, 0] : this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
+            dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.moveDocument = this.Document.onDragStart ? undefined : this.props.moveDocument;
             dragData.applyAsTemplate = applyAsTemplate;
@@ -175,7 +176,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 handlers: {
                     dragComplete: action((emptyFunction))
                 },
-                hideSource: !dropAction && !this.Document.onDragStart
+                hideSource: !dropAction && !this.Document.onDragStart && !this.Document.onClick
             });
         }
     }
@@ -249,7 +250,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this._hitTemplateDrag = true;
             }
         }
-        if ((this.active || this.Document.onDragStart) && e.button === 0 && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); // events stop at the lowest document that is active.  if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
+        if ((this.active || this.Document.onDragStart || this.Document.onClick) && e.button === 0 && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); // events stop at the lowest document that is active.  if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         document.addEventListener("pointermove", this.onPointerMove);
@@ -261,9 +262,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (e.cancelBubble && this.active) {
             document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
         }
-        else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive() || this.Document.onDragStart) && !this.Document.lockedPosition && !this.Document.inOverlay) {
+        else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive() || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
-                if (!e.altKey && (!this.topMost || this.Document.onDragStart) && e.buttons === 1) {
+                if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && e.buttons === 1) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
                     this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
@@ -684,7 +685,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: StrCast(this.Document.color),
                     outline: fullDegree && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
                     border: fullDegree && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
-                    background: backgroundColor,
+                    background: this.props.Document.type === DocumentType.FONTICON || this.props.Document.viewType === CollectionViewType.Linear ? undefined : backgroundColor,
                     width: animwidth,
                     height: animheight,
                     transform: `scale(${this.props.Document.fitWidth ? 1 : this.props.ContentScaling()})`,
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 3f5afb6d1..aa442cd92 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -5,6 +5,7 @@ import { createSchema, makeInterface } from '../../../new_fields/Schema';
 import { DocComponent } from '../DocComponent';
 import './FontIconBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
+import { StrCast } from '../../../new_fields/Types';
 const FontIconSchema = createSchema({
     icon: "string"
 });
@@ -16,6 +17,8 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
     public static LayoutString() { return FieldView.LayoutString(FontIconBox); }
 
     render() {
-        return <button className="fontIconBox-outerDiv" >  <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} size="sm" color="white" />  </button>;
+        return <button className="fontIconBox-outerDiv" style={{ background: StrCast(this.props.Document.backgroundColor) }}>
+            <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} size="sm" color="white" />
+        </button>;
     }
 }
\ No newline at end of file
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 8af6bbfd5..c4b91dadd 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -12,6 +12,7 @@ import { ScriptField } from "../../../new_fields/ScriptField";
 import { Cast, PromiseValue } from "../../../new_fields/Types";
 import { Utils } from "../../../Utils";
 import { RouteStore } from "../../RouteStore";
+import { InkingControl } from "../../../client/views/InkingControl";
 
 export class CurrentUserUtils {
     private static curr_id: string;
@@ -45,17 +46,21 @@ export class CurrentUserUtils {
 
     // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
     static setupCreatorButtons() {
-        let docProtoData = [
-            { title: "collection", icon: "folder", script: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' },
-            { title: "web page", icon: "globe-asia", script: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' },
-            { title: "image", icon: "cat", script: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' },
-            { title: "button", icon: "bolt", script: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' },
-            { title: "presentation", icon: "tv", script: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })' },
-            { title: "import folder", icon: "cloud-upload-alt", script: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' },
+        let docProtoData: { title: string, icon: string, drag?: string, click?: string }[] = [
+            { title: "collection", icon: "folder", drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' },
+            { title: "web page", icon: "globe-asia", drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' },
+            { title: "image", icon: "cat", drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' },
+            { title: "button", icon: "bolt", drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' },
+            { title: "presentation", icon: "tv", drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })' },
+            { title: "import folder", icon: "cloud-upload-alt", drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' },
+            { title: "pen", icon: "pen-nib", click: 'activatePen(); setInkWidth(2);' },
+            { title: "highlighter", icon: "pen", click: 'activateBrush(); setInkWidth(20);' },
+            { title: "eraser", icon: "eraser", click: 'activateEraser();' },
+            { title: "none", icon: "pause", click: 'deactivateInk();' },
         ];
         return docProtoData.map(data => Docs.Create.FontIconDocument({
-            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100,
-            title: data.title, icon: data.icon, onDragStart: ScriptField.MakeFunction(data.script)
+            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "alias" : undefined,
+            title: data.title, icon: data.icon, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined
         }));
     }
 
@@ -144,7 +149,7 @@ export class CurrentUserUtils {
 
         doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc], {
             title: "expanding buttons", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0",
-            backgroundColor: "#eeeeee", preventTreeViewOpen: true, forceActive: true, lockedPosition: true, convertToButtons: true
+            backgroundColor: "black", preventTreeViewOpen: true, forceActive: true, lockedPosition: true, convertToButtons: true
         });
     }
 
@@ -165,6 +170,7 @@ export class CurrentUserUtils {
     }
 
     static updateUserDocument(doc: Doc) {
+        new InkingControl();
         (doc.optionalRightCollection === undefined) && CurrentUserUtils.setupMobileUploads(doc);
         (doc.noteTypes === undefined) && CurrentUserUtils.setupNoteTypes(doc);
         (doc.overlays === undefined) && CurrentUserUtils.setupOverlays(doc);
-- 
cgit v1.2.3-70-g09d2


From 33811c112c7e479813908ba10f72813954a3e289 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 15 Oct 2019 16:25:10 -0400
Subject: working version of inking buttons

---
 src/client/documents/Documents.ts                  |  2 ++
 src/client/util/DragManager.ts                     |  4 +--
 src/client/util/SelectionManager.ts                | 11 --------
 src/client/views/CollectionLinearView.tsx          |  4 +--
 src/client/views/InkingControl.tsx                 |  6 ++--
 src/client/views/MainView.tsx                      |  6 ++--
 src/client/views/nodes/ColorBox.tsx                | 33 ++++++++++++++++++++--
 src/client/views/nodes/DocumentView.tsx            |  7 +++--
 src/client/views/nodes/FontIconBox.tsx             |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 16 ++++++-----
 src/new_fields/Doc.ts                              |  4 +++
 .../authentication/models/current_user_utils.ts    | 31 +++++++++++---------
 12 files changed, 78 insertions(+), 48 deletions(-)

(limited to 'src/client/views/nodes/FontIconBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6df172f46..f4fce34ac 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -93,6 +93,8 @@ export interface DocumentOptions {
     autoHeight?: boolean;
     removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped.  e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
     dbDoc?: Doc;
+    unchecked?: ScriptField; // returns whether a check box is unchecked
+    activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts)
     onClick?: ScriptField;
     onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
     icon?: string;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 169f2edec..92666c03c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -234,9 +234,9 @@ export namespace DragManager {
 
     export let StartDragFunctions: (() => void)[] = [];
 
-    export async function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
+    export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
         runInAction(() => StartDragFunctions.map(func => func()));
-        await dragData.draggedDocuments.map(d => d.dragFactory);
+        dragData.draggedDocuments.map(d => d.dragFactory); // does this help?  trying to make sure the dragFactory Doc is loaded
         StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag :
             (dropData: { [id: string]: any }) => {
                 (dropData.droppedDocuments =
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index df1b46b33..2d717ca57 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -45,17 +45,6 @@ export namespace SelectionManager {
     }
 
     const manager = new Manager();
-    reaction(() => manager.SelectedDocuments, sel => {
-        let targetColor = "#FFFFFF";
-        if (sel.length > 0) {
-            let firstView = sel[0];
-            let doc = firstView.props.Document;
-            let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc);
-            let stored = StrCast(targetDoc.backgroundColor);
-            stored.length > 0 && (targetColor = stored);
-        }
-        InkingControl.Instance.updateSelectedColor(targetColor);
-    }, { fireImmediately: true });
 
     export function DeselectDoc(docView: DocumentView): void {
         manager.DeselectDoc(docView);
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 9d1dd7749..eb3c768d0 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -48,7 +48,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     drop = action((e: Event, de: DragManager.DropEvent) => {
         (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => {
             let dbox = doc;
-            if (!doc.onDragStart && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) {
+            if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) {
                 dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: "bolt" });
                 dbox.dragFactory = doc;
                 dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
@@ -90,7 +90,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                         return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={StrCast(pair.layout.title)} ref={dref}
                             style={{
                                 width: nested ? pair.layout[WidthSym]() : this.dimension(),
-                                height: nested ? pair.layout[HeightSym]() : this.dimension(),
+                                height: nested && pair.layout.isExpanded ? pair.layout[HeightSym]() : this.dimension(),
                                 transform: nested ? undefined : `translate(${deltaSize / 2}px, ${deltaSize / 2}px)`
                             }}  >
                             <DocumentView
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 41dec9f83..149a4fc8a 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -117,9 +117,9 @@ export class InkingControl {
     }
 
 }
-Scripting.addGlobal(function activatePen() { return InkingControl.Instance.switchTool(InkTool.Pen); });
-Scripting.addGlobal(function activateBrush() { return InkingControl.Instance.switchTool(InkTool.Highlighter); });
-Scripting.addGlobal(function activateEraser() { return InkingControl.Instance.switchTool(InkTool.Eraser); });
+Scripting.addGlobal(function activatePen(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Pen : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
+Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
+Scripting.addGlobal(function activateEraser(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Eraser : InkTool.None); });
 Scripting.addGlobal(function deactivateInk() { return InkingControl.Instance.switchTool(InkTool.None); });
 Scripting.addGlobal(function setInkWidth(width: any) { return InkingControl.Instance.switchWidth(width); });
 Scripting.addGlobal(function setInkColor(color: any) { return InkingControl.Instance.updateSelectedColor(color); });
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4e06763a4..8035916a2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -466,12 +466,12 @@ export class MainView extends React.Component {
     }
     _docBtnRef = React.createRef<HTMLDivElement>();
     @computed get docButtons() {
-        console.log("stuff = " + this.flyoutWidthFunc() + " " + this.getContentsHeight());
         if (CurrentUserUtils.UserDocument.expandingButtons instanceof Doc) {
-            return <div className="mainView-docButtons" ref={this._docBtnRef} style={{ left: (this._flyoutTranslate ? this.flyoutWidth : 0) + 20, height: "42px" }} >
+            return <div className="mainView-docButtons" ref={this._docBtnRef}
+                style={{ left: (this._flyoutTranslate ? this.flyoutWidth : 0) + 20, height: !CurrentUserUtils.UserDocument.expandingButtons.isExpanded ? "42px" : undefined }} >
                 <MainViewNotifs />
                 <CollectionLinearView
-                    Document={CurrentUserUtils.UserDocument.expandingButtons as Doc}
+                    Document={CurrentUserUtils.UserDocument.expandingButtons}
                     DataDoc={undefined}
                     fieldKey={"data"}
                     fieldExt={""}
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index d280258ae..87c91c121 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -7,6 +7,9 @@ import { InkingControl } from "../InkingControl";
 import { DocStaticComponent } from "../DocComponent";
 import { documentSchema } from "./DocumentView";
 import { makeInterface } from "../../../new_fields/Schema";
+import { trace, reaction, observable, action, IReactionDisposer } from "mobx";
+import { SelectionManager } from "../../util/SelectionManager";
+import { StrCast } from "../../../new_fields/Types";
 
 type ColorDocument = makeInterface<[typeof documentSchema]>;
 const ColorDocument = makeInterface(documentSchema);
@@ -14,9 +17,35 @@ const ColorDocument = makeInterface(documentSchema);
 @observer
 export class ColorBox extends DocStaticComponent<FieldViewProps, ColorDocument>(ColorDocument) {
     public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
+    _selectedDisposer: IReactionDisposer | undefined;
+    componentDidMount() {
+        this._selectedDisposer = reaction(() => SelectionManager.SelectedDocuments(),
+            action(() => this._startupColor = SelectionManager.SelectedDocuments().length ? StrCast(SelectionManager.SelectedDocuments()[0].Document.backgroundColor, "black") : "black"),
+            { fireImmediately: true });
+
+        // compare to this reaction that used to be in Selection Manager
+        // reaction(() => manager.SelectedDocuments, sel => {
+        //     let targetColor = "#FFFFFF";
+        //     if (sel.length > 0) {
+        //         let firstView = sel[0];
+        //         let doc = firstView.props.Document;
+        //         let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc);
+        //         let stored = StrCast(targetDoc.backgroundColor);
+        //         stored.length > 0 && (targetColor = stored);
+        //     }
+        //     InkingControl.Instance.updateSelectedColor(targetColor);
+        // }, { fireImmediately: true });
+    }
+    componentWillUnmount() {
+        this._selectedDisposer && this._selectedDisposer();
+    }
+
+    @observable _startupColor = "black";
+
     render() {
-        return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`} onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}>
-            <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
+        return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`}
+            onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}>
+            <SketchPicker color={this._startupColor} onChange={InkingControl.Instance.switchColor} />
         </div>;
     }
 } 
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c0e5185c1..6f99c541f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -107,7 +107,7 @@ export const documentSchema = createSchema({
     removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
     onClick: 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
+    dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
     ignoreAspect: "boolean",    // whether aspect ratio should be ignored when laying out or manipulating the document
     autoHeight: "boolean",      // whether the height of the document should be computed automatically based on its contents
     isTemplate: "boolean",      // whether this document acts as a template layout for describing how other documents should be displayed
@@ -676,6 +676,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
         const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"];
+        let highlighting = fullDegree && this.props.Document.type !== DocumentType.FONTICON && this.props.Document.viewType !== CollectionViewType.Linear
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -683,8 +684,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     transition: this.props.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition),
                     pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all",
                     color: StrCast(this.Document.color),
-                    outline: fullDegree && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
-                    border: fullDegree && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
+                    outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
+                    border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
                     background: this.props.Document.type === DocumentType.FONTICON || this.props.Document.viewType === CollectionViewType.Linear ? undefined : backgroundColor,
                     width: animwidth,
                     height: animheight,
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index aa442cd92..efe47d8a8 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -18,7 +18,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
 
     render() {
         return <button className="fontIconBox-outerDiv" style={{ background: StrCast(this.props.Document.backgroundColor) }}>
-            <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} size="sm" color="white" />
+            <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} size="sm" opacity={this.props.Document.unchecked ? "0.5" : "1"} />
         </button>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 181f37d36..1bdff3ec7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -927,16 +927,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     onMouseUp = (e: React.MouseEvent): void => {
         e.stopPropagation();
 
-        // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there are nested prosemirrors.  We only want the lowest level prosemirror to be invoked.
-        if ((this._editorView as any).mouseDown) {
-            let originalUpHandler = (this._editorView as any).mouseDown.up;
-            (this._editorView as any).root.removeEventListener("mouseup", originalUpHandler);
-            (this._editorView as any).mouseDown.up = (e: MouseEvent) => {
+        let view = this._editorView as any;
+        // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there 
+        // are nested prosemirrors.  We only want the lowest level prosemirror to be invoked.
+        if (view.mouseDown) {
+            let originalUpHandler = view.mouseDown.up;
+            view.root.removeEventListener("mouseup", originalUpHandler);
+            view.mouseDown.up = (e: MouseEvent) => {
                 !(e as any).formattedHandled && originalUpHandler(e);
-                e.stopPropagation();
+                // e.stopPropagation();
                 (e as any).formattedHandled = true;
             };
-            (this._editorView as any).root.addEventListener("mouseup", (this._editorView as any).mouseDown.up);
+            view.root.addEventListener("mouseup", view.mouseDown.up);
         }
     }
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 4bed113e3..276596fb8 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -525,6 +525,7 @@ export namespace Doc {
     export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc {
         const copy = new Doc(copyProtoId, true);
         Object.keys(doc).forEach(key => {
+            let cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
             const field = ProxyField.WithoutProxy(() => doc[key]);
             if (key === "proto" && copyProto) {
                 if (doc[key] instanceof Doc) {
@@ -533,6 +534,8 @@ export namespace Doc {
             } else {
                 if (field instanceof RefField) {
                     copy[key] = field;
+                } else if (cfield instanceof ComputedField) {
+                    copy[key] = ComputedField.MakeFunction(cfield.script.originalScript);
                 } else if (field instanceof ObjectField) {
                     copy[key] = ObjectField.MakeCopy(field);
                 } else if (field instanceof Promise) {
@@ -735,5 +738,6 @@ Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return Doc.Make
 Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); });
 Scripting.addGlobal(function aliasDocs(field: any) { return new List<Doc>(field.map((d: any) => Doc.MakeAlias(d))); });
 Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
+Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2) });
 Scripting.addGlobal(function undo() { return UndoManager.Undo(); });
 Scripting.addGlobal(function redo() { return UndoManager.Redo(); });
\ No newline at end of file
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index c4b91dadd..3c4a46ed8 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -8,7 +8,7 @@ import { UndoManager } from "../../../client/util/UndoManager";
 import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
-import { ScriptField } from "../../../new_fields/ScriptField";
+import { ScriptField, ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, PromiseValue } from "../../../new_fields/Types";
 import { Utils } from "../../../Utils";
 import { RouteStore } from "../../RouteStore";
@@ -45,29 +45,32 @@ export class CurrentUserUtils {
     }
 
     // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
-    static setupCreatorButtons() {
-        let docProtoData: { title: string, icon: string, drag?: string, click?: string }[] = [
+    static setupCreatorButtons(doc: Doc) {
+        doc.activePen = doc;
+        let docProtoData: { title: string, icon: string, drag?: string, click?: string, unchecked?: string, activePen?: Doc, backgroundColor?: string }[] = [
             { title: "collection", icon: "folder", drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' },
             { title: "web page", icon: "globe-asia", drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' },
             { title: "image", icon: "cat", drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' },
             { title: "button", icon: "bolt", drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' },
             { title: "presentation", icon: "tv", drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })' },
             { title: "import folder", icon: "cloud-upload-alt", drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' },
-            { title: "pen", icon: "pen-nib", click: 'activatePen(); setInkWidth(2);' },
-            { title: "highlighter", icon: "pen", click: 'activateBrush(); setInkWidth(20);' },
-            { title: "eraser", icon: "eraser", click: 'activateEraser();' },
-            { title: "none", icon: "pause", click: 'deactivateInk();' },
+            { title: "pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", unchecked: `!sameDocs(this.activePen.pen,  this)`, activePen: doc },
+            { title: "highlighter", icon: "pen", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc },
+            { title: "eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc },
+            { title: "none", icon: "pause", click: 'deactivateInk();this.activePen.pen = this;', unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc },
         ];
         return docProtoData.map(data => Docs.Create.FontIconDocument({
-            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "alias" : undefined,
-            title: data.title, icon: data.icon, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined
+            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon,
+            onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
+            unchecked: data.unchecked ? ComputedField.MakeFunction(data.unchecked) : undefined, activePen: data.activePen,
+            backgroundColor: data.backgroundColor
         }));
     }
 
     // setup the Creator button which will display the creator panel.  This panel will include the drag creators and the color picker.  when clicked, this panel will be displayed in the target container (ie, sidebarContainer)  
-    static setupCreatePanel(sidebarContainer: Doc) {
+    static setupCreatePanel(sidebarContainer: Doc, doc: Doc) {
         // setup a masonry view of all he creators
-        const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(), {
+        const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(doc), {
             width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons"
         });
         // setup a color picker
@@ -129,7 +132,7 @@ export class CurrentUserUtils {
         doc.sidebarContainer = new Doc();
         (doc.sidebarContainer as Doc).chromeStatus = "disabled";
 
-        doc.CreateBtn = this.setupCreatePanel(doc.sidebarContainer as Doc);
+        doc.CreateBtn = this.setupCreatePanel(doc.sidebarContainer as Doc, doc);
         doc.LibraryBtn = this.setupLibraryPanel(doc.sidebarContainer as Doc, doc);
         doc.SearchBtn = this.setupSearchPanel(doc.sidebarContainer as Doc);
 
@@ -143,9 +146,9 @@ export class CurrentUserUtils {
     /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
     static setupExpandingButtons(doc: Doc) {
         doc.undoBtn = Docs.Create.FontIconDocument(
-            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" });
+            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" });
         doc.redoBtn = Docs.Create.FontIconDocument(
-            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" });
+            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" });
 
         doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc], {
             title: "expanding buttons", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0",
-- 
cgit v1.2.3-70-g09d2


From e4e6026dfb819dd141597e7a2dfab6c566f61fac Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 15 Oct 2019 23:37:36 -0400
Subject: fixes to feedback of color buttons

---
 src/Utils.ts                              | 10 +++++-----
 src/client/views/CollectionLinearView.tsx |  6 +++---
 src/client/views/GlobalKeyHandler.ts      |  3 +++
 src/client/views/InkingControl.tsx        |  2 +-
 src/client/views/nodes/ColorBox.tsx       |  8 ++++++++
 src/client/views/nodes/DocumentView.tsx   |  2 +-
 src/client/views/nodes/FontIconBox.tsx    | 25 ++++++++++++++++++++++---
 src/new_fields/Doc.ts                     |  2 +-
 8 files changed, 44 insertions(+), 14 deletions(-)

(limited to 'src/client/views/nodes/FontIconBox.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 9a2f01f80..6889936b8 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -62,14 +62,14 @@ export namespace Utils {
     }
 
     export function fromRGBAstr(rgba: string) {
-        let rm = rgba.match(/rgb[a]?\(([0-9]+)/);
+        let rm = rgba.match(/rgb[a]?\(([ 0-9]+)/);
         let r = rm ? Number(rm[1]) : 0;
-        let gm = rgba.match(/rgb[a]?\([0-9]+,([0-9]+)/);
+        let gm = rgba.match(/rgb[a]?\([ 0-9]+,([ 0-9]+)/);
         let g = gm ? Number(gm[1]) : 0;
-        let bm = rgba.match(/rgb[a]?\([0-9]+,[0-9]+,([0-9]+)/);
+        let bm = rgba.match(/rgb[a]?\([ 0-9]+,[ 0-9]+,([ 0-9]+)/);
         let b = bm ? Number(bm[1]) : 0;
-        let am = rgba.match(/rgba?\([0-9]+,[0-9]+,[0-9]+,([0-9]+)/);
-        let a = am ? Number(am[1]) : 0;
+        let am = rgba.match(/rgba?\([ 0-9]+,[ 0-9]+,[ 0-9]+,([ .0-9]+)/);
+        let a = am ? Number(am[1]) : 1;
         return { r: r, g: g, b: b, a: a };
     }
 
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index eb3c768d0..af3b194ea 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -25,6 +25,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     @observable public addMenuToggle = React.createRef<HTMLInputElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _heightDisposer?: IReactionDisposer;
+    private _spacing = 20;
 
     componentWillUnmount() {
         this._dropDisposer && this._dropDisposer();
@@ -69,8 +70,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         if (!ele.current) return Transform.Identity();
         let { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
         return new Transform(-translateX, -translateY, 1 / scale);
-    };
-    _spacing = 20;
+    }
     render() {
         let guid = Utils.GenerateGuid();
         return <div className="collectionLinearView-outer">
@@ -118,7 +118,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 zoomToScale={emptyFunction}
                                 getScale={returnOne}>
                             </DocumentView>
-                        </div>
+                        </div>;
                     })}
                     {/* <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li> */}
 
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index f31a29bdc..9ca9fc163 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -10,6 +10,8 @@ import SharingManager from "../util/SharingManager";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 import { Cast, PromiseValue } from "../../new_fields/Types";
 import { ScriptField } from "../../new_fields/ScriptField";
+import { InkingControl } from "./InkingControl";
+import { InkTool } from "../../new_fields/InkField";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -64,6 +66,7 @@ export default class KeyManager {
         switch (keyname) {
             case "escape":
                 let main = MainView.Instance;
+                InkingControl.Instance.switchTool(InkTool.None);
                 if (main.isPointerDown) {
                     DragManager.AbortDrag();
                 } else {
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index b51264932..51fc7ca8f 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -25,7 +25,7 @@ export class InkingControl {
 
     switchTool = action((tool: InkTool): void => {
         this._selectedTool = tool;
-    })
+    });
     decimalToHexString(number: number) {
         if (number < 0) {
             number = 0xFFFFFFFF + number + 1;
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 87c91c121..e4fef0922 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -10,6 +10,8 @@ import { makeInterface } from "../../../new_fields/Schema";
 import { trace, reaction, observable, action, IReactionDisposer } from "mobx";
 import { SelectionManager } from "../../util/SelectionManager";
 import { StrCast } from "../../../new_fields/Types";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { Doc } from "../../../new_fields/Doc";
 
 type ColorDocument = makeInterface<[typeof documentSchema]>;
 const ColorDocument = makeInterface(documentSchema);
@@ -18,10 +20,15 @@ const ColorDocument = makeInterface(documentSchema);
 export class ColorBox extends DocStaticComponent<FieldViewProps, ColorDocument>(ColorDocument) {
     public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
     _selectedDisposer: IReactionDisposer | undefined;
+    _penDisposer: IReactionDisposer | undefined;
     componentDidMount() {
         this._selectedDisposer = reaction(() => SelectionManager.SelectedDocuments(),
             action(() => this._startupColor = SelectionManager.SelectedDocuments().length ? StrCast(SelectionManager.SelectedDocuments()[0].Document.backgroundColor, "black") : "black"),
             { fireImmediately: true });
+        this._penDisposer = reaction(() => CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen,
+            action(() => this._startupColor = CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen instanceof Doc ?
+                StrCast(CurrentUserUtils.UserDocument.activePen.pen.backgroundColor, "black") : "black"),
+            { fireImmediately: true });
 
         // compare to this reaction that used to be in Selection Manager
         // reaction(() => manager.SelectedDocuments, sel => {
@@ -37,6 +44,7 @@ export class ColorBox extends DocStaticComponent<FieldViewProps, ColorDocument>(
         // }, { fireImmediately: true });
     }
     componentWillUnmount() {
+        this._penDisposer && this._penDisposer();
         this._selectedDisposer && this._selectedDisposer();
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6f99c541f..8a320d39b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -676,7 +676,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
         const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"];
-        let highlighting = fullDegree && this.props.Document.type !== DocumentType.FONTICON && this.props.Document.viewType !== CollectionViewType.Linear
+        let highlighting = fullDegree && this.props.Document.type !== DocumentType.FONTICON && this.props.Document.viewType !== CollectionViewType.Linear;
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index efe47d8a8..1c2ed33eb 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -6,6 +6,8 @@ import { DocComponent } from '../DocComponent';
 import './FontIconBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
 import { StrCast } from '../../../new_fields/Types';
+import { Utils } from "../../../Utils";
+import { runInAction, observable, reaction, IReactionDisposer } from 'mobx';
 const FontIconSchema = createSchema({
     icon: "string"
 });
@@ -15,10 +17,27 @@ const FontIconDocument = makeInterface(FontIconSchema);
 @observer
 export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(FontIconDocument) {
     public static LayoutString() { return FieldView.LayoutString(FontIconBox); }
-
+    @observable _foregroundColor = "white";
+    _ref: React.RefObject<HTMLButtonElement> = React.createRef();
+    _backgroundReaction: IReactionDisposer | undefined;
+    componentDidMount() {
+        this._backgroundReaction = reaction(() => this.props.Document.backgroundColor,
+            () => {
+                if (this._ref && this._ref.current) {
+                    let col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor!);
+                    let colsum = (col.r + col.g + col.b);
+                    if (colsum / col.a > 600 || col.a < 0.25) runInAction(() => this._foregroundColor = "black");
+                    else if (colsum / col.a <= 600 || col.a >= .25) runInAction(() => this._foregroundColor = "white");
+                }
+            }, { fireImmediately: true });
+    }
+    componentWillUnmount() {
+        this._backgroundReaction && this._backgroundReaction();
+    }
     render() {
-        return <button className="fontIconBox-outerDiv" style={{ background: StrCast(this.props.Document.backgroundColor) }}>
-            <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} size="sm" opacity={this.props.Document.unchecked ? "0.5" : "1"} />
+        return <button className="fontIconBox-outerDiv" ref={this._ref}
+            style={{ background: StrCast(this.props.Document.backgroundColor), boxShadow: this.props.Document.unchecked ? undefined : `4px 4px 12px black` }}>
+            <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} color={this._foregroundColor} size="sm" />
         </button>;
     }
 }
\ No newline at end of file
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 276596fb8..9e0944c69 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -738,6 +738,6 @@ Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return Doc.Make
 Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); });
 Scripting.addGlobal(function aliasDocs(field: any) { return new List<Doc>(field.map((d: any) => Doc.MakeAlias(d))); });
 Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
-Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2) });
+Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });
 Scripting.addGlobal(function undo() { return UndoManager.Undo(); });
 Scripting.addGlobal(function redo() { return UndoManager.Redo(); });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 6d9cad19047b9970a8429771e7c0e03554e63f39 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 16 Oct 2019 12:13:30 -0400
Subject: more small fixes.  dragging dragIcons, pdfmenu highlighting,
 fonticonbox hover text

---
 src/client/util/DragManager.ts                     |  3 +-
 src/client/views/DocumentDecorations.tsx           |  1 +
 src/client/views/MainView.tsx                      |  4 ++-
 src/client/views/collections/CollectionSubView.tsx |  7 ++---
 src/client/views/nodes/FontIconBox.tsx             |  2 +-
 src/client/views/nodes/PDFBox.scss                 |  1 +
 src/client/views/pdf/PDFMenu.scss                  |  2 +-
 .../authentication/models/current_user_utils.ts    | 32 +++++++++++-----------
 8 files changed, 28 insertions(+), 24 deletions(-)

(limited to 'src/client/views/nodes/FontIconBox.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 92666c03c..dcd19e50b 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -212,6 +212,7 @@ export namespace DragManager {
         dropAction: dropActionType;
         userDropAction: dropActionType;
         moveDocument?: MoveFunction;
+        isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged.  this will suppress onDragStart scripts
         applyAsTemplate?: boolean;
         [id: string]: any;
     }
@@ -240,7 +241,7 @@ export namespace DragManager {
         StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag :
             (dropData: { [id: string]: any }) => {
                 (dropData.droppedDocuments =
-                    dragData.draggedDocuments.map(d => ScriptCast(d.onDragStart) ? ScriptCast(d.onDragStart).script.run({ this: d }).result :
+                    dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? ScriptCast(d.onDragStart).script.run({ this: d }).result :
                         dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ? Doc.MakeAlias(d) :
                             dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeCopy(d, true) : d)
                 );
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3d73f048d..755739a11 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -202,6 +202,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
         dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top);
         dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument;
+        dragData.isSelectionMove = true;
         this.Interacting = true;
         this._hidden = true;
         document.removeEventListener("pointermove", this.onBackgroundMove);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8035916a2..0e15784f7 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,7 +1,7 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import {
     faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
-    faMusic, faObjectGroup, faPause, faPenNib, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt
+    faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -102,7 +102,9 @@ export class MainView extends React.Component {
         library.add(faGlobeAsia);
         library.add(faUndoAlt);
         library.add(faRedoAlt);
+        library.add(faMousePointer);
         library.add(faPen);
+        library.add(faHighlighter);
         library.add(faEraser);
         library.add(faPenNib);
         library.add(faFilm);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index fdbe5339d..9919a9dc3 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -133,11 +133,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 if (de.data.dropAction || de.data.userDropAction) {
                     added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 } else if (de.data.moveDocument) {
-                    let movedDocs = de.data.draggedDocuments;// de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments;
-                    // note that it's possible the drag function might create a drop document that's not the same as the
-                    // original dragged document.  So we explicitly call addDocument() with a droppedDocument and 
+                    let movedDocs = de.data.draggedDocuments;
                     added = movedDocs.reduce((added: boolean, d, i) =>
-                        de.data.moveDocument(d, this.props.Document, (doc: Doc) => this.props.addDocument(de.data.droppedDocuments[i])) || added, false);
+                        de.data.droppedDocuments[i] !== d ? this.props.addDocument(de.data.droppedDocuments[i]) :
+                            de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false);
                 } else {
                     added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 }
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 1c2ed33eb..848afccf3 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -35,7 +35,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
         this._backgroundReaction && this._backgroundReaction();
     }
     render() {
-        return <button className="fontIconBox-outerDiv" ref={this._ref}
+        return <button className="fontIconBox-outerDiv" title={StrCast(this.props.Document.title)} ref={this._ref}
             style={{ background: StrCast(this.props.Document.backgroundColor), boxShadow: this.props.Document.unchecked ? undefined : `4px 4px 12px black` }}>
             <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} color={this._foregroundColor} size="sm" />
         </button>;
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index dd909e09f..deb98dc8d 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -7,6 +7,7 @@
     overflow: hidden;
     position:absolute;
     z-index: -1;
+    cursor:auto;
 }
 
 .pdfBox-title-outer {
diff --git a/src/client/views/pdf/PDFMenu.scss b/src/client/views/pdf/PDFMenu.scss
index b06d19c53..44e075153 100644
--- a/src/client/views/pdf/PDFMenu.scss
+++ b/src/client/views/pdf/PDFMenu.scss
@@ -15,7 +15,7 @@
     }
 
     .pdfMenu-button:hover {
-        background-color: #121212;
+        background-color: #d4d4d4;
     }
 
     .pdfMenu-dragger {
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 4abf85381..0fe8d996f 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -47,20 +47,20 @@ export class CurrentUserUtils {
     // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
     static setupCreatorButtons(doc: Doc) {
         doc.activePen = doc;
-        let docProtoData: { title: string, icon: string, drag?: string, click?: string, unchecked?: string, activePen?: Doc, backgroundColor?: string }[] = [
-            { title: "collection", icon: "folder", drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' },
-            { title: "web page", icon: "globe-asia", drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' },
-            { title: "image", icon: "cat", drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' },
-            { title: "button", icon: "bolt", drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' },
-            { title: "presentation", icon: "tv", drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })' },
-            { title: "import folder", icon: "cloud-upload-alt", drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' },
-            { title: "pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", unchecked: `!sameDocs(this.activePen.pen,  this)`, activePen: doc },
-            { title: "highlighter", icon: "pen", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc },
-            { title: "eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc },
-            { title: "none", icon: "pause", click: 'deactivateInk();this.activePen.pen = this;', unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc },
+        let docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, unchecked?: string, activePen?: Doc, backgroundColor?: string }[] = [
+            { title: "collection", icon: "folder", ignoreClick: true, drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' },
+            { title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' },
+            { title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' },
+            { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' },
+            { title: "presentation", icon: "tv", ignoreClick: true, drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })' },
+            { title: "import folder", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' },
+            { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", unchecked: `!sameDocs(this.activePen.pen,  this)`, activePen: doc },
+            { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc },
+            { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc },
+            { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', unchecked: `!sameDocs(this.activePen.pen, this) && this.activePen.pen !== undefined`, backgroundColor: "white", activePen: doc },
         ];
         return docProtoData.map(data => Docs.Create.FontIconDocument({
-            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon,
+            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
             onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
             unchecked: data.unchecked ? ComputedField.MakeFunction(data.unchecked) : undefined, activePen: data.activePen,
             backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"])
@@ -144,9 +144,9 @@ export class CurrentUserUtils {
     /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
     static setupExpandingButtons(doc: Doc) {
         doc.undoBtn = Docs.Create.FontIconDocument(
-            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" });
+            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("undo()"), removeDropProperties: new List<string>(["dropAction"]), title: "undo button", icon: "undo-alt" });
         doc.redoBtn = Docs.Create.FontIconDocument(
-            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" });
+            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), removeDropProperties: new List<string>(["dropAction"]), title: "redo button", icon: "redo-alt" });
 
         doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc], {
             title: "expanding buttons", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0",
@@ -192,8 +192,8 @@ export class CurrentUserUtils {
         });
 
         // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
-        doc.undoBtn && reaction(() => UndoManager.undoStack.slice(), () => (doc.undoBtn as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
-        doc.redoBtn && reaction(() => UndoManager.redoStack.slice(), () => (doc.redoBtn as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
+        doc.undoBtn && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc.undoBtn as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
+        doc.redoBtn && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc.redoBtn as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
 
         return doc;
     }
-- 
cgit v1.2.3-70-g09d2


From 8a3cfa8d6e72c9bfea4b38760e0b138b6525574c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 16 Oct 2019 16:42:37 -0400
Subject: a bunch of fixes to layouts to support templates in paticular.

---
 src/client/util/DocumentManager.ts                 |   2 +
 src/client/util/DragManager.ts                     |   4 +-
 src/client/views/CollectionLinearView.tsx          |   7 +-
 src/client/views/DocumentButtonBar.tsx             |   3 +-
 src/client/views/DocumentDecorations.tsx           |  47 +++++-----
 src/client/views/InkingControl.tsx                 |   9 +-
 .../views/collections/CollectionStackingView.tsx   |  11 ++-
 .../views/collections/CollectionTreeView.scss      |   1 +
 .../views/collections/CollectionTreeView.tsx       |  17 ++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  63 ++++++-------
 .../collections/collectionFreeForm/MarqueeView.tsx |  31 ++++---
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   4 +-
 src/client/views/nodes/DocumentView.tsx            |   6 +-
 src/client/views/nodes/FontIconBox.tsx             |   5 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 101 +++++++++++----------
 src/new_fields/Doc.ts                              |  11 ++-
 16 files changed, 178 insertions(+), 144 deletions(-)

(limited to 'src/client/views/nodes/FontIconBox.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 00de39671..e23ac55c2 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -72,6 +72,8 @@ export class DocumentManager {
                         toReturn = view;
                     }
                 });
+            } else {
+                break;
             }
         }
 
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 12e0b11ba..06dab024e 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -211,7 +211,7 @@ export namespace DragManager {
         offset: number[];
         dropAction: dropActionType;
         userDropAction: dropActionType;
-        forceUserDropAction: dropActionType;
+        embedDoc?: boolean;
         moveDocument?: MoveFunction;
         isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged.  this will suppress onDragStart scripts
         applyAsTemplate?: boolean;
@@ -414,7 +414,7 @@ export namespace DragManager {
         const moveHandler = (e: PointerEvent) => {
             e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
             if (dragData instanceof DocumentDragData) {
-                dragData.userDropAction = dragData.forceUserDropAction ? dragData.forceUserDropAction : e.ctrlKey || e.altKey ? "alias" : undefined;
+                dragData.userDropAction = e.ctrlKey ? "alias" : undefined;
             }
             if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) {
                 AbortDrag();
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index af3b194ea..44d9b042e 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -15,6 +15,7 @@ import { CollectionViewType } from './collections/CollectionBaseView';
 import { CollectionSubView } from './collections/CollectionSubView';
 import { documentSchema, DocumentView } from './nodes/DocumentView';
 import { translate } from 'googleapis/build/src/apis/translate';
+import { DocumentType } from '../documents/DocumentTypes';
 
 
 type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -50,8 +51,10 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => {
             let dbox = doc;
             if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) {
-                dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: "bolt" });
-                dbox.dragFactory = doc;
+                let template = doc.layout instanceof Doc && doc.layout.isTemplate ? doc.layout : doc;
+                template.isTemplate = (template.type === DocumentType.TEXT || template.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction;
+                dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: template.isTemplate ? "font" : "bolt" });
+                dbox.dragFactory = template;
                 dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
                 dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
             } else if (doc.viewType === CollectionViewType.Linear) {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index a00a4298f..959b120ed 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -165,7 +165,8 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
             let dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
             const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
             dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.clientX - left, e.clientY - top);
-            dragData.forceUserDropAction = "alias";
+            dragData.embedDoc = true;
+            dragData.dropAction = "alias";
             DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, e.x, e.y, {
                 offsetX: dragData.offset[0],
                 offsetY: dragData.offset[1],
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 755739a11..2f40ea746 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -471,47 +471,48 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         SelectionManager.SelectedDocuments().forEach(element => {
             if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
                 let doc = PositionDocument(element.props.Document);
+                let layoutDoc = PositionDocument(element.props.Document.layout instanceof Doc ? element.props.Document.layout : element.props.Document);
                 let nwidth = doc.nativeWidth || 0;
                 let nheight = doc.nativeHeight || 0;
-                let width = (doc.width || 0);
-                let height = (doc.height || (nheight / nwidth * width));
+                let width = (layoutDoc.width || 0);
+                let height = (layoutDoc.height || (nheight / nwidth * width));
                 let scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling();
                 let actualdW = Math.max(width + (dW * scale), 20);
                 let actualdH = Math.max(height + (dH * scale), 20);
-                doc.x = (doc.x || 0) + dX * (actualdW - width);
-                doc.y = (doc.y || 0) + dY * (actualdH - height);
+                layoutDoc.x = (layoutDoc.x || 0) + dX * (actualdW - width);
+                layoutDoc.y = (layoutDoc.y || 0) + dY * (actualdH - height);
                 let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here...
-                let fixedAspect = e.ctrlKey || (!doc.ignoreAspect && nwidth && nheight);
-                if (fixedAspect && e.ctrlKey && doc.ignoreAspect) {
-                    doc.ignoreAspect = false;
-                    proto.nativeWidth = nwidth = doc.width || 0;
-                    proto.nativeHeight = nheight = doc.height || 0;
+                let fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight);
+                if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) {
+                    layoutDoc.ignoreAspect = false;
+                    proto.nativeWidth = nwidth = layoutDoc.width || 0;
+                    proto.nativeHeight = nheight = layoutDoc.height || 0;
                 }
                 if (fixedAspect && (!nwidth || !nheight)) {
-                    proto.nativeWidth = nwidth = doc.width || 0;
-                    proto.nativeHeight = nheight = doc.height || 0;
+                    proto.nativeWidth = nwidth = layoutDoc.width || 0;
+                    proto.nativeHeight = nheight = layoutDoc.height || 0;
                 }
-                if (nwidth > 0 && nheight > 0 && !BoolCast(doc.ignoreAspect)) {
+                if (nwidth > 0 && nheight > 0 && !layoutDoc.ignoreAspect) {
                     if (Math.abs(dW) > Math.abs(dH)) {
                         if (!fixedAspect) {
-                            Doc.SetInPlace(element.props.Document, "nativeWidth", actualdW / (doc.width || 1) * (doc.nativeWidth || 0), true);
+                            Doc.SetInPlace(doc, "nativeWidth", actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0), true);
                         }
-                        doc.width = actualdW;
-                        if (fixedAspect && !doc.fitWidth) doc.height = nheight / nwidth * doc.width;
-                        else doc.height = actualdH;
+                        layoutDoc.width = actualdW;
+                        if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width;
+                        else layoutDoc.height = actualdH;
                     }
                     else {
                         if (!fixedAspect) {
-                            Doc.SetInPlace(element.props.Document, "nativeHeight", actualdH / (doc.height || 1) * (doc.nativeHeight || 0), true);
+                            Doc.SetInPlace(doc, "nativeHeight", actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0), true);
                         }
-                        doc.height = actualdH;
-                        if (fixedAspect && !doc.fitWidth) doc.width = nwidth / nheight * doc.height;
-                        else doc.width = actualdW;
+                        layoutDoc.height = actualdH;
+                        if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height;
+                        else layoutDoc.width = actualdW;
                     }
                 } else {
-                    dW && (doc.width = actualdW);
-                    dH && (doc.height = actualdH);
-                    dH && element.props.Document.autoHeight && Doc.SetInPlace(element.props.Document, "autoHeight", false, true);
+                    dW && (layoutDoc.width = actualdW);
+                    dH && (layoutDoc.height = actualdH);
+                    dH && layoutDoc.autoHeight && Doc.SetInPlace(layoutDoc, "autoHeight", false, true);
                 }
             }
         });
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 51fc7ca8f..46c6fae1c 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -41,7 +41,9 @@ export class InkingControl {
         if (InkingControl.Instance.selectedTool === InkTool.None) {
             let selected = SelectionManager.SelectedDocuments();
             let oldColors = selected.map(view => {
-                let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document);
+                let targetDoc = view.props.Document.dragFactory instanceof Doc ? view.props.Document.dragFactory :
+                    view.props.Document.layout instanceof Doc ? view.props.Document.layout :
+                        view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document);
                 let sel = window.getSelection();
                 if (StrCast(targetDoc.layout).indexOf("FormattedTextBox") !== -1 && (!sel || sel.toString() !== "")) {
                     targetDoc.color = this._selectedColor;
@@ -79,7 +81,10 @@ export class InkingControl {
                     ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined;
                     ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)));
                 }
-                !ruleProvider && (targetDoc.backgroundColor = matchedColor);
+                if (!ruleProvider) {
+                    if (targetDoc)
+                        targetDoc.backgroundColor = matchedColor;
+                }
 
                 return {
                     target: targetDoc,
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 90fc9c3e0..cde1a5036 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -110,9 +110,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     componentDidMount() {
-        // is there any reason this needs to exist? -syip.  yes, it handles autoHeight for stacking views (masonry isn't yet supported).
         this._heightDisposer = reaction(() => {
-            if (BoolCast(this.props.Document.autoHeight)) {
+            if (this.props.Document.autoHeight) {
                 let sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]);
                 if (this.isStackingView) {
                     return this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => Math.max(maxHght,
@@ -188,15 +187,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     getDocHeight(d?: Doc) {
         if (!d) return 0;
+        let layoutDoc = d.layout instanceof Doc ? d.layout : d;
         let nw = NumCast(d.nativeWidth);
         let nh = NumCast(d.nativeHeight);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
-        if (!d.ignoreAspect && !d.fitWidth && nw && nh) {
+        if (!layoutDoc.ignoreAspect && !layoutDoc.fitWidth && nw && nh) {
             let aspect = nw && nh ? nh / nw : 1;
-            if (!(d.nativeWidth && !d.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(d[WidthSym](), wid);
+            if (!(d.nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
             return wid * aspect;
         }
-        return d.fitWidth ? !d.nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin : Math.min(wid * NumCast(d.scrollHeight, NumCast(d.nativeHeight)) / NumCast(d.nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : d[HeightSym]();
+        return layoutDoc.fitWidth ? !d.nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin :
+            Math.min(wid * NumCast(layoutDoc.scrollHeight, NumCast(d.nativeHeight)) / NumCast(d.nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym]();
     }
 
     columnDividerDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index ca0c321b7..bff8ce5c1 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -16,6 +16,7 @@
     background: $light-color-secondary;
     font-size: 13px;
     overflow: auto;
+    cursor: default;
 
     ul {
         list-style: none;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index abaa9662c..403da0e54 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -251,19 +251,21 @@ class TreeView extends React.Component<TreeViewProps> {
     }
     docWidth = () => {
         let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth);
-        if (aspect) return Math.min(this.props.document[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20));
-        return NumCast(this.props.document.nativeWidth) ? Math.min(this.props.document[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20;
+        let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document;
+        if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20));
+        return NumCast(this.props.document.nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20;
     }
     docHeight = () => {
         let bounds = this.boundsOfCollectionDocument;
         return Math.min(this.MAX_EMBED_HEIGHT, (() => {
             let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth);
+            let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document;
             if (aspect) return this.docWidth() * aspect;
             if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x);
-            return this.props.document.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) :
-                Math.min(this.docWidth() * NumCast(this.props.document.scrollHeight, NumCast(this.props.document.nativeHeight)) / NumCast(this.props.document.nativeWidth,
+            return layoutDoc.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) :
+                Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(this.props.document.nativeHeight)) / NumCast(this.props.document.nativeWidth,
                     NumCast(this.props.containingCollection.height)))) :
-                NumCast(this.props.document.height) ? NumCast(this.props.document.height) : 50;
+                NumCast(layoutDoc.height) ? NumCast(layoutDoc.height) : 50;
         })());
     }
 
@@ -461,10 +463,11 @@ class TreeView extends React.Component<TreeViewProps> {
 
         let rowWidth = () => panelWidth() - 20;
         return docs.map((child, i) => {
-            let pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child);
+            const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child);
             if (!pair.layout || pair.data instanceof Promise) {
                 return (null);
             }
+            const childLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout;
 
             let indent = i === 0 ? undefined : () => {
                 if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) {
@@ -482,7 +485,7 @@ class TreeView extends React.Component<TreeViewProps> {
             };
             let rowHeight = () => {
                 let aspect = NumCast(child.nativeWidth, 0) / NumCast(child.nativeHeight, 0);
-                return aspect ? Math.min(child[WidthSym](), rowWidth()) / aspect : child[HeightSym]();
+                return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]();
             };
             return <TreeView
                 document={pair.layout}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index adbad5da5..229e7fffc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -30,7 +30,6 @@ import { DocumentContentsView } from "../../nodes/DocumentContentsView";
 import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView";
 import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
-import PDFMenu from "../../pdf/PDFMenu";
 import { CollectionSubView } from "../CollectionSubView";
 import { computePivotLayout, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
@@ -131,21 +130,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         if (super.drop(e, de)) {
             if (de.data instanceof DragManager.DocumentDragData) {
                 if (de.data.droppedDocuments.length) {
-                    let z = NumCast(de.data.droppedDocuments[0].z);
+                    let firstLayoutDoc = de.data.droppedDocuments[0].layout instanceof Doc ? de.data.droppedDocuments[0].layout : de.data.droppedDocuments[0];
+                    let z = NumCast(firstLayoutDoc.z);
                     let x = (z ? xpo : xp) - de.data.offset[0];
                     let y = (z ? ypo : yp) - de.data.offset[1];
-                    let dropX = NumCast(de.data.droppedDocuments[0].x);
-                    let dropY = NumCast(de.data.droppedDocuments[0].y);
+                    let dropX = NumCast(firstLayoutDoc.x);
+                    let dropY = NumCast(firstLayoutDoc.y);
                     de.data.droppedDocuments.forEach(action((d: Doc) => {
-                        d.x = x + NumCast(d.x) - dropX;
-                        d.y = y + NumCast(d.y) - dropY;
-                        if (!NumCast(d.width)) {
-                            d.width = 300;
+                        let layoutDoc = d.layout instanceof Doc ? d.layout : d;
+                        layoutDoc.x = x + NumCast(layoutDoc.x) - dropX;
+                        layoutDoc.y = y + NumCast(layoutDoc.y) - dropY;
+                        if (!NumCast(layoutDoc.width)) {
+                            layoutDoc.width = 300;
                         }
                         if (!NumCast(d.height)) {
                             let nw = NumCast(d.nativeWidth);
                             let nh = NumCast(d.nativeHeight);
-                            d.height = nw && nh ? nh / nw * NumCast(d.width) : 300;
+                            layoutDoc.height = nw && nh ? nh / nw * NumCast(layoutDoc.width) : 300;
                         }
                         this.bringToFront(d);
                     }));
@@ -156,14 +157,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             else if (de.data instanceof DragManager.AnnotationDragData) {
                 if (de.data.dropDocument) {
                     let dragDoc = de.data.dropDocument;
+                    let layoutDoc = dragDoc.layout instanceof Doc ? dragDoc.layout : dragDoc;
                     let x = xp - de.data.offset[0];
                     let y = yp - de.data.offset[1];
-                    let dropX = NumCast(de.data.dropDocument.x);
-                    let dropY = NumCast(de.data.dropDocument.y);
-                    dragDoc.x = x + NumCast(dragDoc.x) - dropX;
-                    dragDoc.y = y + NumCast(dragDoc.y) - dropY;
-                    de.data.targetContext = this.props.Document;
-                    dragDoc.targetContext = this.props.Document;
+                    let dropX = NumCast(layoutDoc.x);
+                    let dropY = NumCast(layoutDoc.y);
+                    layoutDoc.x = x + NumCast(layoutDoc.x) - dropX;
+                    layoutDoc.y = y + NumCast(layoutDoc.y) - dropY;
+                    de.data.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation
                     this.bringToFront(dragDoc);
                 }
             }
@@ -173,11 +174,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     pickCluster(probe: number[]) {
         return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => {
-            let cx = NumCast(cd.x) - this._clusterDistance;
-            let cy = NumCast(cd.y) - this._clusterDistance;
-            let cw = NumCast(cd.width) + 2 * this._clusterDistance;
-            let ch = NumCast(cd.height) + 2 * this._clusterDistance;
-            return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ?
+            let layoutDoc = cd.layout instanceof Doc ? cd.layout : cd;
+            let cx = NumCast(layoutDoc.x) - this._clusterDistance;
+            let cy = NumCast(layoutDoc.y) - this._clusterDistance;
+            let cw = NumCast(layoutDoc.width) + 2 * this._clusterDistance;
+            let ch = NumCast(layoutDoc.height) + 2 * this._clusterDistance;
+            return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ?
                 NumCast(cd.cluster) : cluster;
         }, -1);
     }
@@ -185,14 +187,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         let cluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
         if (cluster !== -1) {
             let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster);
-
-            // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view
-            let prevSelected = SelectionManager.SelectedDocuments();
-            this.selectDocuments(eles);
-            let clusterDocs = SelectionManager.SelectedDocuments();
-            SelectionManager.DeselectAll();
-            prevSelected.map(dv => SelectionManager.SelectDoc(dv, true));
-
+            let clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!);
             let de = new DragManager.DocumentDragData(eles);
             de.moveDocument = this.props.moveDocument;
             const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
@@ -223,8 +218,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
             let preferredInd = NumCast(doc.cluster);
             doc.cluster = -1;
+            let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc;
             this._clusterSets.map((set, i) => set.map(member => {
-                if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+                let memberLayout = member.layout instanceof Doc ? member.layout : member;
+                if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(layoutDoc, memberLayout, this._clusterDistance)) {
                     doc.cluster = i;
                 }
             }));
@@ -296,10 +293,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }
             let x = this.Document.panX || 0;
             let y = this.Document.panY || 0;
-            let docs = this.childLayoutPairs.map(pair => pair.layout);
+            let docs = this.childLayoutPairs.map(pair => pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout);
             let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
             if (!this.isAnnotationOverlay) {
-                PDFMenu.Instance.fadeOut(true);
                 let minx = docs.length ? NumCast(docs[0].x) : 0;
                 let maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
                 let miny = docs.length ? NumCast(docs[0].y) : 0;
@@ -549,7 +545,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         let elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
 
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
-            const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
+            let finalLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout;
+            const pos = this.getCalculatedPositions({ doc: finalLayout, index: i, collection: this.Document, docs: layoutDocs, state });
             state = pos.state === undefined ? state : pos.state;
             layoutPoolData.set(pair, pos);
         });
@@ -592,7 +589,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             let i = 0;
             const width = Math.max(...docs.map(doc => NumCast(doc.width)));
             const height = Math.max(...docs.map(doc => NumCast(doc.height)));
-            for (const doc of docs) {
+            for (const doc of docs.map(d => d.layout instanceof Doc ? d.layout : d)) {
                 doc.x = x;
                 doc.y = y;
                 x += width + 20;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ecdd02b0f..1362736cf 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -281,7 +281,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             let bounds = this.Bounds;
             let selected = this.marqueeSelect(false);
             if (e.key === "c") {
-                selected.map(d => {
+                selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => {
                     this.props.removeDocument(d);
                     d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
                     d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
@@ -325,7 +325,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             this.marqueeInkDelete(inkData);
 
             if (e.key === "s" || e.key === "S") {
-                selected.map(d => {
+                selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => {
                     this.props.removeDocument(d);
                     d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
                     d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
@@ -394,20 +394,22 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         let selRect = this.Bounds;
         let selection: Doc[] = [];
         this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => {
-            var x = NumCast(doc.x);
-            var y = NumCast(doc.y);
-            var w = NumCast(doc.width);
-            var h = NumCast(doc.height);
+            let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc;
+            var x = NumCast(layoutDoc.x);
+            var y = NumCast(layoutDoc.y);
+            var w = NumCast(layoutDoc.width);
+            var h = NumCast(layoutDoc.height);
             if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
                 selection.push(doc);
             }
         });
         if (!selection.length && selectBackgrounds) {
             this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => {
-                var x = NumCast(doc.x);
-                var y = NumCast(doc.y);
-                var w = NumCast(doc.width);
-                var h = NumCast(doc.height);
+                let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc;
+                var x = NumCast(layoutDoc.x);
+                var y = NumCast(layoutDoc.y);
+                var w = NumCast(layoutDoc.width);
+                var h = NumCast(layoutDoc.height);
                 if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
                     selection.push(doc);
                 }
@@ -420,10 +422,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
             let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
             this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => {
-                var x = NumCast(doc.x);
-                var y = NumCast(doc.y);
-                var w = NumCast(doc.width);
-                var h = NumCast(doc.height);
+                let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc;
+                var x = NumCast(layoutDoc.x);
+                var y = NumCast(layoutDoc.y);
+                var w = NumCast(layoutDoc.width);
+                var h = NumCast(layoutDoc.height);
                 if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) {
                     selection.push(doc);
                 }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 93f6dc468..5e8ac3ecd 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -36,8 +36,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; }
     get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : this.Document.x || 0; }
     get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : this.Document.y || 0; }
-    get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.props.Document[WidthSym](); }
-    get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.props.Document[HeightSym](); }
+    get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); }
+    get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym](); }
     @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document, this.props.DataDoc) ? this.props.dataProvider(this.props.Document, this.props.DataDoc) : undefined; }
     @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); }
     @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ebd0ca549..a071f782a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -224,7 +224,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             maxLocation = this.Document.maximizeLocation = (!ctrlKey ? !altKey ? maxLocation : (maxLocation !== "inPlace" ? "inPlace" : "onRight") : (maxLocation !== "inPlace" ? "inPlace" : "inTab"));
             if (maxLocation === "inPlace") {
                 expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc));
-                let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2);
+                let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.layoutDoc.width) / 2, NumCast(this.layoutDoc.height) / 2);
                 DocumentManager.Instance.animateBetweenPoint(scrpt, expandedDocs);
             } else {
                 expandedDocs.forEach(maxDoc => (!this.props.addDocTab(maxDoc, undefined, "close") && this.props.addDocTab(maxDoc, undefined, maxLocation)));
@@ -392,7 +392,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (!anchors.find(anchor2 => anchor2 && anchor2.title === this.Document.title + ".portal" ? true : false)) {
             let portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, "");
             DocServer.GetRefField(portalID).then(existingPortal => {
-                let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.Document.width || 0) + 10, height: this.Document.height || 0, title: portalID });
+                let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.layoutDoc.width || 0) + 10, height: this.layoutDoc.height || 0, title: portalID });
                 DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: portal }, portalID, "portal link");
                 this.Document.isButton = true;
             });
@@ -491,7 +491,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" });
         }
         layoutItems.push({ description: `${this.Document.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document.chromeStatus = (this.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
-        layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.Document.autoHeight = !this.Document.autoHeight, icon: "plus" });
+        layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" });
         layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
         layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
         layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 848afccf3..c5bf28d5b 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -8,6 +8,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { StrCast } from '../../../new_fields/Types';
 import { Utils } from "../../../Utils";
 import { runInAction, observable, reaction, IReactionDisposer } from 'mobx';
+import { Doc } from '../../../new_fields/Doc';
 const FontIconSchema = createSchema({
     icon: "string"
 });
@@ -35,8 +36,10 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
         this._backgroundReaction && this._backgroundReaction();
     }
     render() {
+        let referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document);
+        let referenceLayout = referenceDoc.layout instanceof Doc ? referenceDoc.layout : referenceDoc;
         return <button className="fontIconBox-outerDiv" title={StrCast(this.props.Document.title)} ref={this._ref}
-            style={{ background: StrCast(this.props.Document.backgroundColor), boxShadow: this.props.Document.unchecked ? undefined : `4px 4px 12px black` }}>
+            style={{ background: StrCast(referenceLayout.backgroundColor), boxShadow: this.props.Document.unchecked ? undefined : `4px 4px 12px black` }}>
             <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} color={this._foregroundColor} size="sm" />
         </button>;
     }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ae67e94cc..f157a953e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -127,7 +127,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         let view = this._editorView!;
         if (view.state.selection.from === view.state.selection.to) return false;
         if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) {
-            this.props.Document.color = color;
+            this.layoutDoc.color = color;
         }
         let colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color });
         view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark));
@@ -143,7 +143,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
 
-    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); }
+
+    // the document containing the view layout information - will be the Document itself unless the Document has
+    // a layout field.  In that case, all layout information comes from there unless overriden by Document
+    @computed get layoutDoc(): Doc {
+        return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
+    }
 
     linkOnDeselect: Map<string, string> = new Map();
 
@@ -249,39 +255,41 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     @undoBatch
     @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
-        if (de.data instanceof DragManager.DocumentDragData && de.data.userDropAction) {
-            let target = de.data.droppedDocuments[0];
-            const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
-            target.fitToBox = true;
-            let node = schema.nodes.dashDoc.create({
-                width: target[WidthSym](), height: target[HeightSym](),
-                title: "dashDoc", docid: target[Id],
-                float: "right"
-            });
-            let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y });
-            link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, node));
-            this.tryUpdateHeight();
-            e.stopPropagation();
-        } else if (de.data instanceof DragManager.DocumentDragData) {
+        if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
-            if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) {
-                if (de.mods === "AltKey") {
-                    if (draggedDoc.data instanceof RichTextField) {
-                        Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data);
-                        e.stopPropagation();
-                    }
-                } else if (de.mods === "CtrlKey") {
-                    draggedDoc.isTemplate = true;
-                    if (typeof (draggedDoc.layout) === "string") {
-                        let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc);
-                        layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`);
-                        this.props.Document.layout = layoutDelegateToOverrideFieldKey;
-                    } else {
-                        this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
-                    }
+            // replace text contents whend dragging with Alt
+            if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "AltKey") {
+                if (draggedDoc.data instanceof RichTextField) {
+                    Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data);
+                    e.stopPropagation();
+                }
+                // apply as template when dragging with Meta
+            } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "MetaKey") {
+                draggedDoc.isTemplate = true;
+                let newLayout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
+                if (typeof (draggedDoc.layout) === "string") {
+                    newLayout = Doc.MakeDelegate(draggedDoc);
+                    newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`);
                 }
+                this.props.Document.layout = newLayout;
                 e.stopPropagation();
-            }
+                // embed document when dragging with a userDropAction or an embedDoc flag set
+            } else if (de.data.userDropAction || de.data.embedDoc) {
+                let target = de.data.droppedDocuments[0];
+                const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
+                if (link) {
+                    target.fitToBox = true;
+                    let node = schema.nodes.dashDoc.create({
+                        width: target[WidthSym](), height: target[HeightSym](),
+                        title: "dashDoc", docid: target[Id],
+                        float: "right"
+                    });
+                    let view = this._editorView!;
+                    view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
+                    this.tryUpdateHeight();
+                    e.stopPropagation();
+                }
+            } // otherwise, fall through to outer collection to handle drop
         }
     }
 
@@ -486,7 +494,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         );
 
         this._heightReactionDisposer = reaction(
-            () => [this.props.Document[WidthSym](), this.props.Document.autoHeight],
+            () => [this.layoutDoc[WidthSym](), this.layoutDoc.autoHeight],
             () => this.tryUpdateHeight()
         );
 
@@ -505,7 +513,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         this.setupEditor(this.config, this.dataDoc, this.props.fieldKey);
 
         this._searchReactionDisposer = reaction(() => {
-            return StrCast(this.props.Document.search_string);
+            return StrCast(this.layoutDoc.search_string);
         }, searchString => {
             if (searchString) {
                 this.highlightSearchTerms([searchString]);
@@ -518,7 +526,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
         this._rulesReactionDisposer = reaction(() => {
             let ruleProvider = this.props.ruleProvider;
-            let heading = NumCast(this.props.Document.heading);
+            let heading = NumCast(this.layoutDoc.heading);
             if (ruleProvider instanceof Doc) {
                 return {
                     align: StrCast(ruleProvider["ruleAlign_" + heading], ""),
@@ -530,7 +538,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         },
             action((rules: any) => {
                 this._fontFamily = rules ? rules.font : "Arial";
-                this._fontSize = rules ? rules.size : NumCast(this.props.Document.fontSize, 13);
+                this._fontSize = rules ? rules.size : NumCast(this.layoutDoc.fontSize, 13);
                 rules && setTimeout(() => {
                     const view = this._editorView!;
                     if (this._proseRef) {
@@ -547,7 +555,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }), { fireImmediately: true }
         );
         this._scrollToRegionReactionDisposer = reaction(
-            () => StrCast(this.props.Document.scrollToLinkID),
+            () => StrCast(this.layoutDoc.scrollToLinkID),
             async (scrollToLinkID) => {
                 let findLinkFrag = (frag: Fragment, editor: EditorView) => {
                     const nodes: Node[] = [];
@@ -585,7 +593,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                         setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
                         setTimeout(() => this.unhighlightSearchTerms(), 2000);
                     }
-                    this.props.Document.scrollToLinkID = undefined;
+                    this.layoutDoc.scrollToLinkID = undefined;
                 }
 
             },
@@ -885,7 +893,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                         });
                     }
                 } else {
-                    let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.props.Document.x, 0) + NumCast(this.props.Document.width, 0), y: NumCast(this.props.Document.y) });
+                    let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) });
                     this.props.addDocument && this.props.addDocument(webDoc);
                 }
                 e.stopPropagation();
@@ -983,19 +991,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     @action
     tryUpdateHeight() {
         let scrollHeight = this._ref.current ? this._ref.current.scrollHeight : 0;
-        if (!this.props.Document.isAnimating && this.props.Document.autoHeight && scrollHeight !== 0 &&
+        if (!this.layoutDoc.isAnimating && this.layoutDoc.autoHeight && scrollHeight !== 0 &&
             getComputedStyle(this._ref.current!.parentElement!).top === "0px") {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
             let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
-            let dh = NumCast(this.props.Document.height, 0);
-            this.props.Document.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
+            let dh = NumCast(this.layoutDoc.height, 0);
+            this.layoutDoc.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
             this.dataDoc.nativeHeight = nh ? scrollHeight : undefined;
         }
     }
 
     render() {
         let style = "hidden";
-        let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : "";
-        let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground
+        let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
+        let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground
             ? "none" : "all";
         Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
         if (this.props.isSelected()) {
@@ -1004,8 +1012,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         return (
             <div className={`formattedTextBox-cont-${style}`} ref={this._ref}
                 style={{
-                    overflowY: this.props.Document.autoHeight ? "hidden" : "auto",
-                    height: this.props.Document.autoHeight ? "max-content" : this.props.height ? this.props.height : undefined,
+                    overflowY: "auto",
+                    maxHeight: "100%",
+                    height: this.layoutDoc.autoHeight ? "max-content" : this.props.height ? this.props.height : undefined,
                     background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined,
                     opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
                     color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 9e0944c69..21d4d16f6 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -563,7 +563,12 @@ export namespace Doc {
 
     let _applyCount: number = 0;
     export function ApplyTemplate(templateDoc: Doc) {
-        return !templateDoc ? undefined : ApplyTemplateTo(templateDoc, Doc.MakeDelegate(new Doc()), templateDoc.title + "(..." + _applyCount++ + ")");
+        if (templateDoc) {
+            let applied = ApplyTemplateTo(templateDoc, Doc.MakeDelegate(new Doc()), templateDoc.title + "(..." + _applyCount++ + ")");
+            applied && (Doc.GetProto(applied).layout = applied.layout);
+            return applied;
+        }
+        return undefined;
     }
     export function ApplyTemplateTo(templateDoc: Doc, target: Doc, titleTarget: string | undefined = undefined) {
         if (!templateDoc) {
@@ -578,7 +583,7 @@ export namespace Doc {
         let layoutCustom = Doc.MakeTitled("layoutCustom");
         let layoutCustomLayout = Doc.MakeDelegate(templateDoc);
 
-        titleTarget && (target.title = titleTarget);
+        titleTarget && (Doc.GetProto(target).title = titleTarget);
         target.type = DocumentType.TEMPLATE;
         target.width = templateDoc.width;
         target.height = templateDoc.height;
@@ -734,7 +739,7 @@ export namespace Doc {
 Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; });
 Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); });
 Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
-Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return Doc.MakeCopy(doc, copyProto); });
+Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplate ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
 Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); });
 Scripting.addGlobal(function aliasDocs(field: any) { return new List<Doc>(field.map((d: any) => Doc.MakeAlias(d))); });
 Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
-- 
cgit v1.2.3-70-g09d2


From 3d910bae8771e67cabc1500c49b77d425fdf62e9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 19 Oct 2019 13:49:20 -0400
Subject: cleaned up a bunch layout vs doc issues related to templates

---
 src/client/views/DocumentDecorations.tsx           |  6 +-
 src/client/views/InkingControl.tsx                 |  2 +-
 src/client/views/TemplateMenu.tsx                  |  4 +-
 .../views/collections/CollectionStackingView.tsx   |  8 +--
 .../views/collections/CollectionTreeView.tsx       |  6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 66 +++++++++++-----------
 .../collections/collectionFreeForm/MarqueeView.tsx | 24 ++++----
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 14 ++---
 src/client/views/nodes/DocumentContentsView.tsx    |  6 +-
 src/client/views/nodes/DocumentView.tsx            | 11 ++--
 src/client/views/nodes/FontIconBox.tsx             |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  6 +-
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/new_fields/Doc.ts                              | 24 +++++---
 14 files changed, 86 insertions(+), 95 deletions(-)

(limited to 'src/client/views/nodes/FontIconBox.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8409a34da..07af4799b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -470,7 +470,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         SelectionManager.SelectedDocuments().forEach(element => {
             if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
                 let doc = PositionDocument(element.props.Document);
-                let layoutDoc = PositionDocument(element.props.Document.layout instanceof Doc ? element.props.Document.layout : element.props.Document);
+                let layoutDoc = PositionDocument(Doc.Layout(element.props.Document));
                 let nwidth = doc.nativeWidth || 0;
                 let nheight = doc.nativeHeight || 0;
                 let width = (layoutDoc.width || 0);
@@ -478,8 +478,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 let scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling();
                 let actualdW = Math.max(width + (dW * scale), 20);
                 let actualdH = Math.max(height + (dH * scale), 20);
-                layoutDoc.x = (layoutDoc.x || 0) + dX * (actualdW - width);
-                layoutDoc.y = (layoutDoc.y || 0) + dY * (actualdH - height);
+                doc.x = (doc.x || 0) + dX * (actualdW - width);
+                doc.y = (doc.y || 0) + dY * (actualdH - height);
                 let proto = doc.isTemplateField ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here...
                 let fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight);
                 if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) {
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index c3a617ffe..105adc03d 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -81,7 +81,7 @@ export class InkingControl {
                     ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined;
                     ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)));
                 }
-                (!ruleProvider && targetDoc) && (view.props.Document.backgroundColor = matchedColor);
+                (!ruleProvider && targetDoc) && (Doc.Layout(view.props.Document).backgroundColor = matchedColor);
 
                 return {
                     target: targetDoc,
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index da776f887..d76b033f0 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -117,13 +117,13 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @action
     toggleChrome = (): void => {
         this.props.docs.map(dv => {
-            let layout = dv.Document.layout instanceof Doc ? dv.Document.layout : dv.Document;
+            let layout = Doc.Layout(dv.Document);
             layout.chromeStatus = (layout.chromeStatus !== "disabled" ? "disabled" : "enabled");
         });
     }
 
     render() {
-        let layout = this.props.docs[0].Document.layout instanceof Doc ? this.props.docs[0].Document.layout : this.props.docs[0].Document;
+        let layout = Doc.Layout(this.props.docs[0].Document);
         let templateMenu: Array<JSX.Element> = [];
         this.props.templates.forEach((checked, template) =>
             templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index f9f040c6b..1a578f4fc 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -74,11 +74,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         this._heightMap.set(key, sectionHeight);
     }
 
-    get layoutDoc() {
-        // if this document's layout field contains a document (ie, a rendering template), then we will use that
-        // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string.
-        return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
-    }
+    get layoutDoc() { return Doc.Layout(this.props.Document); }
 
     get Sections() {
         if (!this.sectionFilter || this.sectionHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
@@ -189,7 +185,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     getDocHeight(d?: Doc) {
         if (!d) return 0;
-        let layoutDoc = d.layout instanceof Doc ? d.layout : d;
+        let layoutDoc = Doc.Layout(d);
         let nw = NumCast(d.nativeWidth);
         let nh = NumCast(d.nativeHeight);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 5d88e6290..2fbe8527e 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -251,7 +251,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
     docWidth = () => {
         let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth);
-        let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document;
+        let layoutDoc = Doc.Layout(this.props.document);
         if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20));
         return NumCast(this.props.document.nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20;
     }
@@ -259,7 +259,7 @@ class TreeView extends React.Component<TreeViewProps> {
         let bounds = this.boundsOfCollectionDocument;
         return Math.min(this.MAX_EMBED_HEIGHT, (() => {
             let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth);
-            let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document;
+            let layoutDoc = Doc.Layout(this.props.document);
             if (aspect) return this.docWidth() * aspect;
             if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x);
             return layoutDoc.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) :
@@ -467,7 +467,6 @@ class TreeView extends React.Component<TreeViewProps> {
             if (!pair.layout || pair.data instanceof Promise) {
                 return (null);
             }
-            const childLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout;
 
             let indent = i === 0 ? undefined : () => {
                 if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) {
@@ -483,6 +482,7 @@ class TreeView extends React.Component<TreeViewProps> {
             let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
                 return add(doc, relativeTo ? relativeTo : docs[i], before !== undefined ? before : false);
             };
+            const childLayout = Doc.Layout(pair.layout);
             let rowHeight = () => {
                 let aspect = NumCast(child.nativeWidth, 0) / NumCast(child.nativeHeight, 0);
                 return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 932b6722b..eff73b14e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -130,22 +130,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         if (super.drop(e, de)) {
             if (de.data instanceof DragManager.DocumentDragData) {
                 if (de.data.droppedDocuments.length) {
-                    let firstLayoutDoc = de.data.droppedDocuments[0].layout instanceof Doc ? de.data.droppedDocuments[0].layout : de.data.droppedDocuments[0];
-                    let z = NumCast(firstLayoutDoc.z);
+                    let firstDoc = de.data.droppedDocuments[0];
+                    let z = NumCast(firstDoc.z);
                     let x = (z ? xpo : xp) - de.data.offset[0];
                     let y = (z ? ypo : yp) - de.data.offset[1];
-                    let dropX = NumCast(firstLayoutDoc.x);
-                    let dropY = NumCast(firstLayoutDoc.y);
+                    let dropX = NumCast(firstDoc.x);
+                    let dropY = NumCast(firstDoc.y);
                     de.data.droppedDocuments.forEach(action((d: Doc) => {
-                        let layoutDoc = d.layout instanceof Doc ? d.layout : d;
-                        layoutDoc.x = x + NumCast(layoutDoc.x) - dropX;
-                        layoutDoc.y = y + NumCast(layoutDoc.y) - dropY;
+                        let layoutDoc = Doc.Layout(d);
+                        d.x = x + NumCast(d.x) - dropX;
+                        d.y = y + NumCast(d.y) - dropY;
                         if (!NumCast(layoutDoc.width)) {
                             layoutDoc.width = 300;
                         }
-                        if (!NumCast(d.height)) {
-                            let nw = NumCast(d.nativeWidth);
-                            let nh = NumCast(d.nativeHeight);
+                        if (!NumCast(layoutDoc.height)) {
+                            let nw = NumCast(layoutDoc.nativeWidth);
+                            let nh = NumCast(layoutDoc.nativeHeight);
                             layoutDoc.height = nw && nh ? nh / nw * NumCast(layoutDoc.width) : 300;
                         }
                         this.bringToFront(d);
@@ -157,13 +157,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             else if (de.data instanceof DragManager.AnnotationDragData) {
                 if (de.data.dropDocument) {
                     let dragDoc = de.data.dropDocument;
-                    let layoutDoc = dragDoc.layout instanceof Doc ? dragDoc.layout : dragDoc;
                     let x = xp - de.data.offset[0];
                     let y = yp - de.data.offset[1];
-                    let dropX = NumCast(layoutDoc.x);
-                    let dropY = NumCast(layoutDoc.y);
-                    layoutDoc.x = x + NumCast(layoutDoc.x) - dropX;
-                    layoutDoc.y = y + NumCast(layoutDoc.y) - dropY;
+                    let dropX = NumCast(dragDoc.x);
+                    let dropY = NumCast(dragDoc.y);
+                    dragDoc.x = x + NumCast(dragDoc.x) - dropX;
+                    dragDoc.y = y + NumCast(dragDoc.y) - dropY;
                     de.data.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation
                     this.bringToFront(dragDoc);
                 }
@@ -174,9 +173,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     pickCluster(probe: number[]) {
         return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => {
-            let layoutDoc = cd.layout instanceof Doc ? cd.layout : cd;
-            let cx = NumCast(layoutDoc.x) - this._clusterDistance;
-            let cy = NumCast(layoutDoc.y) - this._clusterDistance;
+            let layoutDoc = Doc.Layout(cd);
+            let cx = NumCast(cd.x) - this._clusterDistance;
+            let cy = NumCast(cd.y) - this._clusterDistance;
             let cw = NumCast(layoutDoc.width) + 2 * this._clusterDistance;
             let ch = NumCast(layoutDoc.height) + 2 * this._clusterDistance;
             return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ?
@@ -218,10 +217,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
             let preferredInd = NumCast(doc.cluster);
             doc.cluster = -1;
-            let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc;
             this._clusterSets.map((set, i) => set.map(member => {
-                let memberLayout = member.layout instanceof Doc ? member.layout : member;
-                if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(layoutDoc, memberLayout, this._clusterDistance)) {
+                if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
                     doc.cluster = i;
                 }
             }));
@@ -293,18 +290,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }
             let x = this.Document.panX || 0;
             let y = this.Document.panY || 0;
-            let docs = this.childLayoutPairs.map(pair => pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout);
+            let docs = this.childLayoutPairs.map(pair => pair.layout);
             let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
             if (!this.isAnnotationOverlay) {
                 let minx = docs.length ? NumCast(docs[0].x) : 0;
-                let maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
+                let maxx = docs.length ? NumCast(Doc.Layout(docs[0]).width) + minx : minx;
                 let miny = docs.length ? NumCast(docs[0].y) : 0;
-                let maxy = docs.length ? NumCast(docs[0].height) + miny : miny;
+                let maxy = docs.length ? NumCast(Doc.Layout(docs[0]).height) + miny : miny;
                 let ranges = docs.filter(doc => doc).reduce((range, doc) => {
+                    let layoutDoc = Doc.Layout(doc);
                     let x = NumCast(doc.x);
-                    let xe = x + NumCast(doc.width);
+                    let xe = x + NumCast(layoutDoc.width);
                     let y = NumCast(doc.y);
-                    let ye = y + NumCast(doc.height);
+                    let ye = y + NumCast(layoutDoc.height);
                     return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
                     [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
                 }, [[minx, maxx], [miny, maxy]]);
@@ -408,9 +406,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 this.props.Document.scrollY = NumCast(doc.y) - offset;
             }
         } else {
-            let layoutdoc = doc.layout instanceof Doc ? doc.layout : doc;
-            const newPanX = NumCast(layoutdoc.x) + NumCast(layoutdoc.width) / 2;
-            const newPanY = NumCast(layoutdoc.y) + NumCast(layoutdoc.height) / 2;
+            let layoutdoc = Doc.Layout(doc);
+            const newPanX = NumCast(doc.x) + NumCast(layoutdoc.width) / 2;
+            const newPanY = NumCast(doc.y) + NumCast(layoutdoc.height) / 2;
             const newState = HistoryUtil.getState();
             newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
             HistoryUtil.pushState(newState);
@@ -471,10 +469,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } {
         const script = this.Document.arrangeScript;
         const result = script && script.script.run(params, console.log);
+        const layoutDoc = Doc.Layout(params.doc);
         if (result && result.success) {
             return { ...result, transition: "transform 1s" };
         }
-        return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") };
+        return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(layoutDoc.width, "number"), height: Cast(layoutDoc.height, "number") };
     }
 
     viewDefsToJSX = (views: any[]) => {
@@ -530,8 +529,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         let elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
 
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
-            let finalLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout;
-            const pos = this.getCalculatedPositions({ doc: finalLayout, index: i, collection: this.Document, docs: layoutDocs, state });
+            const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
             state = pos.state === undefined ? state : pos.state;
             layoutPoolData.set(pair, pos);
         });
@@ -574,7 +572,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             let i = 0;
             const width = Math.max(...docs.map(doc => NumCast(doc.width)));
             const height = Math.max(...docs.map(doc => NumCast(doc.height)));
-            for (const doc of docs.map(d => d.layout instanceof Doc ? d.layout : d)) {
+            for (const doc of docs) {
                 doc.x = x;
                 doc.y = y;
                 x += width + 20;
@@ -592,7 +590,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // find rule colorations when rule providing is turned on by looking at each document to see if it has a coloring -- if so, use it's color as the rule for its associated heading.
         this.Document.isRuleProvider && this.childLayoutPairs.map(pair =>
             // iterate over the children of a displayed document (or if the displayed document is a template, iterate over the children of that template)
-            DocListCast(pair.layout.layout instanceof Doc ? pair.layout.layout.data : pair.layout.data).map(heading => {
+            DocListCast(Doc.Layout(pair.layout).data).map(heading => {
                 let headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading);
                 let headingLayout = headingPair.layout && (pair.layout.data_ext instanceof Doc) && (pair.layout.data_ext[`Layout[${headingPair.layout[Id]}]`] as Doc) || headingPair.layout;
                 if (headingLayout && NumCast(headingLayout.heading) > 0 && headingLayout.backgroundColor !== headingLayout.defaultBackgroundColor) {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 1362736cf..637168f1b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -281,7 +281,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             let bounds = this.Bounds;
             let selected = this.marqueeSelect(false);
             if (e.key === "c") {
-                selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => {
+                selected.map(d => {
                     this.props.removeDocument(d);
                     d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
                     d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
@@ -296,7 +296,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]);
             let usedPaletted = new Map<string, number>();
             [...this.props.activeDocuments(), this.props.container.props.Document].map(child => {
-                let bg = StrCast(child.layout instanceof Doc ? child.layout.backgroundColor : child.backgroundColor);
+                let bg = StrCast(Doc.Layout(child).backgroundColor);
                 if (palette.indexOf(bg) !== -1) {
                     palette.splice(palette.indexOf(bg), 1);
                     if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1);
@@ -325,7 +325,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             this.marqueeInkDelete(inkData);
 
             if (e.key === "s" || e.key === "S") {
-                selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => {
+                selected.map(d => {
                     this.props.removeDocument(d);
                     d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
                     d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
@@ -394,9 +394,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         let selRect = this.Bounds;
         let selection: Doc[] = [];
         this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => {
-            let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc;
-            var x = NumCast(layoutDoc.x);
-            var y = NumCast(layoutDoc.y);
+            let layoutDoc = Doc.Layout(doc);
+            var x = NumCast(doc.x);
+            var y = NumCast(doc.y);
             var w = NumCast(layoutDoc.width);
             var h = NumCast(layoutDoc.height);
             if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
@@ -405,9 +405,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         });
         if (!selection.length && selectBackgrounds) {
             this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => {
-                let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc;
-                var x = NumCast(layoutDoc.x);
-                var y = NumCast(layoutDoc.y);
+                let layoutDoc = Doc.Layout(doc);
+                var x = NumCast(doc.x);
+                var y = NumCast(doc.y);
                 var w = NumCast(layoutDoc.width);
                 var h = NumCast(layoutDoc.height);
                 if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
@@ -422,9 +422,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
             let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
             this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => {
-                let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc;
-                var x = NumCast(layoutDoc.x);
-                var y = NumCast(layoutDoc.y);
+                let layoutDoc = Doc.Layout(doc);
+                var x = NumCast(doc.x);
+                var y = NumCast(doc.y);
                 var w = NumCast(layoutDoc.width);
                 var h = NumCast(layoutDoc.height);
                 if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 8153923de..2243a44d5 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -34,13 +34,13 @@ export const PositionDocument = makeInterface(documentSchema, positionSchema);
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
     _disposer: IReactionDisposer | undefined = undefined;
     get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; }
-    get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : NumCast(this.layoutDoc.x); }
-    get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : NumCast(this.layoutDoc.y); }
+    get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
+    get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
     get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); }
     get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym](); }
     @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document, this.props.DataDoc) ? this.props.dataProvider(this.props.Document, this.props.DataDoc) : undefined; }
-    @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); }
-    @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); }
+    @computed get nativeWidth() { return NumCast(this.layoutDoc.nativeWidth); }
+    @computed get nativeHeight() { return NumCast(this.layoutDoc.nativeHeight); }
 
     @computed get renderScriptDim() {
         if (this.Document.renderScript) {
@@ -92,11 +92,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     clusterColorFunc = (doc: Doc) => this.clusterColor;
 
-    get layoutDoc() {
-        // if this document's layout field contains a document (ie, a rendering template), then we will use that
-        // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string.
-        return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
-    }
+    get layoutDoc() { return Doc.Layout(this.props.Document); }
 
     @observable _animPos: number[] | undefined = undefined;
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index d375405b9..bb9315ca3 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -77,11 +77,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         }
         return this.props.DataDoc;
     }
-    get layoutDoc() {
-        // if this document's layout field contains a document (ie, a rendering template), then we will use that
-        // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string.
-        return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
-    }
+    get layoutDoc() { return Doc.Layout(this.props.Document); }
 
     CreateBindings(): JsxBindings {
         let list = {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 26e2c0fa2..1087c57ad 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -144,8 +144,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     public get ContentDiv() { return this._mainCont.current; }
     @computed get active() { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
     @computed get topMost() { return this.props.renderDepth === 0; }
-    @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
-    @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
+    @computed get nativeWidth() { return this.layoutDoc.nativeWidth || 0; }
+    @computed get nativeHeight() { return this.layoutDoc.nativeHeight || 0; }
     @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; }
 
     @action
@@ -287,8 +287,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); }
 
-    @undoBatch
-    static makeNativeViewClicked = async (doc: Doc): Promise<void> => swapViews(doc, "layoutNative", "layoutCustom")
+    static makeNativeViewClicked = async (doc: Doc): Promise<void> => {
+        undoBatch(() => swapViews(doc, "layoutNative", "layoutCustom"))();
+    }
 
     static makeCustomViewClicked = async (doc: Doc, dataDoc: Opt<Doc>) => {
         const batch = UndoManager.StartBatch("CustomViewClicked");
@@ -580,7 +581,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     // the document containing the view layout information - will be the Document itself unless the Document has
     // a layout field.  In that case, all layout information comes from there unless overriden by Document
     get layoutDoc(): Document {
-        return Document(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document);
+        return Document(Doc.Layout(this.props.Document));
     }
 
     // does Document set a layout prop 
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index c5bf28d5b..fd6a475fb 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -37,7 +37,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
     }
     render() {
         let referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document);
-        let referenceLayout = referenceDoc.layout instanceof Doc ? referenceDoc.layout : referenceDoc;
+        let referenceLayout = Doc.Layout(referenceDoc);
         return <button className="fontIconBox-outerDiv" title={StrCast(this.props.Document.title)} ref={this._ref}
             style={{ background: StrCast(referenceLayout.backgroundColor), boxShadow: this.props.Document.unchecked ? undefined : `4px 4px 12px black` }}>
             <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} color={this._foregroundColor} size="sm" />
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 6f4ced7f3..ac10d729a 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -149,9 +149,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     // the document containing the view layout information - will be the Document itself unless the Document has
     // a layout field.  In that case, all layout information comes from there unless overriden by Document
-    @computed get layoutDoc(): Doc {
-        return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
-    }
+    @computed get layoutDoc(): Doc { return Doc.Layout(this.props.Document); }
 
     linkOnDeselect: Map<string, string> = new Map();
 
@@ -268,7 +266,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 // apply as template when dragging with Meta
             } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "MetaKey") {
                 draggedDoc.isTemplateDoc = true;
-                let newLayout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
+                let newLayout = Doc.Layout(draggedDoc);
                 if (typeof (draggedDoc.layout) === "string") {
                     newLayout = Doc.MakeDelegate(draggedDoc);
                     newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`);
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 3a9318469..76cf60188 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -68,7 +68,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
 
     public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript): boolean {
         const { script, type, onDelegate } = kvpScript;
-        //const target = onDelegate ? (doc.layout instanceof Doc ? doc.layout : doc) : Doc.GetProto(doc); // bcz: need to be able to set fields on layout templates
+        //const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
         const target = onDelegate ? doc : Doc.GetProto(doc);
         let field: Field;
         if (type === "computed") {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 69c048ebf..31173a5c1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -624,7 +624,7 @@ export namespace Doc {
         setTimeout(action(() => {
             !templateDataDoc[metadataFieldName] && data instanceof ObjectField && (Doc.GetProto(templateDataDoc)[metadataFieldName] = ObjectField.MakeCopy(data));
             let layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metadataFieldName}"}`);
-            let layoutDelegate = fieldTemplate.layout instanceof Doc ? fieldLayoutDoc : fieldTemplate;
+            let layoutDelegate = Doc.Layout(fieldTemplate);
             layoutDelegate.layout = layout;
             fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout;
             if (fieldTemplate.backgroundColor !== templateDataDoc.defaultBackgroundColor) fieldTemplate.defaultBackgroundColor = fieldTemplate.backgroundColor;
@@ -633,16 +633,18 @@ export namespace Doc {
         return true;
     }
 
-    export function overlapping(doc: Doc, doc2: Doc, clusterDistance: number) {
+    export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
+        let doc2Layout = Doc.Layout(doc2);
+        let doc1Layout = Doc.Layout(doc1);
         var x2 = NumCast(doc2.x) - clusterDistance;
         var y2 = NumCast(doc2.y) - clusterDistance;
-        var w2 = NumCast(doc2.width) + clusterDistance;
-        var h2 = NumCast(doc2.height) + clusterDistance;
-        var x = NumCast(doc.x) - clusterDistance;
-        var y = NumCast(doc.y) - clusterDistance;
-        var w = NumCast(doc.width) + clusterDistance;
-        var h = NumCast(doc.height) + clusterDistance;
-        return doc.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
+        var w2 = NumCast(doc2Layout.width) + clusterDistance;
+        var h2 = NumCast(doc2Layout.height) + clusterDistance;
+        var x = NumCast(doc1.x) - clusterDistance;
+        var y = NumCast(doc1.y) - clusterDistance;
+        var w = NumCast(doc1Layout.width) + clusterDistance;
+        var h = NumCast(doc1Layout.height) + clusterDistance;
+        return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
     }
 
     export function isBrushedHighlightedDegree(doc: Doc) {
@@ -663,6 +665,10 @@ export namespace Doc {
         @observable _user_doc: Doc = undefined!;
         @observable BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
     }
+
+    // if this document's layout field contains a document (ie, a rendering template), then we will use that
+    // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string.
+    export function Layout(doc: Doc) { return doc.layout instanceof Doc ? doc.layout : doc; }
     const manager = new DocData();
     export function UserDoc(): Doc { return manager._user_doc; }
     export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
-- 
cgit v1.2.3-70-g09d2

