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/views/DocumentDecorations.tsx | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/client/views/DocumentDecorations.tsx')

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);
-- 
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/DocumentDecorations.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 7763a08eb5ed931dbf854e2b72d07b7613791e2b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 16 Oct 2019 21:27:25 -0400
Subject: some fixes to linearView for handling templates.  fixed DOCUMENTS on
 library view.   renaming a bunch of template stuff... still in progress.

---
 src/client/documents/Documents.ts                      |  1 +
 src/client/views/CollectionLinearView.tsx              | 18 ++++++++++++++----
 src/client/views/DocComponent.tsx                      |  2 +-
 src/client/views/DocumentDecorations.tsx               |  4 ++--
 src/client/views/InkingControl.tsx                     |  7 ++-----
 src/client/views/TemplateMenu.tsx                      |  2 +-
 src/client/views/collections/CollectionBaseView.tsx    | 10 +++++-----
 .../views/collections/CollectionStackingView.tsx       |  4 +++-
 src/client/views/collections/CollectionSubView.tsx     | 11 ++++++++---
 src/client/views/collections/CollectionTreeView.tsx    |  3 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx      |  2 +-
 src/client/views/nodes/ButtonBox.tsx                   |  6 +++++-
 src/client/views/nodes/DocumentView.tsx                |  6 +++---
 src/client/views/nodes/FormattedTextBox.tsx            |  2 +-
 src/client/views/nodes/VideoBox.tsx                    |  2 +-
 src/new_fields/Doc.ts                                  |  6 +++---
 src/server/authentication/models/current_user_utils.ts | 16 ++++++++--------
 17 files changed, 61 insertions(+), 41 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ccb08f4cd..2ffbc8394 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -70,6 +70,7 @@ export interface DocumentOptions {
     layout?: string | Doc;
     hideHeadings?: boolean; // whether stacking view column headings should be hidden
     isTemplate?: boolean;
+    isTemplateDoc?: boolean;
     templates?: List<string>;
     viewType?: number;
     backgroundColor?: string;
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 44d9b042e..3e2ab1459 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -1,7 +1,7 @@
 import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, HeightSym, WidthSym } from '../../new_fields/Doc';
+import { Doc, HeightSym, WidthSym, DocListCast } from '../../new_fields/Doc';
 import { ObjectField } from '../../new_fields/ObjectField';
 import { makeInterface } from '../../new_fields/Schema';
 import { ScriptField } from '../../new_fields/ScriptField';
@@ -51,9 +51,19 @@ 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) {
-                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" });
+                let template = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
+                if (template.type === DocumentType.COL) {
+                    let layout = StrCast(template.layout).match(/fieldKey={"[^"]*"}/)![0];
+                    let fieldKey = layout.replace('fieldKey={"', "").replace(/"}$/, "");
+                    let docs = DocListCast(template[fieldKey]);
+                    docs.map(d => {
+                        Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(template));
+                    });
+                    template.isTemplateDoc = true;
+                } else {
+                    template.isTemplateDoc = (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.isTemplateDoc ? "font" : "bolt" });
                 dbox.dragFactory = template;
                 dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
                 dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index fbc27192c..b6b717be0 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -68,7 +68,7 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
             return index !== -1 && value.splice(index, 1) ? true : false;
         }
 
-        @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; }
+        @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; }
 
         @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2f40ea746..6e8ba2d3d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -380,7 +380,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`);
             usingRule = usingRule || (ruleProvider && heading ? true : false);
         });
-        !usingRule && SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)).
+        !usingRule && SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplateField ? dv.props.Document : Doc.GetProto(dv.props.Document)).
             map(d => d.borderRounding = `${Math.min(100, dist)}%`);
         e.stopPropagation();
         e.preventDefault();
@@ -481,7 +481,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 let actualdH = Math.max(height + (dH * scale), 20);
                 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 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) {
                     layoutDoc.ignoreAspect = false;
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 46c6fae1c..bc5249acd 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -43,7 +43,7 @@ export class InkingControl {
             let oldColors = selected.map(view => {
                 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);
+                        view.props.Document.isTemplateField ? 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;
@@ -81,10 +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)));
                 }
-                if (!ruleProvider) {
-                    if (targetDoc)
-                        targetDoc.backgroundColor = matchedColor;
-                }
+                (!ruleProvider && targetDoc) && (targetDoc.backgroundColor = matchedColor);
 
                 return {
                     target: targetDoc,
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 9e5e62e03..da776f887 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -100,7 +100,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     clearTemplates = (event: React.MouseEvent) => {
         Templates.TemplateList.forEach(template => this.props.docs.forEach(d => d.Document["show" + template.Name] = undefined));
         ["backgroundColor", "borderRounding", "width", "height"].forEach(field => this.props.docs.forEach(d => {
-            if (d.Document.isTemplate && d.props.DataDoc) {
+            if (d.Document.isTemplateDoc && d.props.DataDoc) {
                 d.Document[field] = undefined;
             } else if (d.Document["default" + field[0].toUpperCase() + field.slice(1)] !== undefined) {
                 d.Document[field] = Doc.GetProto(d.Document)[field] = undefined;
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 7798964ea..15853fcae 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -84,7 +84,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         }
     }
 
-    @computed get dataDoc() { return Doc.fieldExtensionDoc(this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
+    @computed get dataDoc() { return Doc.fieldExtensionDoc(this.props.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
     @computed get dataField() { return this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; }
 
     active = (): boolean => {
@@ -106,8 +106,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer
             Doc.GetProto(doc).annotationOn = this.props.Document;
         }
-        let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document;
-        let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey;
+        let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplateField ? this.extensionDoc : this.props.Document;
+        let targetField = (this.props.fieldExt || this.props.Document.isTemplateField) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey;
         Doc.AddDocToList(targetDataDoc, targetField, doc);
         Doc.GetProto(doc).lastOpened = new DateField;
         return true;
@@ -118,8 +118,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
         docView && SelectionManager.DeselectDoc(docView);
         //TODO This won't create the field if it doesn't already exist
-        let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document;
-        let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey;
+        let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplateField ? this.extensionDoc : this.props.Document;
+        let targetField = (this.props.fieldExt || this.props.Document.isTemplateField) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey;
         let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
         let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
         index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index cde1a5036..e54374ad7 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -110,6 +110,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     componentDidMount() {
+        super.componentDidMount();
         this._heightDisposer = reaction(() => {
             if (this.props.Document.autoHeight) {
                 let sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]);
@@ -137,6 +138,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         );
     }
     componentWillUnmount() {
+        super.componentWillUnmount();
         this._heightDisposer && this._heightDisposer();
         this._sectionFilterDisposer && this._sectionFilterDisposer();
     }
@@ -167,7 +169,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             renderDepth={this.props.renderDepth}
             ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
             fitToBox={this.props.fitToBox}
-            onClick={layoutDoc.isTemplate ? this.onClickHandler : this.onChildClickHandler}
+            onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
             PanelWidth={width}
             PanelHeight={height}
             getTransform={finalDxf}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 9919a9dc3..46fbb5910 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -57,8 +57,13 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
         componentDidMount() {
             this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)],
-                async (args) => args[1] instanceof Doc &&
-                    this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc))));
+                async (args) => {
+                    if (args[1] instanceof Doc) {
+                        this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc)));
+                    } else {
+                        this.childDocs.map(async doc => doc.layout = undefined);
+                    }
+                });
 
         }
         componentWillUnmount() {
@@ -70,7 +75,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         // to its children which may be templates.
         // The name of the data field comes from fieldExt if it's an extension, or fieldKey otherwise.
         @computed get dataField() {
-            return Doc.fieldExtensionDoc(this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt)[this.props.fieldExt || this.props.fieldKey];
+            return Doc.fieldExtensionDoc(this.props.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt)[this.props.fieldExt || this.props.fieldKey];
         }
 
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 403da0e54..0e9c38fb4 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -520,7 +520,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     private treedropDisposer?: DragManager.DragDropDisposer;
     private _mainEle?: HTMLDivElement;
 
-    @computed get resolvedDataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
+    @computed get resolvedDataDoc() { return BoolCast(this.props.Document.isTemplateField) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
 
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this.treedropDisposer && this.treedropDisposer();
@@ -530,6 +530,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     }
 
     componentWillUnmount() {
+        super.componentWillUnmount();
         this.treedropDisposer && this.treedropDisposer();
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 229e7fffc..2b0ef8ada 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -627,7 +627,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     onContextMenu = (e: React.MouseEvent) => {
         let layoutItems: ContextMenuProps[] = [];
 
-        if (this.childDocs.some(d => BoolCast(d.isTemplate))) {
+        if (this.childDocs.some(d => BoolCast(d.isTemplateDoc))) {
             layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
         }
         layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index 3cf8c3eb3..b4d33fb0f 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -32,7 +32,11 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
     public static LayoutString() { return FieldView.LayoutString(ButtonBox); }
     private dropDisposer?: DragManager.DragDropDisposer;
 
-    @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+    @computed get dataDoc() {
+        return this.props.DataDoc &&
+            (BoolCast(this.props.Document.isTemplateField) || BoolCast(this.props.DataDoc.isTemplateField) ||
+                this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document);
+    }
 
 
     protected createDropTarget = (ele: HTMLDivElement) => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a071f782a..a7e78a7e8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -110,7 +110,7 @@ export const documentSchema = createSchema({
     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
+    isTemplateField: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed
     isBackground: "boolean",    // whether document is a background element and ignores input events (can only selet with marquee)
     type: "string",             // enumerated type of document
     maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) 
@@ -196,7 +196,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 SelectionManager.DeselectAll();
                 Doc.UnBrushDoc(this.props.Document);
             } else if (this.onClickHandler && this.onClickHandler.script) {
-                this.onClickHandler.script.run({ this: this.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
+                this.onClickHandler.script.run({ this: this.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
             } else if (this.props.Document.type === DocumentType.BUTTON) {
                 ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY);
             } else if (this.Document.isButton) {
@@ -376,7 +376,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     @action
     freezeNativeDimensions = (): void => {
-        let proto = this.Document.isTemplate ? this.props.Document : Doc.GetProto(this.props.Document);
+        let proto = this.Document.isTemplateDoc ? this.props.Document : Doc.GetProto(this.props.Document);
         proto.autoHeight = this.Document.autoHeight = false;
         proto.ignoreAspect = !proto.ignoreAspect;
         if (!proto.ignoreAspect && !proto.nativeWidth) {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f157a953e..ea82b1161 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -993,7 +993,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         let scrollHeight = this._ref.current ? this._ref.current.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 nh = this.props.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 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;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index aa9b28118..19968e6e1 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -323,7 +323,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
         this._isResetClick < 10 && (this.props.Document.currentTimecode = 0);
     }
     @computed get fieldExtensionDoc() { 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.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
 
     @computed get youtubeContent() {
         this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 21d4d16f6..51fcb818a 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -453,7 +453,7 @@ export namespace Doc {
     // for individual layout properties to be overridden in the expanded layout.
     //
     export function WillExpandTemplateLayout(layoutDoc: Doc, dataDoc?: Doc) {
-        return BoolCast(layoutDoc.isTemplate) && dataDoc && layoutDoc !== dataDoc && !(layoutDoc.layout instanceof Doc);
+        return BoolCast(layoutDoc.isTemplateField) && dataDoc && layoutDoc !== dataDoc && !(layoutDoc.layout instanceof Doc);
     }
 
     //
@@ -490,7 +490,7 @@ export namespace Doc {
 
     export function GetLayoutDataDocPair(doc: Doc, dataDoc: Doc | undefined, fieldKey: string, childDocLayout: Doc) {
         let layoutDoc: Doc | undefined = childDocLayout;
-        let resolvedDataDoc = !doc.isTemplate && dataDoc !== doc && dataDoc ? Doc.GetDataDoc(dataDoc) : undefined;
+        let resolvedDataDoc = !doc.isTemplateField && dataDoc !== doc && dataDoc ? Doc.GetDataDoc(dataDoc) : undefined;
         if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
             Doc.UpdateDocumentExtensionForField(resolvedDataDoc, fieldKey);
             let fieldExtensionDoc = Doc.fieldExtensionDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)), "dummy");
@@ -739,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.isTemplate ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
+Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? 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); });
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index ef977c89a..5ce707011 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -36,11 +36,11 @@ export class CurrentUserUtils {
     // a default set of note types .. not being used yet...
     static setupNoteTypes(doc: Doc) {
         return [
-            Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplate: true }),
-            Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplate: true }),
-            Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplate: true }),
-            Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplate: true }),
-            Docs.Create.TextDocument({ title: "Todo", backgroundColor: "orange", isTemplate: true })
+            Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplateDoc: true }),
+            Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplateDoc: true }),
+            Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplateDoc: true }),
+            Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplateDoc: true }),
+            Docs.Create.TextDocument({ title: "Todo", backgroundColor: "orange", isTemplateDoc: true })
         ];
     }
 
@@ -98,17 +98,17 @@ export class CurrentUserUtils {
         });
 
         doc.documents = Docs.Create.TreeDocument([], {
-            title: "DOCUMENTS", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0", backgroundColor: "#eeeeee", preventTreeViewOpen: true, forceActive: true, lockedPosition: true
+            title: "DOCUMENTS", height: 42, forceActive: true, boxShadow: "0 0", preventTreeViewOpen: true, lockedPosition: true, backgroundColor: "#eeeeee"
         });
 
         // setup Recently Closed library item
         doc.recentlyClosed = Docs.Create.TreeDocument([], {
-            title: "Recently Closed".toUpperCase(), height: 75, boxShadow: "0 0", preventTreeViewOpen: true, forceActive: true, lockedPosition: true, backgroundColor: "#eeeeee"
+            title: "RECENTLY CLOSED", height: 75, forceActive: true, boxShadow: "0 0", preventTreeViewOpen: true, lockedPosition: true, backgroundColor: "#eeeeee"
         });
 
         return Docs.Create.ButtonDocument({
             width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Library",
-            panel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc, doc.recentlyClosed as Doc], {
+            panel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc.documents as Doc, doc.recentlyClosed as Doc], {
                 title: "Library", xMargin: 5, yMargin: 5, gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true
             }),
             targetContainer: sidebarContainer,
-- 
cgit v1.2.3-70-g09d2


From dcdefb2a5a80d3c4d5451d6c7fc7213565d5ea5f Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 17 Oct 2019 16:02:37 -0400
Subject: initial working version of draggable link anchors.

---
 src/Utils.ts                             |  23 +++++++
 src/client/util/DragManager.ts           |   4 +-
 src/client/views/DocumentButtonBar.tsx   |  10 +--
 src/client/views/DocumentDecorations.tsx |   3 +-
 src/client/views/InkingControl.tsx       |  12 ++--
 src/client/views/nodes/DocuLinkView.tsx  |  85 +++++++++++++++++++++++
 src/client/views/nodes/DocumentView.tsx  | 114 +++++++------------------------
 7 files changed, 145 insertions(+), 106 deletions(-)
 create mode 100644 src/client/views/nodes/DocuLinkView.tsx

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 6889936b8..66ed8be5d 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -149,6 +149,29 @@ export namespace Utils {
     }
 
 
+    export function clamp(n: number, lower: number, upper: number) {
+        return Math.max(lower, Math.min(upper, n));
+    }
+
+    export function getNearestPointInPerimeter(l: number, t: number, w: number, h: number, x: number, y: number) {
+        var r = l + w,
+            b = t + h;
+
+        var x = clamp(x, l, r),
+            y = clamp(y, t, b);
+
+        var dl = Math.abs(x - l),
+            dr = Math.abs(x - r),
+            dt = Math.abs(y - t),
+            db = Math.abs(y - b);
+
+        var m = Math.min(dl, dr, dt, db);
+
+        return (m === dt) ? [x, t] :
+            (m === db) ? [x, b] :
+                (m === dl) ? [l, y] : [r, y];
+    }
+
     export function GetClipboardText(): string {
         var textArea = document.createElement("textarea");
         document.body.appendChild(textArea);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 06dab024e..576b16bc8 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -87,12 +87,12 @@ export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: num
     }
 }
 
-export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc) {
+export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc, singleLink?: Doc) {
     let srcTarg = sourceDoc.proto;
     let draggedDocs: Doc[] = [];
 
     if (srcTarg) {
-        let linkDocs = LinkManager.Instance.getAllRelatedLinks(srcTarg);
+        let linkDocs = singleLink ? [singleLink] : LinkManager.Instance.getAllRelatedLinks(srcTarg);
         if (linkDocs) {
             draggedDocs = linkDocs.map(link => {
                 let opp = LinkManager.Instance.getOppositeAnchor(link, sourceDoc);
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 959b120ed..ba87ecfb4 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -47,7 +47,6 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
     private _aliasButton = React.createRef<HTMLDivElement>();
     private _tooltipoff = React.createRef<HTMLDivElement>();
     private _textDoc?: Doc;
-    private _linkDrag?: UndoManager.Batch;
     public static Instance: DocumentButtonBar;
 
     constructor(props: { views: DocumentView[] }) {
@@ -139,15 +138,10 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
             let selDoc = this.props.views[0];
             let container = selDoc.props.ContainingCollectionDoc ? selDoc.props.ContainingCollectionDoc.proto : undefined;
             let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []);
-            this._linkDrag = UndoManager.StartBatch("Drag Link");
+            let _linkDrag = UndoManager.StartBatch("Drag Link");
             DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
                 handlers: {
-                    dragComplete: () => {
-                        if (this._linkDrag) {
-                            this._linkDrag.end();
-                            this._linkDrag = undefined;
-                        }
-                    },
+                    dragComplete: () => _linkDrag && _linkDrag.end()
                 },
                 hideSource: false
             });
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6e8ba2d3d..927729487 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -167,7 +167,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     get Bounds(): { x: number, y: number, b: number, r: number } {
         let x = this._forceUpdate;
         this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
-            if (documentView.props.renderDepth === 0 ||
+            if (documentView._selectedLink !== -1 ||
+                documentView.props.renderDepth === 0 ||
                 Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) {
                 return bounds;
             }
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index b8f3c2875..1aacdc8ec 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -81,18 +81,18 @@ 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) && (targetDoc.backgroundColor = matchedColor);
+                (!ruleProvider && targetDoc) && (view.setBackround(matchedColor));
 
                 return {
                     target: targetDoc,
                     previous: oldColor
                 };
             });
-            let captured = this._selectedColor;
-            UndoManager.AddEvent({
-                undo: () => oldColors.forEach(pair => pair.target.backgroundColor = pair.previous),
-                redo: () => oldColors.forEach(pair => pair.target.backgroundColor = captured)
-            });
+            //let captured = this._selectedColor;
+            // UndoManager.AddEvent({
+            //     undo: () => oldColors.forEach(pair => pair.target.backgroundColor = pair.previous),
+            //     redo: () => oldColors.forEach(pair => pair.target.backgroundColor = captured)
+            // });
         } else {
             CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.backgroundColor = this._selectedColor);
         }
diff --git a/src/client/views/nodes/DocuLinkView.tsx b/src/client/views/nodes/DocuLinkView.tsx
new file mode 100644
index 000000000..622d41047
--- /dev/null
+++ b/src/client/views/nodes/DocuLinkView.tsx
@@ -0,0 +1,85 @@
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, Opt } from "../../../new_fields/Doc";
+import { NumCast, StrCast } from "../../../new_fields/Types";
+import { Utils } from '../../../Utils';
+import { DocumentManager } from "../../util/DocumentManager";
+import "./DocumentView.scss";
+import React = require("react");
+import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
+import { UndoManager } from "../../util/UndoManager";
+
+
+interface DocuLinkViewProps {
+    Document: Doc;
+    isSelected: () => boolean;
+    addDocTab: (doc: Doc, dataDoc: Opt<Doc>, where: string) => void;
+    anchor: string;
+    otherAnchor: string;
+    scale: () => number;
+    contentDiv: HTMLDivElement | null;
+    link: Doc;
+    index: number;
+    selectedLink: () => number;
+    selectLink: (id: number) => void;
+    blacklist: Opt<Doc>
+}
+
+@observer
+export class DocuLinkView extends React.Component<DocuLinkViewProps> {
+    _downx = 0;
+    _downy = 0;
+    @observable _x = 0;
+    @observable _y = 0;
+    @observable _selected = false;
+    _ref = React.createRef<HTMLDivElement>();
+
+    onPointerDown = (e: React.PointerEvent) => {
+        this._downx = e.clientX;
+        this._downy = e.clientY;
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        document.addEventListener("pointermove", this.onPointerMove);
+        document.addEventListener("pointerup", this.onPointerUp);
+        e.stopPropagation();
+    }
+    onPointerMove = action((e: PointerEvent) => {
+        if (this.props.contentDiv && (Math.abs(e.clientX - this._downx) > 5 || Math.abs(e.clientY - this._downy) > 5)) {
+            let bounds = this.props.contentDiv.getBoundingClientRect();
+            let pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
+            let separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
+            let dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy))
+            if (separation > 100) {
+                DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], this.props.Document, this.props.link);
+                document.removeEventListener("pointermove", this.onPointerMove);
+                document.removeEventListener("pointerup", this.onPointerUp);
+            } else if (dragdist > separation) {
+                this.props.link[this.props.anchor + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
+                this.props.link[this.props.anchor + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
+            }
+        }
+    })
+    onPointerUp = (e: PointerEvent) => {
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button === 2 || e.ctrlKey)) {
+            this.props.selectLink(this.props.selectedLink() === this.props.index ? -1 : this.props.index);
+        }
+    }
+    onClick = (e: React.MouseEvent) => {
+        if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey)) {
+            DocumentManager.Instance.FollowLink(this.props.link, this.props.link[this.props.anchor] as Doc, document => this.props.addDocTab(document, undefined, "inTab"), false);
+        }
+        e.stopPropagation();
+    }
+    render() {
+        let y = NumCast(this.props.link[this.props.anchor + "_y"], 100);
+        let x = NumCast(this.props.link[this.props.anchor + "_x"], 100);
+        let c = StrCast(this.props.link[this.props.anchor + "_background"], "lightblue");
+        return <div onPointerDown={this.onPointerDown} onClick={this.onClick} title={StrCast((this.props.link[this.props.otherAnchor]! as Doc).title)} ref={this._ref} style={{
+            cursor: "default", position: "absolute", background: c, width: "25px", height: "25px", borderRadius: "20px", textAlign: "center", left: `calc(${x}% - 12.5px)`, top: `calc(${y}% - 12.5px)`,
+            transform: `scale(${1 / this.props.scale()})`,
+            border: this.props.selectedLink() === this.props.index && this.props.isSelected() ? "solid 3px black" : undefined
+        }} />
+    }
+}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c217c9d86..d8a1841b1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -40,6 +40,7 @@ import SharingManager from '../../util/SharingManager';
 import { Scripting } from '../../util/Scripting';
 import { DictationOverlay } from '../DictationOverlay';
 import { CollectionViewType } from '../collections/CollectionBaseView';
+import { DocuLinkView } from './DocuLinkView';
 
 library.add(fa.faEdit);
 library.add(fa.faTrash);
@@ -241,6 +242,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.nativeEvent.cancelBubble && e.button === 0) return;
+        runInAction(() => this._selectedLink = -1);
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._hitTemplateDrag = false;
@@ -626,6 +628,25 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             layoutKey="layout"
             DataDoc={this.props.DataDoc} />);
     }
+    linkEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "anchor1" : "anchor2";
+    linkOtherEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "anchor2" : "anchor1";
+    public setBackround(color: string) {
+        let selLink = this._selectedLink !== -1 && DocListCast(this.Document.links)[this._selectedLink];
+        if (selLink) {
+            let both = selLink["anchor1_background"] === selLink["anchor2_background"];
+            selLink[this.linkEndpoint(selLink) + "_background"] = color;
+            both && (selLink[this.linkOtherEndpoint(selLink) + "_background"] = color);
+        } else {
+            this.Document.backgroundColor = color;
+        }
+    }
+    @observable _selectedLink = -1;
+    selectLink = action((which: number) => {
+        SelectionManager.SelectDoc(this, false);
+        this._selectedLink = which;
+    })
+    selectedLink = () => this._selectedLink;
+
     render() {
         if (!this.props.Document) return (null);
         let animDims = this.props.Document.animateToDimensions ? Array.from(Cast(this.props.Document.animateToDimensions, listSpec("number"))!) : undefined;
@@ -696,7 +717,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
                 onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)}
             >
-                {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) => <DocuLink view={this} link={d} index={i} />)}
+                {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) =>
+                    <DocuLinkView Document={this.props.Document} blacklist={this.props.ContainingCollectionDoc} anchor={this.linkEndpoint(d)} otherAnchor={this.linkOtherEndpoint(d)} addDocTab={this.props.addDocTab} contentDiv={this.ContentDiv}
+                        scale={this.props.ContentScaling}
+                        isSelected={this.isSelected} link={d} index={i} selectLink={this.selectLink} selectedLink={this.selectedLink} />)}
                 {!showTitle && !showCaption ?
                     this.Document.searchFields ?
                         (<div className="documentView-searchWrapper">
@@ -720,94 +744,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 }
 
-
-interface DocuLinkProps {
-    view: DocumentView;
-    link: Doc;
-    index: number;
-}
-
-@observer
-export class DocuLink extends React.Component<DocuLinkProps> {
-    _downx = 0;
-    _downy = 0;
-    @observable _x = 0;
-    @observable _y = 0;
-
-    constructor(props: DocuLinkProps) {
-        super(props);
-    }
-
-    clamp(n: number, lower: number, upper: number) {
-        return Math.max(lower, Math.min(upper, n));
-    }
-
-    getNearestPointInPerimeter(l: number, t: number, w: number, h: number, x: number, y: number) {
-        var r = l + w,
-            b = t + h;
-
-        var x = this.clamp(x, l, r),
-            y = this.clamp(y, t, b);
-
-        var dl = Math.abs(x - l),
-            dr = Math.abs(x - r),
-            dt = Math.abs(y - t),
-            db = Math.abs(y - b);
-
-        var m = Math.min(dl, dr, dt, db);
-
-        return (m === dt) ? [x, t] :
-            (m === db) ? [x, b] :
-                (m === dl) ? [l, y] : [r, y];
-    }
-
-    get linkEndpoint() {
-        return Doc.AreProtosEqual(this.props.view.props.Document, Cast(this.props.link.anchor1, Doc) as Doc) ?
-            "anchor1" : "anchor2";
-    }
-    get linkOtherEndpoint() {
-        return !Doc.AreProtosEqual(this.props.view.props.Document, Cast(this.props.link.anchor1, Doc) as Doc) ?
-            "anchor1" : "anchor2";
-    }
-
-    onPointerDown = (e: React.PointerEvent) => {
-        this._downx = e.clientX;
-        this._downy = e.clientY;
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        document.addEventListener("pointermove", this.onPointerMove);
-        document.addEventListener("pointerup", this.onPointerUp);
-        e.stopPropagation();
-    }
-    onPointerMove = action((e: PointerEvent) => {
-        if (this.props.view.ContentDiv && (Math.abs(e.clientX - this._downx) > 3 || Math.abs(e.clientY - this._downy) > 3)) {
-            let bounds = this.props.view.ContentDiv.getBoundingClientRect();
-            let pt = this.getNearestPointInPerimeter(bounds.left - 25, bounds.top - 25, bounds.width, bounds.height, e.clientX, e.clientY);
-            this.props.link[this.linkEndpoint + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
-            this.props.link[this.linkEndpoint + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
-        }
-    })
-    onPointerUp = (e: PointerEvent) => {
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-    }
-    onClick = (e: React.MouseEvent) => {
-        if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3) {
-            DocumentManager.Instance.FollowLink(this.props.link, this.props.view.props.Document, document => this.props.view.props.addDocTab(document, undefined, "inTab"), false);
-        }
-        e.stopPropagation();
-    }
-    render() {
-        return <div onPointerDown={this.onPointerDown} title={StrCast((this.props.link[this.linkOtherEndpoint]! as Doc).title)} style={{
-            position: "absolute", background: "lightblue", width: "25px", height: "25px", borderRadius: "20px",
-            left: `${NumCast(this.props.link[this.linkEndpoint + "_x"], 100)}%`, top: `${NumCast(this.props.link[this.linkEndpoint + "_y"], 100)}%`
-        }} >
-            {this.props.index}
-        </div>
-    }
-}
-
-
 export async function swapViews(doc: Doc, newLayoutField: string, oldLayoutField: string, oldLayout?: Doc) {
     let oldLayoutExt = oldLayout || await Cast(doc[oldLayoutField], Doc);
     if (oldLayoutExt) {
-- 
cgit v1.2.3-70-g09d2


From fca8d503610f799ca0e4afcec114075456d411e0 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 17 Oct 2019 16:48:39 -0400
Subject: switched links over to be a document

---
 src/Utils.ts                                    |  2 +
 src/client/documents/DocumentTypes.ts           |  1 +
 src/client/documents/Documents.ts               |  5 ++
 src/client/views/DocumentDecorations.tsx        |  3 +-
 src/client/views/InkingControl.tsx              |  2 +-
 src/client/views/nodes/DocuLinkBox.tsx          | 77 ++++++++++++++++++++++
 src/client/views/nodes/DocuLinkView.tsx         | 85 -------------------------
 src/client/views/nodes/DocumentContentsView.tsx |  3 +-
 src/client/views/nodes/DocumentView.tsx         | 31 ++-------
 9 files changed, 94 insertions(+), 115 deletions(-)
 create mode 100644 src/client/views/nodes/DocuLinkBox.tsx
 delete mode 100644 src/client/views/nodes/DocuLinkView.tsx

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 66ed8be5d..7bb025e49 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -289,6 +289,8 @@ export function percent2frac(percent: string) {
 
 export function numberRange(num: number) { return Array.from(Array(num)).map((v, i) => i); }
 
+export function returnTransparent() { return "transparent"; }
+
 export function returnTrue() { return true; }
 
 export function returnFalse() { return false; }
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 03178bbdb..ea37fc2f1 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -23,4 +23,5 @@ export enum DocumentType {
     PRESELEMENT = "preselement",
     QUERY = "search",
     COLOR = "color",
+    DOCULINK = "doculink"
 }
\ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 62175cbe3..edc2ac653 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -47,6 +47,7 @@ import { LinkFollowBox } from "../views/linking/LinkFollowBox";
 import { PresElementBox } from "../views/presentationview/PresElementBox";
 import { QueryBox } from "../views/nodes/QueryBox";
 import { ColorBox } from "../views/nodes/ColorBox";
+import { DocuLinkBox } from "../views/nodes/DocuLinkBox";
 var requestImageSize = require('../util/request-image-size');
 var path = require('path');
 
@@ -719,6 +720,10 @@ export namespace DocUtils {
             linkDocProto.anchor2Context = target.ctx;
             linkDocProto.anchor2Groups = new List<Doc>([]);
             linkDocProto.anchor2Timecode = target.doc.currentTimecode;
+            linkDocProto.layoutKey1 = DocuLinkBox.LayoutString("anchor1");
+            linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2");
+            linkDocProto.width = linkDocProto.height = 0;
+            linkDocProto.isBackground = true;
 
             LinkManager.Instance.addLink(linkDocProto);
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 927729487..6e8ba2d3d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -167,8 +167,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     get Bounds(): { x: number, y: number, b: number, r: number } {
         let x = this._forceUpdate;
         this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
-            if (documentView._selectedLink !== -1 ||
-                documentView.props.renderDepth === 0 ||
+            if (documentView.props.renderDepth === 0 ||
                 Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) {
                 return bounds;
             }
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 1aacdc8ec..c3a617ffe 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.setBackround(matchedColor));
+                (!ruleProvider && targetDoc) && (view.props.Document.backgroundColor = matchedColor);
 
                 return {
                     target: targetDoc,
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
new file mode 100644
index 000000000..2e968a9f6
--- /dev/null
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -0,0 +1,77 @@
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Doc } from "../../../new_fields/Doc";
+import { makeInterface } from "../../../new_fields/Schema";
+import { NumCast, StrCast } from "../../../new_fields/Types";
+import { Utils } from '../../../Utils';
+import { DocumentManager } from "../../util/DocumentManager";
+import { DragLinksAsDocuments } from "../../util/DragManager";
+import { DocComponent } from "../DocComponent";
+import { documentSchema } from "./DocumentView";
+import "./DocumentView.scss";
+import { FieldView, FieldViewProps } from "./FieldView";
+import React = require("react");
+
+type DocLinkSchema = makeInterface<[typeof documentSchema]>;
+const DocLinkDocument = makeInterface(documentSchema);
+
+@observer
+export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(DocLinkDocument) {
+    public static LayoutString(fieldKey: string, fieldExt?: string) { return FieldView.LayoutString(DocuLinkBox, fieldKey, fieldExt); }
+    _downx = 0;
+    _downy = 0;
+    @observable _x = 0;
+    @observable _y = 0;
+    @observable _selected = false;
+    _ref = React.createRef<HTMLDivElement>();
+
+    onPointerDown = (e: React.PointerEvent) => {
+        this._downx = e.clientX;
+        this._downy = e.clientY;
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        document.addEventListener("pointermove", this.onPointerMove);
+        document.addEventListener("pointerup", this.onPointerUp);
+        e.stopPropagation();
+    }
+    onPointerMove = action((e: PointerEvent) => {
+        let cdiv = this._ref.current!.parentElement;
+        if (cdiv && (Math.abs(e.clientX - this._downx) > 5 || Math.abs(e.clientY - this._downy) > 5)) {
+            let bounds = cdiv.getBoundingClientRect();
+            let pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
+            let separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
+            let dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy))
+            if (separation > 100) {
+                DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], this.props.ContainingCollectionDoc as Doc, this.props.Document); // Containging collection is the document, not a collection... hack.
+                document.removeEventListener("pointermove", this.onPointerMove);
+                document.removeEventListener("pointerup", this.onPointerUp);
+            } else if (dragdist > separation) {
+                this.props.Document[this.props.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
+                this.props.Document[this.props.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
+            }
+        }
+    })
+    onPointerUp = (e: PointerEvent) => {
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button === 2 || e.ctrlKey)) {
+            this.props.select(false);
+        }
+    }
+    onClick = (e: React.MouseEvent) => {
+        if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey)) {
+            DocumentManager.Instance.FollowLink(this.props.Document, this.props.Document[this.props.fieldKey] as Doc, document => this.props.addDocTab(document, undefined, "inTab"), false);
+        }
+        e.stopPropagation();
+    }
+    render() {
+        let y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
+        let x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
+        let c = StrCast(this.props.Document.backgroundColor, "lightblue");
+        return <div onPointerDown={this.onPointerDown} onClick={this.onClick} title={StrCast((this.props.Document[this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1"]! as Doc).title)} ref={this._ref} style={{
+            cursor: "default", position: "absolute", background: c, width: "25px", height: "25px", borderRadius: "20px", textAlign: "center", left: `calc(${x}% - 12.5px)`, top: `calc(${y}% - 12.5px)`,
+            pointerEvents: "all",
+            transform: `scale(${1 / this.props.ContentScaling()})`,
+        }} />
+    }
+}
diff --git a/src/client/views/nodes/DocuLinkView.tsx b/src/client/views/nodes/DocuLinkView.tsx
deleted file mode 100644
index 622d41047..000000000
--- a/src/client/views/nodes/DocuLinkView.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Opt } from "../../../new_fields/Doc";
-import { NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils } from '../../../Utils';
-import { DocumentManager } from "../../util/DocumentManager";
-import "./DocumentView.scss";
-import React = require("react");
-import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
-import { UndoManager } from "../../util/UndoManager";
-
-
-interface DocuLinkViewProps {
-    Document: Doc;
-    isSelected: () => boolean;
-    addDocTab: (doc: Doc, dataDoc: Opt<Doc>, where: string) => void;
-    anchor: string;
-    otherAnchor: string;
-    scale: () => number;
-    contentDiv: HTMLDivElement | null;
-    link: Doc;
-    index: number;
-    selectedLink: () => number;
-    selectLink: (id: number) => void;
-    blacklist: Opt<Doc>
-}
-
-@observer
-export class DocuLinkView extends React.Component<DocuLinkViewProps> {
-    _downx = 0;
-    _downy = 0;
-    @observable _x = 0;
-    @observable _y = 0;
-    @observable _selected = false;
-    _ref = React.createRef<HTMLDivElement>();
-
-    onPointerDown = (e: React.PointerEvent) => {
-        this._downx = e.clientX;
-        this._downy = e.clientY;
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        document.addEventListener("pointermove", this.onPointerMove);
-        document.addEventListener("pointerup", this.onPointerUp);
-        e.stopPropagation();
-    }
-    onPointerMove = action((e: PointerEvent) => {
-        if (this.props.contentDiv && (Math.abs(e.clientX - this._downx) > 5 || Math.abs(e.clientY - this._downy) > 5)) {
-            let bounds = this.props.contentDiv.getBoundingClientRect();
-            let pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
-            let separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
-            let dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy))
-            if (separation > 100) {
-                DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], this.props.Document, this.props.link);
-                document.removeEventListener("pointermove", this.onPointerMove);
-                document.removeEventListener("pointerup", this.onPointerUp);
-            } else if (dragdist > separation) {
-                this.props.link[this.props.anchor + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
-                this.props.link[this.props.anchor + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
-            }
-        }
-    })
-    onPointerUp = (e: PointerEvent) => {
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button === 2 || e.ctrlKey)) {
-            this.props.selectLink(this.props.selectedLink() === this.props.index ? -1 : this.props.index);
-        }
-    }
-    onClick = (e: React.MouseEvent) => {
-        if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey)) {
-            DocumentManager.Instance.FollowLink(this.props.link, this.props.link[this.props.anchor] as Doc, document => this.props.addDocTab(document, undefined, "inTab"), false);
-        }
-        e.stopPropagation();
-    }
-    render() {
-        let y = NumCast(this.props.link[this.props.anchor + "_y"], 100);
-        let x = NumCast(this.props.link[this.props.anchor + "_x"], 100);
-        let c = StrCast(this.props.link[this.props.anchor + "_background"], "lightblue");
-        return <div onPointerDown={this.onPointerDown} onClick={this.onClick} title={StrCast((this.props.link[this.props.otherAnchor]! as Doc).title)} ref={this._ref} style={{
-            cursor: "default", position: "absolute", background: c, width: "25px", height: "25px", borderRadius: "20px", textAlign: "center", left: `calc(${x}% - 12.5px)`, top: `calc(${y}% - 12.5px)`,
-            transform: `scale(${1 / this.props.scale()})`,
-            border: this.props.selectedLink() === this.props.index && this.props.isSelected() ? "solid 3px black" : undefined
-        }} />
-    }
-}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 19ffdf0cd..4971e6ce5 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -26,6 +26,7 @@ import { PDFBox } from "./PDFBox";
 import { PresBox } from "./PresBox";
 import { QueryBox } from "./QueryBox";
 import { ColorBox } from "./ColorBox";
+import { DocuLinkBox } from "./DocuLinkBox";
 import { PresElementBox } from "../presentationview/PresElementBox";
 import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
@@ -104,7 +105,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             components={{
                 FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, FieldView,
                 CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
-                PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox, ColorBox
+                PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox, ColorBox, DocuLinkBox
             }}
             bindings={this.CreateBindings()}
             jsx={this.finalLayout}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d8a1841b1..f2171dc2a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -9,7 +9,7 @@ import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schem
 import { ScriptField } from '../../../new_fields/ScriptField';
 import { BoolCast, Cast, NumCast, PromiseValue, StrCast, FieldValue } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { emptyFunction, returnTrue, Utils } from "../../../Utils";
+import { emptyFunction, returnTrue, Utils, returnTransparent } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { ClientUtils } from '../../util/ClientUtils';
@@ -40,7 +40,6 @@ import SharingManager from '../../util/SharingManager';
 import { Scripting } from '../../util/Scripting';
 import { DictationOverlay } from '../DictationOverlay';
 import { CollectionViewType } from '../collections/CollectionBaseView';
-import { DocuLinkView } from './DocuLinkView';
 
 library.add(fa.faEdit);
 library.add(fa.faTrash);
@@ -93,6 +92,7 @@ export interface DocumentViewProps {
     getScale: () => number;
     animateBetweenIcon?: (maximize: boolean, target: number[]) => void;
     ChromeHeight?: () => number;
+    layoutKey?: string;
 }
 
 export const documentSchema = createSchema({
@@ -242,7 +242,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.nativeEvent.cancelBubble && e.button === 0) return;
-        runInAction(() => this._selectedLink = -1);
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._hitTemplateDrag = false;
@@ -625,27 +624,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             isSelected={this.isSelected}
             select={this.select}
             onClick={this.onClickHandler}
-            layoutKey="layout"
+            layoutKey={this.props.layoutKey || "layout"}
             DataDoc={this.props.DataDoc} />);
     }
-    linkEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "anchor1" : "anchor2";
-    linkOtherEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "anchor2" : "anchor1";
-    public setBackround(color: string) {
-        let selLink = this._selectedLink !== -1 && DocListCast(this.Document.links)[this._selectedLink];
-        if (selLink) {
-            let both = selLink["anchor1_background"] === selLink["anchor2_background"];
-            selLink[this.linkEndpoint(selLink) + "_background"] = color;
-            both && (selLink[this.linkOtherEndpoint(selLink) + "_background"] = color);
-        } else {
-            this.Document.backgroundColor = color;
-        }
-    }
-    @observable _selectedLink = -1;
-    selectLink = action((which: number) => {
-        SelectionManager.SelectDoc(this, false);
-        this._selectedLink = which;
-    })
-    selectedLink = () => this._selectedLink;
+    linkEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2";
 
     render() {
         if (!this.props.Document) return (null);
@@ -717,10 +699,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
                 onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)}
             >
-                {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) =>
-                    <DocuLinkView Document={this.props.Document} blacklist={this.props.ContainingCollectionDoc} anchor={this.linkEndpoint(d)} otherAnchor={this.linkOtherEndpoint(d)} addDocTab={this.props.addDocTab} contentDiv={this.ContentDiv}
-                        scale={this.props.ContentScaling}
-                        isSelected={this.isSelected} link={d} index={i} selectLink={this.selectLink} selectedLink={this.selectedLink} />)}
+                {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) => <DocumentView {...this.props} backgroundColor={returnTransparent} Document={d} layoutKey={this.linkEndpoint(d)} />)}
                 {!showTitle && !showCaption ?
                     this.Document.searchFields ?
                         (<div className="documentView-searchWrapper">
-- 
cgit v1.2.3-70-g09d2


From c9a84a7d74efa820d1dc55d8ef93bec40315be4a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 18 Oct 2019 01:29:33 -0400
Subject: link lines are working properly except when things get panned/moved
 ... need to turn off or get updates somehow.

---
 src/client/documents/Documents.ts                  | 15 ++--------
 src/client/util/DocumentManager.ts                 | 11 +++++--
 src/client/views/DocumentDecorations.tsx           |  3 +-
 .../CollectionFreeFormLinksView.tsx                |  4 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 35 ----------------------
 src/client/views/nodes/DocumentContentsView.tsx    | 32 ++++++++------------
 src/client/views/nodes/DocumentView.tsx            |  9 ++----
 src/new_fields/Doc.ts                              |  6 ----
 8 files changed, 28 insertions(+), 87 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d979e0a3f..b53549f7a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -80,7 +80,6 @@ export interface DocumentOptions {
     opacity?: number;
     defaultBackgroundColor?: string;
     dropAction?: dropActionType;
-    backgroundLayout?: string;
     chromeStatus?: string;
     columnWidth?: number;
     fontSize?: number;
@@ -127,7 +126,6 @@ export namespace Docs {
             layout: {
                 view: LayoutSource,
                 ext?: string, // optional extension field for layout source
-                collectionView?: CollectionViewType
             },
             options?: Partial<DocumentOptions>
         };
@@ -142,7 +140,7 @@ export namespace Docs {
                 options: { height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" }
             }],
             [DocumentType.HIST, {
-                layout: { view: HistogramBox, collectionView: [CollectionView, data] as CollectionViewType },
+                layout: { view: HistogramBox },
                 options: { height: 300, backgroundColor: "black" }
             }],
             [DocumentType.QUERY, {
@@ -290,15 +288,8 @@ export namespace Docs {
             // synthesize the default options, the type and title from computed values and
             // whatever options pertain to this specific prototype
             let options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) };
-            let primary = layout.view.LayoutString(layout.ext);
-            let collectionView = layout.collectionView;
-            if (collectionView) {
-                options.layout = collectionView[0].LayoutString(collectionView[1], collectionView[2]);
-                options.backgroundLayout = primary;
-            } else {
-                options.layout = primary;
-            }
-            return Doc.assign(new Doc(prototypeId, true), { ...options, baseLayout: primary });
+            options.layout = layout.view.LayoutString(layout.ext);
+            return Doc.assign(new Doc(prototypeId, true), { ...options, baseLayout: options.layout });
         }
 
     }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index aed7aafd1..ee6772f8f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,5 +1,5 @@
 import { action, computed, observable } from 'mobx';
-import { Doc, DocListCastAsync } from '../../new_fields/Doc';
+import { Doc, DocListCastAsync, DocListCast } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
 import { List } from '../../new_fields/List';
 import { Cast, NumCast, StrCast } from '../../new_fields/Types';
@@ -100,7 +100,13 @@ export class DocumentManager {
 
     @computed
     public get LinkedDocumentViews() {
-        let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || Doc.IsBrushed(dv.props.Document)).reduce((pairs, dv) => {
+        let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || Doc.IsBrushed(dv.props.Document)
+            || DocumentManager.Instance.DocumentViews.some(dv2 => {
+                let init = dv2.isSelected() || Doc.IsBrushed(dv2.props.Document);
+                let rest = DocListCast(dv2.props.Document.links).some(l => Doc.AreProtosEqual(l, dv.props.Document));
+                return init && rest;
+            })
+        ).reduce((pairs, dv) => {
             let linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document);
             pairs.push(...linksList.reduce((pairs, link) => {
                 let linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document);
@@ -111,7 +117,6 @@ export class DocumentManager {
                 });
                 return pairs;
             }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
-            // }
             return pairs;
         }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6e8ba2d3d..8409a34da 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -111,7 +111,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                         Doc.GetProto(docTemplate).layoutNative = layoutNative;
                         swapViews(fieldTemplate, "", "layoutNative", layoutNative);
                         layoutNative.layout = StrCast(fieldTemplateView.props.Document.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
-                        layoutNative.backgroundLayout = StrCast(fieldTemplateView.props.Document.backgroundLayout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
                     }
                 }
             }
@@ -343,7 +342,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let iconDoc: Doc | undefined = await Cast(doc.minimizedDoc, Doc);
 
         if (!iconDoc || !DocumentManager.Instance.getDocumentView(iconDoc)) {
-            const layout = StrCast(doc.backgroundLayout, StrCast(doc.layout, FieldView.LayoutString(DocumentView)));
+            const layout = StrCast(doc.layout, FieldView.LayoutString(DocumentView));
             iconDoc = this.createIcon([docView], layout);
         }
         return iconDoc;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index a26febda4..e9191c176 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -68,9 +68,7 @@ export class CollectionFreeFormLinksView extends React.Component {
         //     });
     }
     componentWillUnmount() {
-        if (this._brushReactionDisposer) {
-            this._brushReactionDisposer();
-        }
+        this._brushReactionDisposer && this._brushReactionDisposer();
     }
     @computed
     get uniqueConnections() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b3270d507..932b6722b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -467,22 +467,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             getScale: this.getScale
         };
     }
-    getDocumentViewProps(layoutDoc: Doc): DocumentViewProps {
-        return {
-            ...this.props,
-            ScreenToLocalTransform: this.getTransform,
-            PanelWidth: layoutDoc[WidthSym],
-            PanelHeight: layoutDoc[HeightSym],
-            ContentScaling: returnOne,
-            ContainingCollectionView: this.props.ContainingCollectionView,
-            focus: this.focusDocument,
-            backgroundColor: returnEmptyString,
-            parentActive: this.props.active,
-            bringToFront: this.bringToFront,
-            zoomToScale: this.zoomToScale,
-            getScale: this.getScale
-        };
-    }
 
     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;
@@ -682,7 +666,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private childViews = () => {
         let children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
         return [
-            <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
             ...children,
             ...this.views,
         ];
@@ -712,29 +695,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     </CollectionFreeFormViewPannableContents>
                 </MarqueeView>
                 {this.overlayViews}
-                <CollectionFreeFormOverlayView  {...this.props} {...this.getDocumentViewProps(this.props.Document)} />
             </div>
         );
     }
 }
 
-@observer
-class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
-    render() {
-        return <DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
-            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />;
-    }
-}
-
-@observer
-class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
-    render() {
-        return !this.props.Document.backgroundLayout ? (null) :
-            (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
-                renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
-    }
-}
-
 interface CollectionFreeFormViewPannableContentsProps {
     centeringShiftX: () => number;
     centeringShiftY: () => number;
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 4971e6ce5..d375405b9 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -92,26 +92,20 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         return { props: list };
     }
 
-    @computed get finalLayout() {
-        return this.props.layoutKey === "overlayLayout" ? "<div/>" : this.layout;
-    }
-
     render() {
-        let self = this;
-        if (this.props.renderDepth > 7) return (null);
-        if (!this.layout && this.props.layoutKey !== "overlayLayout") return (null);
-        return <ObserverJsxParser
-            blacklistedAttrs={[]}
-            components={{
-                FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, FieldView,
-                CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
-                PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox, ColorBox, DocuLinkBox
-            }}
-            bindings={this.CreateBindings()}
-            jsx={this.finalLayout}
-            showWarnings={true}
+        return (this.props.renderDepth > 7 || !this.layout) ? (null) :
+            <ObserverJsxParser
+                blacklistedAttrs={[]}
+                components={{
+                    FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, FieldView,
+                    CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
+                    PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox, ColorBox, DocuLinkBox
+                }}
+                bindings={this.CreateBindings()}
+                jsx={this.layout}
+                showWarnings={true}
 
-            onError={(test: any) => { console.log(test); }}
-        />;
+                onError={(test: any) => { console.log(test); }}
+            />;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d4c2c512c..48ad7a632 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -163,6 +163,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @action
     componentWillUnmount() {
         this._dropDisposer && this._dropDisposer();
+        Doc.UnBrushDoc(this.props.Document);
         DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
     }
 
@@ -697,11 +698,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     opacity: this.Document.opacity
                 }}
                 onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
-                onPointerEnter={() => {
-                    Doc.UnBrushAllDocs();
-                    DocListCast(this.props.Document.links).map(Doc.BrushDoc);
-                    Doc.BrushDoc(this.props.Document);
-                }} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)}
+                onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)}
             >
                 {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) =>
                     <div style={{ pointerEvents: "none", position: "absolute", transformOrigin: "top left", width: "100%", height: "100%", transform: `scale(${this.props.Document.fitWidth ? 1 : 1 / this.props.ContentScaling()})` }}>
@@ -739,7 +736,6 @@ export async function swapViews(doc: Doc, newLayoutField: string, oldLayoutField
         oldLayoutExt.nativeWidth = doc.nativeWidth;
         oldLayoutExt.nativeHeight = doc.nativeHeight;
         oldLayoutExt.ignoreAspect = doc.ignoreAspect;
-        oldLayoutExt.backgroundLayout = doc.backgroundLayout;
         oldLayoutExt.type = doc.type;
         oldLayoutExt.layout = doc.layout;
     }
@@ -752,7 +748,6 @@ export async function swapViews(doc: Doc, newLayoutField: string, oldLayoutField
         doc.nativeWidth = newLayoutExt.nativeWidth;
         doc.nativeHeight = newLayoutExt.nativeHeight;
         doc.ignoreAspect = newLayoutExt.ignoreAspect;
-        doc.backgroundLayout = newLayoutExt.backgroundLayout;
         doc.type = newLayoutExt.type;
         doc.layout = await newLayoutExt.layout;
     }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 26355d0e7..69c048ebf 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -592,7 +592,6 @@ export namespace Doc {
         target.ignoreAspect = templateDoc.nativeWidth ? true : false;
         target.onClick = templateDoc.onClick instanceof ObjectField && templateDoc.onClick[Copy]();
         target.layout = layoutCustomLayout;
-        target.backgroundLayout = layoutCustomLayout.backgroundLayout;
 
         target.layoutNative = Cast(templateDoc.layoutNative, Doc) as Doc;
         target.layoutCustom = layoutCustom;
@@ -602,19 +601,14 @@ export namespace Doc {
     export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc, suppressTitle: boolean = false): boolean {
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
         let metadataFieldName = StrCast(fieldTemplate.title).replace(/^-/, "");
-        let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
         let fieldLayoutDoc = fieldTemplate;
         if (fieldTemplate.layout instanceof Doc) {
             fieldLayoutDoc = Doc.MakeDelegate(fieldTemplate.layout);
         }
-        if (backgroundLayout) {
-            backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metadataFieldName}"}`);
-        }
 
         fieldTemplate.templateField = metadataFieldName;
         fieldTemplate.title = metadataFieldName;
         fieldTemplate.isTemplateField = true;
-        fieldTemplate.backgroundLayout = backgroundLayout;
         /* move certain layout properties from the original data doc to the template layout to avoid
            inheriting them from the template's data doc which may also define these fields for its own use.
         */
-- 
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/DocumentDecorations.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


From d8f12dade0d0b336e4328fa1f00a1a538588d34e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 20 Oct 2019 13:34:25 -0400
Subject: initial version of template refactoring

---
 src/client/documents/Documents.ts                  | 10 +--
 src/client/views/DocumentDecorations.tsx           | 26 ++++----
 src/client/views/TemplateMenu.tsx                  |  3 +-
 .../views/collections/CollectionDockingView.tsx    | 23 +++----
 .../views/collections/CollectionSchemaView.tsx     | 13 ++--
 .../views/collections/CollectionStackingView.tsx   | 37 +++++++----
 src/client/views/collections/CollectionSubView.tsx | 11 ++--
 .../views/collections/CollectionTreeView.tsx       | 19 +++---
 .../CollectionFreeFormLayoutEngines.tsx            |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentContentsView.tsx    | 11 ++--
 src/client/views/nodes/DocumentView.tsx            | 77 +++++++---------------
 src/client/views/nodes/FormattedTextBox.tsx        |  3 +-
 src/client/views/nodes/ImageBox.tsx                | 52 ++++++++-------
 src/client/views/nodes/PDFBox.tsx                  | 10 +--
 src/client/views/nodes/VideoBox.tsx                | 35 +++++-----
 src/client/views/nodes/WebBox.tsx                  | 11 ++--
 src/client/views/pdf/PDFViewer.tsx                 | 40 +++++------
 .../views/presentationview/PresElementBox.tsx      |  9 +--
 src/client/views/search/SearchBox.tsx              |  2 +-
 src/client/views/search/SearchItem.tsx             |  2 +-
 src/new_fields/Doc.ts                              | 37 +++++------
 .../authentication/models/current_user_utils.ts    |  2 +-
 24 files changed, 211 insertions(+), 231 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 32fda1954..fbf45816a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -300,7 +300,7 @@ export namespace Docs {
      */
     export namespace Create {
 
-        const delegateKeys = ["x", "y", "width", "height", "panX", "panY", "dropAction", "forceActive"];
+        const delegateKeys = ["x", "y", "width", "height", "panX", "panY", "nativeWidth", "nativeHeight", "dropAction", "forceActive", "fitWidth"];
 
         /**
          * This function receives the relevant document prototype and uses
@@ -369,11 +369,11 @@ export namespace Docs {
             requestImageSize(target)
                 .then((size: any) => {
                     let aspect = size.height / size.width;
-                    if (!inst.proto!.nativeWidth) {
-                        inst.proto!.nativeWidth = size.width;
+                    if (!inst.nativeWidth) {
+                        inst.nativeWidth = size.width;
                     }
-                    inst.proto!.nativeHeight = Number(inst.proto!.nativeWidth!) * aspect;
-                    inst.proto!.height = NumCast(inst.proto!.width) * aspect;
+                    inst.nativeHeight = NumCast(inst.nativeWidth) * aspect;
+                    inst.height = NumCast(inst.width) * aspect;
                 })
                 .catch((err: any) => console.log(err));
             // }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 07af4799b..252f90d46 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -19,7 +19,7 @@ import { CollectionView } from "./collections/CollectionView";
 import { DocumentButtonBar } from './DocumentButtonBar';
 import './DocumentDecorations.scss';
 import { PositionDocument } from './nodes/CollectionFreeFormDocumentView';
-import { DocumentView, swapViews } from "./nodes/DocumentView";
+import { DocumentView } from "./nodes/DocumentView";
 import { FieldView } from "./nodes/FieldView";
 import { FormattedTextBox } from "./nodes/FormattedTextBox";
 import { IconBox } from "./nodes/IconBox";
@@ -107,10 +107,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     fieldTemplate.title = metaKey;
                     Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate));
                     if (text.startsWith(">>")) {
-                        let layoutNative = Doc.MakeTitled("layoutNative");
-                        Doc.GetProto(docTemplate).layoutNative = layoutNative;
-                        swapViews(fieldTemplate, "", "layoutNative", layoutNative);
-                        layoutNative.layout = StrCast(fieldTemplateView.props.Document.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
+                        Doc.GetProto(docTemplate).layout = StrCast(fieldTemplateView.props.Document.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
                     }
                 }
             }
@@ -471,8 +468,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
                 let doc = PositionDocument(element.props.Document);
                 let layoutDoc = PositionDocument(Doc.Layout(element.props.Document));
-                let nwidth = doc.nativeWidth || 0;
-                let nheight = doc.nativeHeight || 0;
+                let nwidth = layoutDoc.nativeWidth || 0;
+                let nheight = layoutDoc.nativeHeight || 0;
                 let width = (layoutDoc.width || 0);
                 let height = (layoutDoc.height || (nheight / nwidth * width));
                 let scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling();
@@ -480,21 +477,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 let actualdH = Math.max(height + (dH * scale), 20);
                 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) {
                     layoutDoc.ignoreAspect = false;
-                    proto.nativeWidth = nwidth = layoutDoc.width || 0;
-                    proto.nativeHeight = nheight = layoutDoc.height || 0;
+                    layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
+                    layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
                 }
                 if (fixedAspect && (!nwidth || !nheight)) {
-                    proto.nativeWidth = nwidth = layoutDoc.width || 0;
-                    proto.nativeHeight = nheight = layoutDoc.height || 0;
+                    layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
+                    layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
                 }
                 if (nwidth > 0 && nheight > 0 && !layoutDoc.ignoreAspect) {
                     if (Math.abs(dW) > Math.abs(dH)) {
                         if (!fixedAspect) {
-                            Doc.SetInPlace(doc, "nativeWidth", actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0), true);
+                            layoutDoc.nativeWidth = actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0);
                         }
                         layoutDoc.width = actualdW;
                         if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width;
@@ -502,7 +498,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     }
                     else {
                         if (!fixedAspect) {
-                            Doc.SetInPlace(doc, "nativeHeight", actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0), true);
+                            layoutDoc.nativeHeight = actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0);
                         }
                         layoutDoc.height = actualdH;
                         if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height;
@@ -511,7 +507,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 } else {
                     dW && (layoutDoc.width = actualdW);
                     dH && (layoutDoc.height = actualdH);
-                    dH && layoutDoc.autoHeight && Doc.SetInPlace(layoutDoc, "autoHeight", false, true);
+                    dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false);
                 }
             }
         });
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index d76b033f0..96265385e 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -9,6 +9,7 @@ import { DocumentView } from "./nodes/DocumentView";
 import { Template, Templates } from "./Templates";
 import React = require("react");
 import { Doc } from "../../new_fields/Doc";
+import { StrCast } from "../../new_fields/Types";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -128,7 +129,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         this.props.templates.forEach((checked, template) =>
             templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
         templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={this.props.docs[0].Document.z ? true : false} toggle={this.toggleFloat} />);
-        templateMenu.push(<OtherToggle key={"custom"} name={"Custom"} checked={typeof this.props.docs[0].Document.layout === "string" ? false : true} toggle={this.toggleCustom} />);
+        templateMenu.push(<OtherToggle key={"custom"} name={"Custom"} checked={StrCast(this.props.docs[0].Document.layoutKey, "layout") !== "layout"} toggle={this.toggleCustom} />);
         templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout.chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
         return (
             <div className="templating-menu" >
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1f78c8c97..42d372f4a 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -615,19 +615,20 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
     }
 
-    panelWidth = () => this._document && this._document.maxWidth ? Math.min(Math.max(NumCast(this._document.width), NumCast(this._document.nativeWidth)), this._panelWidth) : this._panelWidth;
+    get layoutDoc() { return this._document && Doc.Layout(this._document);}
+    panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc.width), NumCast(this.layoutDoc.nativeWidth)), this._panelWidth) : this._panelWidth;
     panelHeight = () => this._panelHeight;
 
-    nativeWidth = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeWidth) || this._panelWidth : 0;
-    nativeHeight = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeHeight) || this._panelHeight : 0;
+    nativeWidth = () => !this.layoutDoc!.ignoreAspect && !this.layoutDoc!.fitWidth ? NumCast(this.layoutDoc!.nativeWidth) || this._panelWidth : 0;
+    nativeHeight = () => !this.layoutDoc!.ignoreAspect && !this.layoutDoc!.fitWidth ? NumCast(this.layoutDoc!.nativeHeight) || this._panelHeight : 0;
 
     contentScaling = () => {
-        if (this._document!.type === DocumentType.PDF) {
-            if ((this._document && this._document.fitWidth) ||
-                this._panelHeight / NumCast(this._document!.nativeHeight) > this._panelWidth / NumCast(this._document!.nativeWidth)) {
-                return this._panelWidth / NumCast(this._document!.nativeWidth);
+        if (this.layoutDoc!.type === DocumentType.PDF) {
+            if ((this.layoutDoc && this.layoutDoc.fitWidth) ||
+                this._panelHeight / NumCast(this.layoutDoc!.nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!.nativeWidth)) {
+                return this._panelWidth / NumCast(this.layoutDoc!.nativeWidth);
             } else {
-                return this._panelHeight / NumCast(this._document!.nativeHeight);
+                return this._panelHeight / NumCast(this.layoutDoc!.nativeHeight);
             }
         }
         const nativeH = this.nativeHeight();
@@ -645,7 +646,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
         return Transform.Identity();
     }
-    get previewPanelCenteringOffset() { return this.nativeWidth() && !this._document!.ignoreAspect ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
+    get previewPanelCenteringOffset() { return this.nativeWidth() && !this.layoutDoc!.ignoreAspect ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
 
     addDocTab = (doc: Doc, dataDoc: Opt<Doc>, location: string) => {
         SelectionManager.DeselectAll();
@@ -690,11 +691,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
 
     render() {
-        return (!this._isActive || !this._document) ? (null) :
+        return (!this._isActive || !this.layoutDoc) ? (null) :
             (<div className="collectionDockingView-content" ref={ref => this._mainCont = ref}
                 style={{
                     transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`,
-                    height: this._document && this._document.fitWidth ? undefined : "100%"
+                    height: this.layoutDoc && this.layoutDoc.fitWidth ? undefined : "100%"
                 }}>
                 {this.docView}
             </div >);
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 3218f630a..23e070750 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -922,8 +922,9 @@ interface CollectionSchemaPreviewProps {
 export class CollectionSchemaPreview extends React.Component<CollectionSchemaPreviewProps>{
     private dropDisposer?: DragManager.DragDropDisposer;
     _mainCont?: HTMLDivElement;
-    private get nativeWidth() { return NumCast(this.props.Document!.nativeWidth, this.props.PanelWidth()); }
-    private get nativeHeight() { return NumCast(this.props.Document!.nativeHeight, this.props.PanelHeight()); }
+    private get layoutDoc() { return this.props.Document && Doc.Layout(this.props.Document); }
+    private get nativeWidth() { return NumCast(this.layoutDoc!.nativeWidth, this.props.PanelWidth()); }
+    private get nativeHeight() { return NumCast(this.layoutDoc!.nativeHeight, this.props.PanelHeight()); }
     private contentScaling = () => {
         let wscale = this.props.PanelWidth() / (this.nativeWidth ? this.nativeWidth : this.props.PanelWidth());
         if (wscale * this.nativeHeight > this.props.PanelHeight()) {
@@ -947,10 +948,8 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
         if (de.data instanceof DragManager.DocumentDragData) {
             this.props.childDocs && this.props.childDocs.map(otherdoc => {
                 let target = Doc.GetProto(otherdoc);
-                let layoutNative = Doc.MakeTitled("layoutNative");
-                layoutNative.layout = ComputedField.MakeFunction("this.image_data[0]");
-                target.layoutNative = layoutNative;
-                target.layoutCUstom = target.layout = Doc.MakeDelegate(de.data.draggedDocuments[0]);
+                target.layout = ComputedField.MakeFunction("this.image_data[0]");
+                target.layoutCustom = Doc.MakeDelegate(de.data.draggedDocuments[0]);
             });
             e.stopPropagation();
         }
@@ -968,7 +967,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
         let br = StrCast(this.props.Document!.borderRounding);
         if (br.endsWith("%")) {
             let percent = Number(br.substr(0, br.length - 1)) / 100;
-            let nativeDim = Math.min(NumCast(this.props.Document!.nativeWidth), NumCast(this.props.Document!.nativeHeight));
+            let nativeDim = Math.min(NumCast(this.layoutDoc!.nativeWidth), NumCast(this.layoutDoc!.nativeHeight));
             let minDim = percent * (nativeDim ? nativeDim : Math.min(this.PanelWidth(), this.PanelHeight()));
             return minDim;
         }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 1a578f4fc..21c266b7e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -57,15 +57,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         this._docXfs.length = 0;
         return docs.map((d, i) => {
             let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d);
-            let width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
-            let height = () => this.getDocHeight(pair.layout);
+            let layoutDoc = pair.layout ? Doc.Layout(pair.layout) : d;
+            let width = () => Math.min(layoutDoc.nativeWidth && !layoutDoc.ignoreAspect && !this.props.Document.fillColumn ? layoutDoc[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
+            let height = () => this.getDocHeight(layoutDoc);
             let dref = React.createRef<HTMLDivElement>();
-            let dxf = () => this.getDocTransform(pair.layout!, dref.current!);
+            let dxf = () => this.getDocTransform(layoutDoc, dref.current!);
             this._docXfs.push({ dxf: dxf, width: width, height: height });
             let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
             let style = this.isStackingView ? { width: width(), margin: "auto", marginTop: i === 0 ? 0 : this.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` };
             return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
-                {pair.layout instanceof Doc && this.getDisplayDoc(pair.layout, pair.data, dxf, width)}
+                {this.getDisplayDoc(pair.layout || d, pair.data, dxf, width)}
             </div>;
         });
     }
@@ -111,8 +112,15 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             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,
-                        (this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => height + this.childDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap), this.yMargin)), 0);
+                    let res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => {
+                        let r1 = Math.max(maxHght,
+                            (this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => {
+                                let val = height + this.childDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap);
+                                return val;
+                            }, this.yMargin));
+                        return r1;
+                    }, 0);
+                    return res;
                 } else {
                     let sum = Array.from(this._heightMap.values()).reduce((acc: number, curr: number) => acc += curr, 0);
                     return this.props.ContentScaling() * (sum + (this.Sections.size ? 85 : -15));
@@ -122,7 +130,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         },
             (hgt: number) => {
                 let doc = hgt === -1 ? undefined : this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
-                doc && (doc.height = hgt);
+                doc && (Doc.Layout(doc).height = hgt);
             },
             { fireImmediately: true }
         );
@@ -155,11 +163,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
     @computed get onClickHandler() { return ScriptCast(this.Document.onChildClick); }
 
-    getDisplayDoc(layoutDoc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
-        let height = () => this.getDocHeight(layoutDoc);
+    getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
+        let layoutDoc = Doc.Layout(doc);
+        let height = () => this.getDocHeight(doc);
         let finalDxf = () => dxf().scale(this.columnWidth / layoutDoc[WidthSym]());
         return <CollectionSchemaPreview
-            Document={layoutDoc}
+            Document={doc}
             DataDocument={dataDoc}
             showOverlays={this.overlays}
             renderDepth={this.props.renderDepth}
@@ -186,16 +195,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     getDocHeight(d?: Doc) {
         if (!d) return 0;
         let layoutDoc = Doc.Layout(d);
-        let nw = NumCast(d.nativeWidth);
-        let nh = NumCast(d.nativeHeight);
+        let nw = NumCast(layoutDoc.nativeWidth);
+        let nh = NumCast(layoutDoc.nativeHeight);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
         if (!layoutDoc.ignoreAspect && !layoutDoc.fitWidth && nw && nh) {
             let aspect = nw && nh ? nh / nw : 1;
             if (!(d.nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
             return wid * aspect;
         }
-        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]();
+        return layoutDoc.fitWidth ? !layoutDoc.nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin :
+            Math.min(wid * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc.nativeHeight)) / NumCast(layoutDoc.nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym]();
     }
 
     columnDividerDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index f76447157..0e8c46d40 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -61,11 +61,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)],
                 async (args) => {
                     if (args[1] instanceof Doc) {
-                        this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc)));
+                        this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc), "layoutFromParent"));
+                    }
+                    else if (!(args[1] instanceof Promise)) {
+                        this.childDocs.filter(d => !d.isTemplateField).map(async doc => doc.layoutKey === "layoutFromParent" && (doc.layoutKey = "layout"));
                     }
-                    // else if (!(args[1] instanceof Promise)) {
-                    //     this.childDocs.filter(d => !d.isTemplateField).map(async doc => doc.layout = undefined);
-                    // }
                 });
 
         }
@@ -134,8 +134,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             if (de.data instanceof DragManager.DocumentDragData && !de.data.applyAsTemplate) {
                 if (de.mods === "AltKey" && de.data.draggedDocuments.length) {
                     this.childDocs.map(doc =>
-                        Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc)
-                    );
+                        Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, "layoutFromParent"));
                     e.stopPropagation();
                     return true;
                 }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 2fbe8527e..77736b7d0 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -249,23 +249,22 @@ class TreeView extends React.Component<TreeViewProps> {
         let finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
         return finalXf;
     }
+    get layoutDoc() { return Doc.Layout(this.props.document); }
     docWidth = () => {
-        let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth);
-        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;
+        let aspect = NumCast(this.layoutDoc.nativeHeight) / NumCast(this.layoutDoc.nativeWidth);
+        if (aspect) return Math.min(this.layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20));
+        return NumCast(this.layoutDoc.nativeWidth) ? Math.min(this.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 = Doc.Layout(this.props.document);
+            let aspect = NumCast(this.layoutDoc.nativeHeight) / NumCast(this.layoutDoc.nativeWidth);
             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) :
-                Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(this.props.document.nativeHeight)) / NumCast(this.props.document.nativeWidth,
+            return this.layoutDoc.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) :
+                Math.min(this.docWidth() * NumCast(this.layoutDoc.scrollHeight, NumCast(this.layoutDoc.nativeHeight)) / NumCast(this.layoutDoc.nativeWidth,
                     NumCast(this.props.containingCollection.height)))) :
-                NumCast(layoutDoc.height) ? NumCast(layoutDoc.height) : 50;
+                NumCast(this.layoutDoc.height) ? NumCast(this.layoutDoc.height) : 50;
         })());
     }
 
@@ -484,7 +483,7 @@ class TreeView extends React.Component<TreeViewProps> {
             };
             const childLayout = Doc.Layout(pair.layout);
             let rowHeight = () => {
-                let aspect = NumCast(child.nativeWidth, 0) / NumCast(child.nativeHeight, 0);
+                let aspect = NumCast(childLayout.nativeWidth, 0) / NumCast(childLayout.nativeHeight, 0);
                 return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]();
             };
             return <TreeView
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 886692172..48d330674 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -63,11 +63,12 @@ export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs:
             fontSize: NumCast(pivotDoc.pivotFontSize, 10)
         });
         for (const doc of val) {
+            let layoutDoc = Doc.Layout(doc);
             docMap.set(doc, {
                 x: x + xCount * pivotAxisWidth * 1.25,
                 y: -y,
                 width: pivotAxisWidth,
-                height: doc.nativeWidth ? (NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth
+                height: layoutDoc.nativeWidth ? (NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth
             });
             xCount++;
             if (xCount >= numCols) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index eff73b14e..aa1106f13 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -63,10 +63,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _hitCluster = false;
     @observable _clusterSets: (Doc[])[] = [];
 
+    get layoutDoc() { return PanZoomDocument(Doc.Layout(this.props.Document)); }
     @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
     @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
     @computed get contentBounds() { return aggregateBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); }
-    @computed get nativeWidth() { return this.fitToContent ? 0 : this.Document.nativeWidth || 0; }
+    @computed get nativeWidth() { return this.layoutDoc.fitToContent ? 0 : this.layoutDoc.nativeWidth || 0; }
     @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; }
     private get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt')
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
@@ -448,6 +449,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             ...this.props,
             DataDoc: childData,
             Document: childLayout,
+            layoutKey: undefined,
             ruleProvider: this.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider, //bcz: hack! - currently ruleProviders apply to documents in nested colleciton, not direct children of themselves
             onClick: undefined, // this.props.onClick,  // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 2243a44d5..e3ca02fa4 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -77,7 +77,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     borderRounding = () => {
         let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
-        let ld = this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout : undefined;
+        let ld = this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] instanceof Doc ? this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] as Doc : undefined;
         let br = StrCast((ld || this.props.Document).borderRounding);
         br = !br && ruleRounding ? ruleRounding : br;
         if (br.endsWith("%")) {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index bb9315ca3..7aed416f4 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -2,7 +2,7 @@ import { computed } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../../new_fields/Doc";
 import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast } from "../../../new_fields/Types";
+import { Cast, StrCast } from "../../../new_fields/Types";
 import { OmitKeys, Without } from "../../../Utils";
 import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
@@ -69,10 +69,9 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     get dataDoc() {
-        if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) {
-            // if there is no dataDoc (ie, we're not rendering a template layout), but this document
-            // has a template layout document, then we will render the template layout but use 
-            // this document as the data document for the layout.
+        if (this.props.DataDoc === undefined && (Doc.LayoutField(this.props.Document) instanceof Doc || this.props.Document instanceof Promise)) {
+            // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), 
+            // then we render the layout document as a template and use this document as the data context for the template layout.
             return this.props.Document;
         }
         return this.props.DataDoc;
@@ -83,7 +82,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         let list = {
             ...OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit,
             Document: this.layoutDoc,
-            DataDoc: this.dataDoc
+            DataDoc: this.dataDoc,
         };
         return { props: list };
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1087c57ad..2d13eeae4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -191,9 +191,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let preventDefault = true;
             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) {
-                    await swapViews(fullScreenAlias, "layoutCustom", "layoutNative");
+                if (StrCast(fullScreenAlias.layoutKey) !== "layoutCustom" && fullScreenAlias["layoutCustom"] !== undefined) {
+                    fullScreenAlias.layoutKey = "layoutCustom";
                 }
                 this.props.addDocTab(fullScreenAlias, undefined, "inTab");
                 SelectionManager.DeselectAll();
@@ -287,16 +286,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); }
 
-    static makeNativeViewClicked = async (doc: Doc): Promise<void> => {
-        undoBatch(() => swapViews(doc, "layoutNative", "layoutCustom"))();
+    static makeNativeViewClicked = (doc: Doc) => {
+        undoBatch(() => doc.layoutKey = "layout")();
     }
 
-    static makeCustomViewClicked = async (doc: Doc, dataDoc: Opt<Doc>) => {
+    static makeCustomViewClicked = (doc: Doc, dataDoc: Opt<Doc>) => {
         const batch = UndoManager.StartBatch("CustomViewClicked");
         if (doc.layoutCustom === undefined) {
-            Doc.GetProto(dataDoc || doc).layoutNative = Doc.MakeTitled("layoutNative");
-            await swapViews(doc, "", "layoutNative");
-
             const width = NumCast(doc.width);
             const height = NumCast(doc.height);
             const options = { title: "data", width, x: -width / 2, y: - height / 2, };
@@ -326,10 +322,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: doc.title + "_layout", width: width + 20, height: Math.max(100, height + 45) });
 
             Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate), true);
-            Doc.ApplyTemplateTo(docTemplate, doc, undefined);
-            Doc.GetProto(dataDoc || doc).layoutCustom = Doc.MakeTitled("layoutCustom");
+            Doc.ApplyTemplateTo(docTemplate, dataDoc || doc, "layoutCustom", undefined);
         } else {
-            await swapViews(doc, "layoutCustom", "layoutNative");
+            doc.layoutKey = "layoutCustom";
         }
         batch.end();
     }
@@ -356,7 +351,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             DocUtils.MakeLink({ doc: de.data.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, `Link from ${StrCast(de.data.annotationDocument.title)}`);
         }
         if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) {
-            Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document);
+            Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, "layoutCustom");
             e.stopPropagation();
         }
         if (de.data instanceof DragManager.LinkDragData) {
@@ -383,12 +378,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     @action
     freezeNativeDimensions = (): void => {
-        let proto = this.Document.isTemplateDoc ? this.props.Document : Doc.GetProto(this.props.Document);
-        proto.autoHeight = this.Document.autoHeight = false;
-        proto.ignoreAspect = !proto.ignoreAspect;
-        if (!proto.ignoreAspect && !proto.nativeWidth) {
-            proto.nativeWidth = this.props.PanelWidth();
-            proto.nativeHeight = this.props.PanelHeight();
+        this.layoutDoc.autoHeight = this.layoutDoc.autoHeight = false;
+        this.layoutDoc.ignoreAspect = !this.layoutDoc.ignoreAspect;
+        if (!this.layoutDoc.ignoreAspect && !this.layoutDoc.nativeWidth) {
+            this.layoutDoc.nativeWidth = this.props.PanelWidth();
+            this.layoutDoc.nativeHeight = this.props.PanelHeight();
         }
     }
 
@@ -505,7 +499,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
         if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) {
             layoutItems.push({ description: "Use Custom Layout", event: () => DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
-        } else if (this.props.Document.layoutNative) {
+        } else {
             layoutItems.push({ description: "Use Native Layout", event: () => DocumentView.makeNativeViewClicked(this.props.Document), icon: "concierge-bell" });
         }
         !existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
@@ -599,7 +593,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return (showTitle ? 25 : 0) + 1;
     }
 
-    childScaling = () => (this.props.Document.fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
+    childScaling = () => (this.layoutDoc.fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
     @computed get contents() {
         return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
             ContainingCollectionDoc={this.props.ContainingCollectionDoc}
@@ -646,8 +640,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) :
             ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
 
-        const nativeWidth = this.props.Document.fitWidth ? this.props.PanelWidth() - 2 : this.nativeWidth > 0 && !this.Document.ignoreAspect ? `${this.nativeWidth}px` : "100%";
-        const nativeHeight = this.props.Document.fitWidth ? this.props.PanelHeight() - 2 : this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
+        const nativeWidth = this.layoutDoc.fitWidth ? this.props.PanelWidth() - 2 : this.nativeWidth > 0 && !this.layoutDoc.ignoreAspect ? `${this.nativeWidth}px` : "100%";
+        const nativeHeight = this.layoutDoc.fitWidth ? this.props.PanelHeight() - 2 : this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
         const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
         const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle");
         const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption");
@@ -686,7 +680,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.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear;
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -696,10 +690,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: StrCast(this.Document.color),
                     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,
+                    background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor,
                     width: animwidth,
                     height: animheight,
-                    transform: `scale(${this.props.Document.fitWidth ? 1 : this.props.ContentScaling()})`,
+                    transform: `scale(${this.layoutDoc.fitWidth ? 1 : this.props.ContentScaling()})`,
                     opacity: this.Document.opacity
                 }}
                 onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
@@ -707,7 +701,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             >
                 {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) =>
                     //this.linkEndpointDoc(d).type === DocumentType.PDFANNO ? (null) :
-                    <div style={{ pointerEvents: "none", position: "absolute", transformOrigin: "top left", width: "100%", height: "100%", transform: `scale(${this.props.Document.fitWidth ? 1 : 1 / this.props.ContentScaling()})` }}>
+                    <div style={{ pointerEvents: "none", position: "absolute", transformOrigin: "top left", width: "100%", height: "100%", transform: `scale(${this.layoutDoc.fitWidth ? 1 : 1 / this.props.ContentScaling()})` }}>
                         <DocumentView {...this.props} backgroundColor={returnTransparent} Document={d} layoutKey={this.linkEndpoint(d)} />
                     </div>)}
                 {!showTitle && !showCaption ?
@@ -733,33 +727,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 }
 
-export async function swapViews(doc: Doc, newLayoutField: string, oldLayoutField: string, oldLayout?: Doc) {
-    let oldLayoutExt = oldLayout || await Cast(doc[oldLayoutField], Doc);
-    if (oldLayoutExt) {
-        oldLayoutExt.autoHeight = doc.autoHeight;
-        oldLayoutExt.width = doc.width;
-        oldLayoutExt.height = doc.height;
-        oldLayoutExt.nativeWidth = doc.nativeWidth;
-        oldLayoutExt.nativeHeight = doc.nativeHeight;
-        oldLayoutExt.ignoreAspect = doc.ignoreAspect;
-        oldLayoutExt.type = doc.type;
-        oldLayoutExt.layout = doc.layout;
-    }
-
-    let newLayoutExt = newLayoutField && await Cast(doc[newLayoutField], Doc);
-    if (newLayoutExt) {
-        doc.autoHeight = newLayoutExt.autoHeight;
-        doc.width = newLayoutExt.width;
-        doc.height = newLayoutExt.height;
-        doc.nativeWidth = newLayoutExt.nativeWidth;
-        doc.nativeHeight = newLayoutExt.nativeHeight;
-        doc.ignoreAspect = newLayoutExt.ignoreAspect;
-        doc.type = newLayoutExt.type;
-        doc.layout = await newLayoutExt.layout;
-    }
-}
-
 Scripting.addGlobal(function toggleDetail(doc: any) {
-    let native = typeof doc.layout === "string";
-    swapViews(doc, native ? "layoutCustom" : "layoutNative", native ? "layoutNative" : "layoutCustom");
+    doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layoutCustom" : "layout";
 });
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ac10d729a..f866e6f7e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -271,7 +271,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     newLayout = Doc.MakeDelegate(draggedDoc);
                     newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`);
                 }
-                this.props.Document.layout = newLayout;
+                this.props.Document.layoutCustom = newLayout;
+                this.props.Document.layoutKey = "layoutCustom";
                 e.stopPropagation();
                 // embed document when dragging with a userDropAction or an embedDoc flag set
             } else if (de.data.userDropAction || de.data.embedDoc) {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 2fc4c04e6..dc80af268 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -2,7 +2,7 @@ import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEye } from '@fortawesome/free-regular-svg-icons';
 import { faAsterisk, faFileAudio, faImage, faPaintBrush } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, observable, runInAction, trace } from 'mobx';
 import { observer } from "mobx-react";
 import Lightbox from 'react-image-lightbox';
 import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
@@ -13,7 +13,7 @@ import { ComputedField } from '../../../new_fields/ScriptField';
 import { BoolCast, Cast, FieldValue, NumCast, StrCast } from '../../../new_fields/Types';
 import { AudioField, ImageField } from '../../../new_fields/URLField';
 import { RouteStore } from '../../../server/RouteStore';
-import { Utils, returnOne, emptyFunction } from '../../../Utils';
+import { Utils, returnOne, emptyFunction, OmitKeys } from '../../../Utils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
 import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
@@ -159,7 +159,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                 const files = await res.json();
                 const url = Utils.prepend(files[0].path);
                 // upload to server with known URL 
-                let audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", x: NumCast(self.props.Document.x), y: NumCast(self.props.Document.y), width: 200, height: 32 });
+                let audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", width: 200, height: 32 });
                 audioDoc.treeViewExpandedView = "layout";
                 let audioAnnos = Cast(self.extensionDoc.audioAnnotations, listSpec(Doc));
                 if (audioAnnos === undefined) {
@@ -178,18 +178,18 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
         });
     }
 
+    get layoutDoc() { return Doc.Layout(this.props.Document); }
     @undoBatch
     rotate = action(() => {
-        let proto = Doc.GetProto(this.props.Document);
-        let nw = this.props.Document.nativeWidth;
-        let nh = this.props.Document.nativeHeight;
-        let w = this.props.Document.width;
-        let h = this.props.Document.height;
-        proto.rotation = (NumCast(this.props.Document.rotation) + 90) % 360;
-        proto.nativeWidth = nh;
-        proto.nativeHeight = nw;
-        this.props.Document.width = h;
-        this.props.Document.height = w;
+        let nw = this.layoutDoc.nativeWidth;
+        let nh = this.layoutDoc.nativeHeight;
+        let w = this.layoutDoc.width;
+        let h = this.layoutDoc.height;
+        this.layoutDoc.rotation = (NumCast(this.layoutDoc.rotation) + 90) % 360;
+        this.layoutDoc.nativeWidth = nh;
+        this.layoutDoc.nativeHeight = nw;
+        this.layoutDoc.width = h;
+        this.layoutDoc.height = w;
     });
 
     specificContextMenu = (e: React.MouseEvent): void => {
@@ -251,7 +251,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
     }
 
     dots(paths: string[]) {
-        let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
+        let nativeWidth = NumCast(this.layoutDoc.nativeWidth, 1);
         let dist = Math.min(nativeWidth / paths.length, 40);
         let left = (nativeWidth - paths.length * dist) / 2;
         return paths.map((p, i) =>
@@ -379,16 +379,17 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
         return (null);
     }
 
-    @computed
-    get content() {
+    content(layoutDoc: Doc) {
+        if (!layoutDoc) return (null);
+        console.log("REDOING IMAGE CONTENT");
         // let transform = this.props.ScreenToLocalTransform().inverse();
         let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
         // var [sptX, sptY] = transform.transformPoint(0, 0);
         // let [bptX, bptY] = transform.transformPoint(pw, this.props.PanelHeight());
         // let w = bptX - sptX;
 
-        let nativeWidth = FieldValue(this.Document.nativeWidth, pw);
-        let nativeHeight = FieldValue(this.Document.nativeHeight, 0);
+        let nativeWidth = NumCast(layoutDoc.nativeWidth, pw);
+        let nativeHeight = NumCast(layoutDoc.nativeHeight, 0);
         let paths: string[] = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
         // this._curSuffix = "";
         // if (w > 20) {
@@ -401,14 +402,14 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
         if (field instanceof ImageField) paths = [this.choosePath(field.url)];
         paths.push(...altpaths);
         // }
-        let interactive = this.active() ? "-interactive" : "";// InkingControl.Instance.selectedTool || this.props.Document.isBackground ? "" : "-interactive";
-        let rotation = NumCast(this.dataDoc.rotation, 0);
-        let aspect = (rotation % 180) ? this.dataDoc[HeightSym]() / this.dataDoc[WidthSym]() : 1;
+        let interactive = InkingControl.Instance.selectedTool || this.props.Document.isBackground ? "" : "-interactive";
+        let rotation = NumCast(layoutDoc.rotation, 0);
+        let aspect = (rotation % 180) ? layoutDoc[HeightSym]() / layoutDoc[WidthSym]() : 1;
         let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
-        let srcpath = paths[Math.min(paths.length - 1, this.Document.curPage || 0)];
+        let srcpath = paths[Math.min(paths.length - 1, NumCast(layoutDoc.curPage))];
         let fadepath = paths[Math.min(paths.length - 1, 1)];
 
-        if (!this.props.Document.ignoreAspect && !this.props.leaveNativeSize) this.resize(srcpath, this.props.Document);
+        (!layoutDoc.ignoreAspect && !this.props.leaveNativeSize) && this.resize(srcpath, layoutDoc);
 
         return (
             <div className={`imageBox-cont${interactive}`} style={{ background: "transparent" }}
@@ -448,7 +449,8 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
     }
 
     render() {
-        Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
+        trace();
+        if (!Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey)) return (null);
         return (<div className={"imageBox-container"} onContextMenu={this.specificContextMenu}>
             <CollectionFreeFormView {...this.props}
                 PanelHeight={this.props.PanelHeight}
@@ -468,7 +470,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                 renderDepth={this.props.renderDepth + 1}
                 ContainingCollectionDoc={this.props.ContainingCollectionDoc}
                 chromeCollapsed={true}>
-                {() => [this.content]}
+                {() => [this.content(this.layoutDoc)]}
             </CollectionFreeFormView>
         </div >);
     }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f31128356..f3ea8328b 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
 import 'react-image-lightbox/style.css';
-import { Opt, WidthSym } from "../../../new_fields/Doc";
+import { Opt, WidthSym, Doc } from "../../../new_fields/Doc";
 import { makeInterface } from "../../../new_fields/Schema";
 import { ScriptField } from '../../../new_fields/ScriptField';
 import { Cast } from "../../../new_fields/Types";
@@ -65,11 +65,13 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
             }, { fireImmediately: true });
     }
 
+    get layoutDoc() { return Doc.Layout(this.props.Document); }
+
     loaded = (nw: number, nh: number, np: number) => {
         this.dataDoc.numPages = np;
-        this.Document.nativeWidth = nw * 96 / 72;
-        this.Document.nativeHeight = nh * 96 / 72;
-        !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw));
+        this.layoutDoc.nativeWidth = nw * 96 / 72;
+        this.layoutDoc.nativeHeight = nh * 96 / 72;
+        !this.layoutDoc.fitWidth && !this.layoutDoc.ignoreAspect && (this.layoutDoc.height = this.layoutDoc[WidthSym]() * (nh / nw));
     }
 
     public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); }
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 19968e6e1..4c01279ae 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -55,14 +55,15 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
         return this._videoRef;
     }
 
+    get layoutDoc() { return Doc.Layout(this.props.Document); }
     videoLoad = () => {
         let aspect = this.player!.videoWidth / this.player!.videoHeight;
-        var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
-        var nativeHeight = FieldValue(this.Document.nativeHeight, 0);
+        var nativeWidth = NumCast(this.layoutDoc.nativeWidth, 0);
+        var nativeHeight = NumCast(this.layoutDoc.nativeHeight, 0);
         if (!nativeWidth || !nativeHeight) {
-            if (!this.Document.nativeWidth) this.Document.nativeWidth = this.player!.videoWidth;
-            this.Document.nativeHeight = this.Document.nativeWidth / aspect;
-            this.Document.height = FieldValue(this.Document.width, 0) / aspect;
+            if (!this.layoutDoc.nativeWidth) this.layoutDoc.nativeWidth = this.player!.videoWidth;
+            this.layoutDoc.nativeHeight = NumCast(this.layoutDoc.nativeWidth) / aspect;
+            this.layoutDoc.height = NumCast(this.layoutDoc.width, 0) / aspect;
         }
         if (!this.Document.duration) this.Document.duration = this.player!.duration;
     }
@@ -107,7 +108,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
         let height = this.Document.height || 0;
         var canvas = document.createElement('canvas');
         canvas.width = 640;
-        canvas.height = 640 * (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1);
+        canvas.height = 640 * NumCast (this.layoutDoc.nativeHeight) / NumCast(this.layoutDoc.nativeWidth,1);
         var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
         if (ctx) {
             ctx.rect(0, 0, canvas.width, canvas.height);
@@ -153,12 +154,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
 
         if (this.youtubeVideoId) {
             let youtubeaspect = 400 / 315;
-            var nativeWidth = (this.Document.nativeWidth || 0);
-            var nativeHeight = (this.Document.nativeHeight || 0);
+            var nativeWidth = NumCast(this.layoutDoc.nativeWidth);
+            var nativeHeight =  NumCast(this.layoutDoc.nativeHeight);
             if (!nativeWidth || !nativeHeight) {
-                if (!this.Document.nativeWidth) this.Document.nativeWidth = 600;
-                this.Document.nativeHeight = this.Document.nativeWidth / youtubeaspect;
-                this.Document.height = (this.Document.width || 0) / youtubeaspect;
+                if (!this.layoutDoc.nativeWidth) this.layoutDoc.nativeWidth = 600;
+                this.layoutDoc.nativeHeight = NumCast(this.layoutDoc.nativeWidth) / youtubeaspect;
+                this.layoutDoc.height = NumCast(this.layoutDoc.width) / youtubeaspect;
             }
         }
     }
@@ -264,7 +265,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
     }
     private get uIButtons() {
         let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
-        let curTime = NumCast(this.props.Document.currentTimecode);
+        let curTime = NumCast(this.layoutDoc.currentTimecode);
         return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling})` }}>
             <span>{"" + Math.round(curTime)}</span>
             <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
@@ -312,7 +313,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
     @action
     onResetMove = (e: PointerEvent) => {
         this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY);
-        this.Seek(Math.max(0, NumCast(this.props.Document.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333));
+        this.Seek(Math.max(0, NumCast(this.layoutDoc.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333));
         e.stopImmediatePropagation();
     }
     @action
@@ -320,10 +321,10 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
         document.removeEventListener("pointermove", this.onResetMove, true);
         document.removeEventListener("pointerup", this.onResetUp, true);
         InkingControl.Instance.switchTool(InkTool.None);
-        this._isResetClick < 10 && (this.props.Document.currentTimecode = 0);
+        this._isResetClick < 10 && (this.layoutDoc.currentTimecode = 0);
     }
     @computed get fieldExtensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
-    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+    @computed get dataDoc() { return this.props.DataDoc && this.layoutDoc.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
 
     @computed get youtubeContent() {
         this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
@@ -331,14 +332,14 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
         let style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
         let start = untracked(() => Math.round(this.Document.currentTimecode || 0));
         return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
-            onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.Document.nativeWidth || 640)} height={(this.Document.nativeHeight || 390)}
+            onLoad={this.youtubeIframeLoaded} className={`${style}`} width={NumCast(this.layoutDoc.nativeWidth, 640)} height={NumCast(this.layoutDoc.nativeHeigh, 390)}
             src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`} />;
     }
 
     @action.bound
     addDocumentWithTimestamp(doc: Doc): boolean {
         Doc.GetProto(doc).annotationOn = this.props.Document;
-        var curTime = NumCast(this.props.Document.currentTimecode, -1);
+        var curTime = NumCast(this.layoutDoc.currentTimecode, -1);
         curTime !== -1 && (doc.displayTimecode = curTime);
         return Doc.AddDocToList(this.fieldExtensionDoc, this.props.fieldExt, doc);
     }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 7c7f9fb83..bb58912b5 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -34,17 +34,18 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
     @observable private collapsed: boolean = true;
     @observable private url: string = "";
 
+    get layoutDoc() {return Doc.Layout(this.props.Document); }
     componentWillMount() {
 
         let field = Cast(this.props.Document[this.props.fieldKey], WebField);
         if (field && field.url.href.indexOf("youtube") !== -1) {
             let youtubeaspect = 400 / 315;
-            var nativeWidth = NumCast(this.props.Document.nativeWidth, 0);
-            var nativeHeight = NumCast(this.props.Document.nativeHeight, 0);
+            var nativeWidth = NumCast(this.layoutDoc.nativeWidth);
+            var nativeHeight = NumCast(this.layoutDoc.nativeHeight);
             if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
-                if (!nativeWidth) this.props.Document.nativeWidth = 600;
-                this.props.Document.nativeHeight = NumCast(this.props.Document.nativeWidth) / youtubeaspect;
-                this.props.Document.height = NumCast(this.props.Document.width) / youtubeaspect;
+                if (!nativeWidth) this.layoutDoc.nativeWidth = 600;
+                this.layoutDoc.nativeHeight = NumCast(this.layoutDoc.nativeWidth) / youtubeaspect;
+                this.layoutDoc.height = NumCast(this.layoutDoc.width) / youtubeaspect;
             }
         }
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 6e5f1a981..51f7829e8 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -113,7 +113,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     componentDidMount = async () => {
         // change the address to be the file address of the PNG version of each page
         // file address of the pdf
-        this._coverPath = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${NumCast(this.props.Document.curPage, 1)}.PNG`)));
+        this._coverPath = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${NumCast(this.layoutDoc.curPage, 1)}.PNG`)));
         runInAction(() => this._showWaiting = this._showCover = true);
         this.props.startupLive && this.setupPdfJsViewer();
         this._searchReactionDisposer = reaction(() => StrCast(this.props.Document.search_string), searchString => {
@@ -131,14 +131,14 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
             () => (SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer(),
             { fireImmediately: true });
         this._reactionDisposer = reaction(
-            () => this.props.Document.scrollY,
+            () => this.layoutDoc.scrollY,
             (scrollY) => {
                 if (scrollY !== undefined) {
                     if (this._showCover || this._showWaiting) {
                         this.setupPdfJsViewer();
                     }
-                    this._mainCont.current && smoothScroll(1000, this._mainCont.current, NumCast(this.props.Document.scrollY) || 0);
-                    this.props.Document.scrollY = undefined;
+                    this._mainCont.current && smoothScroll(1000, this._mainCont.current, NumCast(this.layoutDoc.scrollY) || 0);
+                    this.layoutDoc.scrollY = undefined;
                 }
             },
             { fireImmediately: true }
@@ -179,7 +179,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
                     i === this.props.pdf.numPages - 1 && this.props.loaded((page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]),
                         (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]), i);
                 }))));
-            Doc.GetProto(this.props.Document).scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72;
+                this.layoutDoc.scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72;
         }
     }
 
@@ -197,7 +197,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
             { fireImmediately: true });
 
         this._filterReactionDisposer = reaction(
-            () => ({ scriptField: Cast(this.props.Document.filterScript, ScriptField), annos: this._annotations.slice() }),
+            () => ({ scriptField: Cast(this.layoutDoc.filterScript, ScriptField), annos: this._annotations.slice() }),
             action(({ scriptField, annos }: { scriptField: FieldResult<ScriptField>, annos: Doc[] }) => {
                 let oldScript = this._script.originalScript;
                 this._script = scriptField && scriptField.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript;
@@ -224,7 +224,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         document.addEventListener("copy", this.copy);
         document.addEventListener("pagesinit", action(() => {
             this._pdfViewer.currentScaleValue = this._zoomed = 1;
-            this.gotoPage(NumCast(this.props.Document.curPage, 1));
+            this.gotoPage(NumCast(this.layoutDoc.curPage, 1));
         }));
         document.addEventListener("pagerendered", action(() => this._showCover = this._showWaiting = false));
         var pdfLinkService = new PDFJSViewer.PDFLinkService();
@@ -335,7 +335,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     @action
     onScroll = (e: React.UIEvent<HTMLElement>) => {
         this._scrollTop = this._mainCont.current!.scrollTop;
-        this._pdfViewer && (this.props.Document.curPage = this._pdfViewer.currentPageNumber);
+        this._pdfViewer && (this.layoutDoc.curPage = this._pdfViewer.currentPageNumber);
     }
 
     // get the page index that the vertical offset passed in is on
@@ -403,7 +403,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         this._downX = e.clientX;
         this._downY = e.clientY;
         addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" });
-        if (NumCast(this.props.Document.scale, 1) !== 1) return;
+        if (NumCast(this.layoutDoc.scale, 1) !== 1) return;
         if ((e.button !== 0 || e.altKey) && this.active()) {
             this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true);
         }
@@ -568,16 +568,18 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         }
     }
 
+    get layoutDoc() { return Doc.Layout(this.props.Document); }
+
     createSnippet = (marquee: { left: number, top: number, width: number, height: number }): void => {
         let view = Doc.MakeAlias(this.props.Document);
         let data = Doc.MakeDelegate(Doc.GetProto(this.props.Document));
         data.title = StrCast(data.title) + "_snippet";
         view.proto = data;
         view.nativeHeight = marquee.height;
-        view.height = (this.props.Document[WidthSym]() / NumCast(this.props.Document.nativeWidth)) * marquee.height;
-        view.nativeWidth = this.props.Document.nativeWidth;
+        view.height = (this.layoutDoc[WidthSym]() / NumCast(this.layoutDoc.nativeWidth)) * marquee.height;
+        view.nativeWidth = this.layoutDoc.nativeWidth;
         view.startY = marquee.top;
-        view.width = this.props.Document[WidthSym]();
+        view.width = this.layoutDoc[WidthSym]();
         DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view]), 0, 0);
     }
 
@@ -598,12 +600,12 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     getCoverImage = () => {
         if (!this.props.Document[HeightSym]() || !this.props.Document.nativeHeight) {
             setTimeout((() => {
-                this.props.Document.height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
-                this.props.Document.nativeHeight = nativeWidth * this._coverPath.height / this._coverPath.width;
+                this.layoutDoc.height = this.layoutDoc[WidthSym]() * this._coverPath.height / this._coverPath.width;
+                this.layoutDoc.nativeHeight = nativeWidth * this._coverPath.height / this._coverPath.width;
             }).bind(this), 0);
         }
-        let nativeWidth = NumCast(this.props.Document.nativeWidth);
-        let nativeHeight = NumCast(this.props.Document.nativeHeight);
+        let nativeWidth = NumCast(this.layoutDoc.nativeWidth);
+        let nativeHeight = NumCast(this.layoutDoc.nativeHeight);
         return <img key={this._coverPath.path} src={this._coverPath.path} onError={action(() => this._coverPath.path = "http://www.cs.brown.edu/~bcz/face.gif")} onLoad={action(() => this._showWaiting = false)}
             style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />;
     }
@@ -619,14 +621,14 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     }
 
     @computed get annotationLayer() {
-        return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.props.Document.nativeHeight) }} ref={this._annotationLayer}>
+        return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.layoutDoc.nativeHeight) }} ref={this._annotationLayer}>
             {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
                 <Annotation {...this.props} focus={this.props.focus} anno={anno} key={`${anno[Id]}-annotation`} />)}
             <div className="pdfViewer-overlay" id="overlay" style={{ transform: `scale(${this._zoomed})` }}>
                 <CollectionFreeFormView {...this.props}
                     setPreviewCursor={this.setPreviewCursor}
-                    PanelHeight={() => NumCast(this.props.Document.scrollHeight, NumCast(this.props.Document.nativeHeight))}
-                    PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : NumCast(this.props.Document.nativeWidth)}
+                    PanelHeight={() => NumCast(this.layoutDoc.scrollHeight, NumCast(this.layoutDoc.nativeHeight))}
+                    PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : NumCast(this.layoutDoc.nativeWidth)}
                     VisibleHeight={this.visibleHeight}
                     focus={this.props.focus}
                     isSelected={this.props.isSelected}
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index daf000dc7..0824808eb 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -151,6 +151,7 @@ export class PresElementBox extends React.Component<FieldViewProps> {
      */
     ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
 
+    get layoutDoc() { return Doc.Layout(this.props.Document); }
     /**
      * The function that is responsible for rendering the a preview or not for this
      * presentation element.
@@ -160,12 +161,12 @@ export class PresElementBox extends React.Component<FieldViewProps> {
             return (null);
         }
 
-        let propDocWidth = NumCast(this.props.Document.nativeWidth);
-        let propDocHeight = NumCast(this.props.Document.nativeHeight);
-        let scale = () => 175 / NumCast(this.props.Document.nativeWidth, 175);
+        let propDocWidth = NumCast(this.layoutDoc.nativeWidth);
+        let propDocHeight = NumCast(this.layoutDoc.nativeHeight);
+        let scale = () => 175 / NumCast(this.layoutDoc.nativeWidth, 175);
         return (
             <div className="presElementBox-embedded" style={{
-                height: propDocHeight === 0 ? NumCast(this.props.Document.height) - NumCast(this.props.Document.collapsedHeight) : propDocHeight * scale(),
+                height: propDocHeight === 0 ? NumCast(this.layoutDoc.height) - NumCast(this.layoutDoc.collapsedHeight) : propDocHeight * scale(),
                 width: propDocWidth === 0 ? "auto" : propDocWidth * scale(),
             }}>
                 <CollectionSchemaPreview
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index b728ffaa9..899a35f48 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -211,7 +211,7 @@ export class SearchBox extends React.Component {
         });
         let x = 0;
         let y = 0;
-        for (const doc of docs) {
+        for (const doc of docs.map(d => Doc.Layout(d))) {
             doc.x = x;
             doc.y = y;
             const size = 200;
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index b8cff16f2..60307065a 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -150,7 +150,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
         if (!this._useIcons) {
             let returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE);
             let returnYDimension = () => this._displayDim;
-            let scale = () => returnXDimension() / NumCast(this.props.doc.nativeWidth, returnXDimension());
+            let scale = () => returnXDimension() / NumCast(Doc.Layout(this.props.doc).nativeWidth, returnXDimension());
             const docview = <div
                 onPointerDown={action(() => {
                     this._useIcons = !this._useIcons;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 31173a5c1..68196de45 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -424,12 +424,16 @@ export namespace Doc {
     export function UpdateDocumentExtensionForField(doc: Doc, fieldKey: string, immediate: boolean = false) {
         let docExtensionForField = doc[fieldKey + "_ext"] as Doc;
         if (docExtensionForField === undefined) {
-            if (immediate) CreateDocumentExtensionForField(doc, fieldKey);
-            else setTimeout(() => CreateDocumentExtensionForField(doc, fieldKey), 0);
-        } else if (doc instanceof Doc) { // backward compatibility -- add fields for docs that don't have them already
-            docExtensionForField.extendsDoc === undefined && setTimeout(() => docExtensionForField.extendsDoc = doc, 0);
-            docExtensionForField.type === undefined && setTimeout(() => docExtensionForField.type = DocumentType.EXTENSION, 0);
+            if (immediate) {
+                CreateDocumentExtensionForField(doc, fieldKey);
+                return true;
+            }
+            else {
+                setTimeout(() => CreateDocumentExtensionForField(doc, fieldKey), 0);
+                return false;
+            }
         }
+        return true;
     }
     export function MakeTitled(title: string) {
         let doc = new Doc();
@@ -453,7 +457,7 @@ export namespace Doc {
     // for individual layout properties to be overridden in the expanded layout.
     //
     export function WillExpandTemplateLayout(layoutDoc: Doc, dataDoc?: Doc) {
-        return BoolCast(layoutDoc.isTemplateField) && dataDoc && layoutDoc !== dataDoc && !(layoutDoc.layout instanceof Doc);
+        return BoolCast(layoutDoc.isTemplateField) && dataDoc && layoutDoc !== dataDoc && !(layoutDoc[StrCast(layoutDoc.layoutKey, "layout")] instanceof Doc);
     }
 
     //
@@ -564,13 +568,13 @@ export namespace Doc {
     let _applyCount: number = 0;
     export function ApplyTemplate(templateDoc: Doc) {
         if (templateDoc) {
-            let applied = ApplyTemplateTo(templateDoc, Doc.MakeDelegate(new Doc()), templateDoc.title + "(..." + _applyCount++ + ")");
+            let applied = ApplyTemplateTo(templateDoc, Doc.MakeDelegate(new Doc()), "layoutCustom", 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) {
+    export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined = undefined) {
         if (!templateDoc) {
             target.layout = undefined;
             target.nativeWidth = undefined;
@@ -580,21 +584,14 @@ export namespace Doc {
             return;
         }
 
-        let layoutCustom = Doc.MakeTitled("layoutCustom");
         let layoutCustomLayout = Doc.MakeDelegate(templateDoc);
 
         titleTarget && (Doc.GetProto(target).title = titleTarget);
         target.type = DocumentType.TEMPLATE;
-        target.width = templateDoc.width;
-        target.height = templateDoc.height;
-        target.nativeWidth = templateDoc.nativeWidth ? templateDoc.nativeWidth : 0;
-        target.nativeHeight = templateDoc.nativeHeight ? templateDoc.nativeHeight : 0;
-        target.ignoreAspect = templateDoc.nativeWidth ? true : false;
         target.onClick = templateDoc.onClick instanceof ObjectField && templateDoc.onClick[Copy]();
-        target.layout = layoutCustomLayout;
 
-        target.layoutNative = Cast(templateDoc.layoutNative, Doc) as Doc;
-        target.layoutCustom = layoutCustom;
+        Doc.GetProto(target)[targetKey] = layoutCustomLayout;
+        target.layoutKey = targetKey;
         return target;
     }
 
@@ -666,9 +663,9 @@ export namespace Doc {
         @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; }
+    // returns the active layout document for 'doc'.  
+    export function Layout(doc: Doc) { return Doc.LayoutField(doc) instanceof Doc ? doc[StrCast(doc.layoutKey, "layout")] as Doc : doc; }
+    export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; }
     const manager = new DocData();
     export function UserDoc(): Doc { return manager._user_doc; }
     export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 9c41c7651..532f527cc 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -71,7 +71,7 @@ export class CurrentUserUtils {
         // setup a masonry view of all he creators
         const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(doc), {
             width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons",
-            dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), yMargin: 0
+            dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), yMargin: 5
         });
         // setup a color picker
         const color = Docs.Create.ColorDocument({
-- 
cgit v1.2.3-70-g09d2


From 891b9706ddabc0a73ea6b25dc504297d6efb90fe Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 21 Oct 2019 22:03:02 -0400
Subject: big cleanup of layoutStrings, fieldExt, fieldKey, etc

---
 src/client/apis/youtube/YoutubeBox.tsx             |   2 +-
 src/client/documents/Documents.ts                  |  55 ++++----
 src/client/northstar/dash-nodes/HistogramBox.tsx   |   2 +-
 .../util/Import & Export/DirectoryImportBox.tsx    |   2 +-
 src/client/views/CollectionLinearView.tsx          |   4 +-
 src/client/views/DocComponent.tsx                  |  45 +++----
 src/client/views/DocumentDecorations.tsx           |  13 +-
 src/client/views/MainView.tsx                      |   5 +-
 .../views/collections/CollectionSchemaView.tsx     |   1 -
 .../views/collections/CollectionStackingView.tsx   |   2 -
 src/client/views/collections/CollectionSubView.tsx |   9 +-
 src/client/views/collections/CollectionView.tsx    |  18 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  15 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |  24 ++--
 src/client/views/linking/LinkFollowBox.tsx         |   2 +-
 src/client/views/nodes/AudioBox.tsx                |   8 +-
 src/client/views/nodes/ButtonBox.tsx               |  20 +--
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  18 +--
 src/client/views/nodes/ColorBox.tsx                |  10 +-
 src/client/views/nodes/DocuLinkBox.tsx             |   2 +-
 src/client/views/nodes/DocumentView.tsx            |  71 ++--------
 src/client/views/nodes/FieldView.tsx               |   5 +-
 src/client/views/nodes/FontIconBox.tsx             |   2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  27 ++--
 src/client/views/nodes/IconBox.tsx                 |   2 +-
 src/client/views/nodes/ImageBox.tsx                |  18 +--
 src/client/views/nodes/KeyValueBox.tsx             |   5 +-
 src/client/views/nodes/PDFBox.tsx                  |   8 +-
 src/client/views/nodes/PresBox.tsx                 |   2 +-
 src/client/views/nodes/QueryBox.tsx                |   2 +-
 src/client/views/nodes/VideoBox.tsx                |  40 +++---
 src/client/views/nodes/WebBox.tsx                  |   9 +-
 src/client/views/pdf/PDFViewer.tsx                 |  17 ++-
 .../views/presentationview/PresElementBox.tsx      | 148 ++++++++++-----------
 src/new_fields/Doc.ts                              |   3 +-
 src/new_fields/documentSchemas.ts                  |  51 +++++++
 36 files changed, 319 insertions(+), 348 deletions(-)
 create mode 100644 src/new_fields/documentSchemas.ts

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index d73988bb8..bed812852 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -40,7 +40,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
     @observable curVideoTemplates: VideoTemplate[] = [];
 
 
-    public static LayoutString() { return FieldView.LayoutString(YoutubeBox); }
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(YoutubeBox, fieldKey); }
 
     /**
      * When component mounts, last search's results are laoded in based on the back up stored
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 937d3c058..a400e68a3 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -84,7 +84,8 @@ export interface DocumentOptions {
     columnWidth?: number;
     fontSize?: number;
     curPage?: number;
-    currentTimecode?: number;
+    currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video)
+    displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
     documentText?: string;
     borderRounding?: string;
     boxShadow?: string;
@@ -119,11 +120,11 @@ export namespace Docs {
 
     export namespace Prototypes {
 
-        type LayoutSource = { LayoutString: (ext?: string) => string };
+        type LayoutSource = { LayoutString: (key: string) => string };
         type PrototypeTemplate = {
             layout: {
                 view: LayoutSource,
-                ext?: string, // optional extension field for layout source
+                dataField: string
             },
             options?: Partial<DocumentOptions>
         };
@@ -133,80 +134,80 @@ export namespace Docs {
 
         const TemplateMap: TemplateMap = new Map([
             [DocumentType.TEXT, {
-                layout: { view: FormattedTextBox },
+                layout: { view: FormattedTextBox, dataField: data },
                 options: { height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" }
             }],
             [DocumentType.HIST, {
-                layout: { view: HistogramBox },
+                layout: { view: HistogramBox, dataField: data },
                 options: { height: 300, backgroundColor: "black" }
             }],
             [DocumentType.QUERY, {
-                layout: { view: QueryBox },
+                layout: { view: QueryBox, dataField: data },
                 options: { width: 400 }
             }],
             [DocumentType.COLOR, {
-                layout: { view: ColorBox },
+                layout: { view: ColorBox, dataField: data },
                 options: { nativeWidth: 220, nativeHeight: 300 }
             }],
             [DocumentType.IMG, {
-                layout: { view: ImageBox },
+                layout: { view: ImageBox, dataField: data },
                 options: {}
             }],
             [DocumentType.WEB, {
-                layout: { view: WebBox },
+                layout: { view: WebBox, dataField: data },
                 options: { height: 300 }
             }],
             [DocumentType.COL, {
-                layout: { view: CollectionView },
+                layout: { view: CollectionView, dataField: data },
                 options: { panX: 0, panY: 0, scale: 1, width: 500, height: 500 }
             }],
             [DocumentType.KVP, {
-                layout: { view: KeyValueBox },
+                layout: { view: KeyValueBox, dataField: data },
                 options: { height: 150 }
             }],
             [DocumentType.VID, {
-                layout: { view: VideoBox },
+                layout: { view: VideoBox, dataField: data },
                 options: { currentTimecode: 0 },
             }],
             [DocumentType.AUDIO, {
-                layout: { view: AudioBox },
+                layout: { view: AudioBox, dataField: data },
                 options: { height: 32 }
             }],
             [DocumentType.PDF, {
-                layout: { view: PDFBox },
+                layout: { view: PDFBox, dataField: data },
                 options: { nativeWidth: 1200, curPage: 1 }
             }],
             [DocumentType.ICON, {
-                layout: { view: IconBox },
+                layout: { view: IconBox, dataField: data },
                 options: { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) },
             }],
             [DocumentType.IMPORT, {
-                layout: { view: DirectoryImportBox },
+                layout: { view: DirectoryImportBox, dataField: data },
                 options: { height: 150 }
             }],
             [DocumentType.LINKDOC, {
                 data: new List<Doc>(),
-                layout: { view: EmptyBox },
+                layout: { view: EmptyBox, dataField: data },
             }],
             [DocumentType.YOUTUBE, {
-                layout: { view: YoutubeBox }
+                layout: { view: YoutubeBox, dataField: data }
             }],
             [DocumentType.BUTTON, {
-                layout: { view: ButtonBox },
+                layout: { view: ButtonBox, dataField: data },
             }],
             [DocumentType.PRES, {
-                layout: { view: PresBox },
+                layout: { view: PresBox, dataField: data },
                 options: {}
             }],
             [DocumentType.FONTICON, {
-                layout: { view: FontIconBox },
+                layout: { view: FontIconBox, dataField: data },
                 options: { width: 40, height: 40, borderRounding: "100%" },
             }],
             [DocumentType.LINKFOLLOW, {
-                layout: { view: LinkFollowBox }
+                layout: { view: LinkFollowBox, dataField: data }
             }],
             [DocumentType.PRESELEMENT, {
-                layout: { view: PresElementBox }
+                layout: { view: PresElementBox, dataField: data }
             }],
         ]);
 
@@ -285,7 +286,7 @@ export namespace Docs {
             // synthesize the default options, the type and title from computed values and
             // whatever options pertain to this specific prototype
             let options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) };
-            options.layout = layout.view.LayoutString();
+            options.layout = layout.view.LayoutString(layout.dataField);
             return Doc.assign(new Doc(prototypeId, true), { ...options, baseLayout: options.layout });
         }
 
@@ -701,12 +702,12 @@ export namespace DocUtils {
             linkDocProto.linkDescription = description;
 
             linkDocProto.anchor1 = source.doc;
-            linkDocProto.anchor1Context = source.ctx;
-            linkDocProto.anchor1Timecode = source.doc.currentTimecode;
-            linkDocProto.anchor1Groups = new List<Doc>([]);
             linkDocProto.anchor2 = target.doc;
+            linkDocProto.anchor1Context = source.ctx;
             linkDocProto.anchor2Context = target.ctx;
+            linkDocProto.anchor1Groups = new List<Doc>([]);
             linkDocProto.anchor2Groups = new List<Doc>([]);
+            linkDocProto.anchor1Timecode = source.doc.currentTimecode;
             linkDocProto.anchor2Timecode = target.doc.currentTimecode;
             linkDocProto.layoutKey1 = DocuLinkBox.LayoutString("anchor1");
             linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2");
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index b81eafbee..854135648 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -24,7 +24,7 @@ import { Id } from "../../../new_fields/FieldSymbols";
 
 @observer
 export class HistogramBox extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(HistogramBox, fieldStr); }
+    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(HistogramBox, fieldStr); }
     private _dropXRef = React.createRef<HTMLDivElement>();
     private _dropYRef = React.createRef<HTMLDivElement>();
     private _dropXDisposer?: DragManager.DragDropDisposer;
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index d74b51993..f27d05487 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -50,7 +50,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
     @observable private uploading = false;
     @observable private removeHover = false;
 
-    public static LayoutString() { return FieldView.LayoutString(DirectoryImportBox); }
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DirectoryImportBox, fieldKey); }
 
     constructor(props: FieldViewProps) {
         super(props);
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 4e03b8c95..1f28ef35d 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -10,7 +10,8 @@ import { Transform } from '../util/Transform';
 import "./CollectionLinearView.scss";
 import { CollectionViewType } from './collections/CollectionBaseView';
 import { CollectionSubView } from './collections/CollectionSubView';
-import { documentSchema, DocumentView } from './nodes/DocumentView';
+import { DocumentView } from './nodes/DocumentView';
+import { documentSchema } from '../../new_fields/documentSchemas';
 
 
 type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -75,7 +76,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                             <DocumentView
                                 Document={pair.layout}
                                 DataDoc={pair.data}
-                                fieldKey={this.props.fieldKey}
                                 addDocument={this.props.addDocument}
                                 moveDocument={this.props.moveDocument}
                                 addDocTab={this.props.addDocTab}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 2f93d9584..ae4b7cf3a 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,48 +1,40 @@
 import * as React from 'react';
 import { Doc } from '../../new_fields/Doc';
 import { computed, action } from 'mobx';
-import { Cast, BoolCast } from '../../new_fields/Types';
+import { Cast } from '../../new_fields/Types';
 import { listSpec } from '../../new_fields/Schema';
 import { InkingControl } from './InkingControl';
 import { InkTool } from '../../new_fields/InkField';
+import { PositionDocument } from '../../new_fields/documentSchemas';
 
 
-///  DocComponents returns a generic base class for React views of document fields that are not interactive
+///  DocComponent returns a generic React base class used by views that don't have any data extensions (e.g.,CollectionFreeFormDocumentView, DocumentView, ButtonBox)
 interface DocComponentProps {
     Document: Doc;
-    DataDoc?: Doc;
-    fieldKey: string;
 }
 export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends React.Component<P> {
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
-        @computed
-        get Document(): T {
-            return schemaCtor(this.props.Document);
-        }
-        @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); }
-        @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
+        @computed get Document(): T { return schemaCtor(this.props.Document); }
+        @computed get layoutDoc() { return PositionDocument(Doc.Layout(this.props.Document)); }
     }
     return Component;
 }
 
-
-///  DocStaticProps return a base class for React views of document fields that are interactive only when selected (e.g. ColorBox)
-interface DocStaticProps {
+///  DocStaticProps return a base class for React document views that have data extensions but aren't annotatable (e.g. AudioBox, FormattedTextBox)
+interface DocExtendableProps {
     Document: Doc;
     DataDoc?: Doc;
     fieldKey: string;
     isSelected: () => boolean;
     renderDepth: number;
 }
-export function DocStaticComponent<P extends DocStaticProps, T>(schemaCtor: (doc: Doc) => T) {
+export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends React.Component<P> {
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
-        @computed
-        get Document(): T {
-            return schemaCtor(this.props.Document);
-        }
-        @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); }
+        @computed get Document(): T { return schemaCtor(this.props.Document); }
+        @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
+        @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; }
         @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
         active = () => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected() || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
     }
@@ -59,23 +51,20 @@ interface DocAnnotatableProps {
     isSelected: () => boolean;
     renderDepth: number;
 }
-export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schemaCtor: (doc: Doc) => T, fieldExt: string) {
+export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends React.Component<P> {
         _isChildActive = false;
-        _fieldExt = fieldExt;
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
-        @computed
-        get Document(): T {
-            return schemaCtor(this.props.Document);
-        }
+        @computed get Document(): T { return schemaCtor(this.props.Document); }
+        @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
         @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; }
         @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
-        @computed get fieldExt() { return this._fieldExt; }
+        @computed get annotationsKey() { return "annotations"; }
 
         @action.bound
         removeDocument(doc: Doc): boolean {
             Doc.GetProto(doc).annotationOn = undefined;
-            let value = this.extensionDoc && Cast(this.extensionDoc[this._fieldExt], listSpec(Doc), []);
+            let value = this.extensionDoc && Cast(this.extensionDoc[this.annotationsKey], listSpec(Doc), []);
             let index = value ? Doc.IndexOf(doc, value.map(d => d as Doc), true) : -1;
             return index !== -1 && value && value.splice(index, 1) ? true : false;
         }
@@ -88,7 +77,7 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
         @action.bound
         addDocument(doc: Doc): boolean {
             Doc.GetProto(doc).annotationOn = this.props.Document;
-            return this.extensionDoc && Doc.AddDocToList(this.extensionDoc, this._fieldExt, doc) ? true : false;
+            return this.extensionDoc && Doc.AddDocToList(this.extensionDoc, this.annotationsKey, doc) ? true : false;
         }
 
         whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 252f90d46..b46caf3ea 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -4,27 +4,26 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCastAsync } from "../../new_fields/Doc";
+import { PositionDocument } from '../../new_fields/documentSchemas';
 import { List } from "../../new_fields/List";
 import { ObjectField } from '../../new_fields/ObjectField';
-import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
+import { Cast, NumCast, StrCast } from "../../new_fields/Types";
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { emptyFunction, Utils } from "../../Utils";
+import { Utils } from "../../Utils";
 import { Docs, DocUtils } from "../documents/Documents";
 import { DocumentManager } from "../util/DocumentManager";
 import { DragManager } from "../util/DragManager";
 import { SelectionManager } from "../util/SelectionManager";
+import { TooltipTextMenu } from '../util/TooltipTextMenu';
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
 import { CollectionView } from "./collections/CollectionView";
 import { DocumentButtonBar } from './DocumentButtonBar';
 import './DocumentDecorations.scss';
-import { PositionDocument } from './nodes/CollectionFreeFormDocumentView';
 import { DocumentView } from "./nodes/DocumentView";
 import { FieldView } from "./nodes/FieldView";
-import { FormattedTextBox } from "./nodes/FormattedTextBox";
 import { IconBox } from "./nodes/IconBox";
 import React = require("react");
-import { TooltipTextMenu } from '../util/TooltipTextMenu';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -281,7 +280,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
 
             if (selectedDocs.length > 1) {
-                this._iconDoc = this._iconDoc ? this._iconDoc : this.createIcon(SelectionManager.SelectedDocuments(), CollectionView.LayoutString());
+                this._iconDoc = this._iconDoc ? this._iconDoc : this.createIcon(SelectionManager.SelectedDocuments(), CollectionView.LayoutString(""));
                 this.moveIconDoc(this._iconDoc);
             } else {
                 this.getIconDoc(selectedDocs[0]).then(icon => icon && this.moveIconDoc(this._iconDoc = icon));
@@ -339,7 +338,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let iconDoc: Doc | undefined = await Cast(doc.minimizedDoc, Doc);
 
         if (!iconDoc || !DocumentManager.Instance.getDocumentView(iconDoc)) {
-            const layout = StrCast(doc.layout, FieldView.LayoutString(DocumentView));
+            const layout = StrCast(doc.layout, FieldView.LayoutString(DocumentView, ""));
             iconDoc = this.createIcon([docView], layout);
         }
         return iconDoc;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d4b92a110..55a61f098 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -270,7 +270,6 @@ export class MainView extends React.Component {
                         <DocumentView Document={mainContainer}
                             DataDoc={undefined}
                             addDocument={undefined}
-                            fieldKey={"data"}
                             addDocTab={this.addDocTabFunc}
                             pinToPres={emptyFunction}
                             onClick={undefined}
@@ -360,7 +359,6 @@ export class MainView extends React.Component {
                 <DocumentView
                     Document={sidebarButtonsDoc}
                     DataDoc={undefined}
-                    fieldKey={"data"}
                     addDocument={undefined}
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
@@ -387,7 +385,6 @@ export class MainView extends React.Component {
                 <DocumentView
                     Document={sidebarContent}
                     DataDoc={undefined}
-                    fieldKey={"data"}
                     addDocument={undefined}
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
@@ -481,7 +478,7 @@ export class MainView extends React.Component {
                     Document={CurrentUserUtils.UserDocument.expandingButtons}
                     DataDoc={undefined}
                     fieldKey={"data"}
-                    fieldExt={""}
+                    annotationsKey={""}
                     select={emptyFunction}
                     chromeCollapsed={true}
                     active={returnFalse}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 04bc550ea..34f642f80 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -994,7 +994,6 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                     <DocumentView {...this.props}
                         DataDoc={this.props.DataDocument}
                         Document={this.props.Document}
-                        fieldKey={this.props.fieldKey}
                         fitToBox={this.props.fitToBox}
                         onClick={this.props.onClick}
                         ruleProvider={this.props.ruleProvider}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 5fb0c0f9f..7af56500e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -75,8 +75,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         this._heightMap.set(key, sectionHeight);
     }
 
-    get layoutDoc() { return Doc.Layout(this.props.Document); }
-
     get Sections() {
         if (!this.sectionFilter || this.sectionHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 7f16fe9e0..55365de8c 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -41,7 +41,7 @@ export interface SubCollectionViewProps extends CollectionViewProps {
     ruleProvider: Doc | undefined;
     children?: never | (() => JSX.Element[]) | React.ReactNode;
     isAnnotationOverlay?: boolean;
-    fieldExt: string;
+    annotationsKey: string;
 }
 
 export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
@@ -74,12 +74,15 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             this._childLayoutDisposer && this._childLayoutDisposer();
         }
 
+        @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); }
+        @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
+
         // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
         // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
         // to its children which may be templates.
-        // The name of the data field comes from fieldExt if it's an extension, or fieldKey otherwise.
+        // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
         @computed get dataField() {
-            return this.props.fieldExt ? (this.extensionDoc ? this.extensionDoc[this.props.fieldExt] : undefined) : this.dataDoc[this.props.fieldKey];
+            return this.props.annotationsKey ? (this.extensionDoc ? this.extensionDoc[this.props.annotationsKey] : undefined) : this.dataDoc[this.props.fieldKey];
         }
 
         get childLayoutPairs() {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index f3b0b17f8..f8eb28ade 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -32,7 +32,7 @@ library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faF
 @observer
 export class CollectionView extends React.Component<FieldViewProps> {
 
-    public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(CollectionView, fieldStr); }
+    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); }
 
     private _reactionDisposer: IReactionDisposer | undefined;
     @observable private _isLightboxOpen = false;
@@ -63,18 +63,18 @@ export class CollectionView extends React.Component<FieldViewProps> {
     private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         let props = { ...this.props, ...renderProps };
         switch (type) {
-            case CollectionViewType.Schema: return (<CollectionSchemaView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} fieldExt={""} />);
+            case CollectionViewType.Schema: return (<CollectionSchemaView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} annotationsKey={""} />);
             // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
-            case CollectionViewType.Docking: return (<CollectionDockingView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} fieldExt={""} />);
-            case CollectionViewType.Tree: return (<CollectionTreeView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} fieldExt={""} />);
-            case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} fieldExt={""} />); }
-            case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} fieldExt={""} />); }
-            case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (<CollectionFreeFormView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} fieldExt={""} />); }
-            case CollectionViewType.Linear: { return (<CollectionLinearView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} fieldExt={""} />); }
+            case CollectionViewType.Docking: return (<CollectionDockingView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} annotationsKey={""} />);
+            case CollectionViewType.Tree: return (<CollectionTreeView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} annotationsKey={""} />);
+            case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} annotationsKey={""} />); }
+            case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} annotationsKey={""} />); }
+            case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (<CollectionFreeFormView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} annotationsKey={""} />); }
+            case CollectionViewType.Linear: { return (<CollectionLinearView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} annotationsKey={""} />); }
             case CollectionViewType.Freeform:
             default:
                 this.props.Document.freeformLayoutEngine = undefined;
-                return (<CollectionFreeFormView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} fieldExt={""} />);
+                return (<CollectionFreeFormView chromeCollapsed={this._collapsed} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} annotationsKey={""} />);
         }
         return (null);
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cc89dc2d4..0419bc3fa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -25,8 +25,8 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"
 import { ContextMenu } from "../../ContextMenu";
 import { ContextMenuProps } from "../../ContextMenuItem";
 import { InkingCanvas } from "../../InkingCanvas";
-import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView";
-import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView";
+import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
+import { DocumentViewProps } from "../../nodes/DocumentView";
 import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
 import { CollectionSubView } from "../CollectionSubView";
@@ -35,6 +35,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
 import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
+import { documentSchema, positionSchema } from "../../DocComponent";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -677,13 +678,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
         // if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey.
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
-        return (
+        return !this.extensionDoc ? (null) :
             <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}
                 style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
                 onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}>
-                <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
-                    addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} setPreviewCursor={this.props.setPreviewCursor}
-                    getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
+                <MarqueeView {...this.props} extensionDoc={this.extensionDoc} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
+                    addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
                     <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
                         easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
                         {!this.extensionDoc ? (null) :
@@ -694,8 +694,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     </CollectionFreeFormViewPannableContents>
                 </MarqueeView>
                 {this.overlayViews}
-            </div>
-        );
+            </div>;
     }
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 3eaad3ad7..e0bf4a2f0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -19,23 +19,24 @@ import { CollectionViewType } from "../CollectionBaseView";
 import { CollectionFreeFormView } from "./CollectionFreeFormView";
 import "./MarqueeView.scss";
 import React = require("react");
+import { SubCollectionViewProps } from "../CollectionSubView";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
     getTransform: () => Transform;
-    container: CollectionFreeFormView;
     addDocument: (doc: Doc) => boolean;
     activeDocuments: () => Doc[];
     selectDocuments: (docs: Doc[]) => void;
     removeDocument: (doc: Doc) => boolean;
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
+    extensionDoc: Doc;
     isAnnotationOverlay?: boolean;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
 
 @observer
-export class MarqueeView extends React.Component<MarqueeViewProps>
+export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps>
 {
     private _mainCont = React.createRef<HTMLDivElement>();
     @observable _lastX: number = 0;
@@ -187,13 +188,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
 
     @action
     onPointerUp = (e: PointerEvent): void => {
-        if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]);
+        if (!this.props.active()) this.props.selectDocuments([this.props.Document]);
         if (this._visible) {
             let mselect = this.marqueeSelect();
             if (!e.shiftKey) {
-                SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
+                SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document);
             }
-            this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
+            this.props.selectDocuments(mselect.length ? mselect : [this.props.Document]);
         }
         this.cleanupInteractions(true);
 
@@ -246,12 +247,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     }
 
     get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored.
-        return this.props.container.extensionDoc && Cast(this.props.container.extensionDoc.ink, InkField);
+        return this.props.extensionDoc && Cast(this.props.extensionDoc.ink, InkField);
     }
 
     set ink(value: InkField | undefined) {
-        let cprops = this.props.container.props;
-        this.props.container.extensionDoc && (this.props.container.extensionDoc.ink = value);
+        this.props.extensionDoc && (this.props.extensionDoc.ink = value);
     }
 
     @undoBatch
@@ -290,11 +290,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             }
             let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
                 "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
-            let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string"));
-            if (!colorPalette) this.props.container.props.Document.colorPalette = new List<string>(defaultPalette);
-            let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]);
+            let colorPalette = Cast(this.props.Document.colorPalette, listSpec("string"));
+            if (!colorPalette) this.props.Document.colorPalette = new List<string>(defaultPalette);
+            let palette = Array.from(Cast(this.props.Document.colorPalette, listSpec("string")) as string[]);
             let usedPaletted = new Map<string, number>();
-            [...this.props.activeDocuments(), this.props.container.props.Document].map(child => {
+            [...this.props.activeDocuments(), this.props.Document].map(child => {
                 let bg = StrCast(Doc.Layout(child).backgroundColor);
                 if (palette.indexOf(bg) !== -1) {
                     palette.splice(palette.indexOf(bg), 1);
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 32ebe7c61..ef194624a 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -37,7 +37,7 @@ enum FollowOptions {
 @observer
 export class LinkFollowBox extends React.Component<FieldViewProps> {
 
-    public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); }
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkFollowBox, fieldKey); }
     public static Instance: LinkFollowBox | undefined;
     @observable static linkDoc: Doc | undefined = undefined;
     @observable static destinationDoc: Doc | undefined = undefined;
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 3e5deb55b..4c1c3a465 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -4,18 +4,18 @@ import { observer } from "mobx-react";
 import "./AudioBox.scss";
 import { Cast } from "../../../new_fields/Types";
 import { AudioField } from "../../../new_fields/URLField";
-import { DocStaticComponent } from "../DocComponent";
+import { DocExtendableComponent } from "../DocComponent";
 import { makeInterface } from "../../../new_fields/Schema";
-import { documentSchema } from "./DocumentView";
+import { documentSchema } from "../../../new_fields/documentSchemas";
 
 type AudioDocument = makeInterface<[typeof documentSchema]>;
 const AudioDocument = makeInterface(documentSchema);
 const defaultField: AudioField = new AudioField(new URL("http://techslides.com/demos/samples/sample.mp3"));
 
 @observer
-export class AudioBox extends DocStaticComponent<FieldViewProps, AudioDocument>(AudioDocument) {
+export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocument>(AudioDocument) {
 
-    public static LayoutString() { return FieldView.LayoutString(AudioBox); }
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); }
     _ref = React.createRef<HTMLAudioElement>();
 
     componentDidMount() {
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index b4d33fb0f..1531d825b 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -3,11 +3,11 @@ import { faEdit } from '@fortawesome/free-regular-svg-icons';
 import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCastAsync, DocListCast } from '../../../new_fields/Doc';
+import { Doc, DocListCast } from '../../../new_fields/Doc';
 import { List } from '../../../new_fields/List';
 import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';
 import { ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, StrCast, Cast } from '../../../new_fields/Types';
+import { BoolCast, StrCast, Cast, FieldValue } from '../../../new_fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
 import { DocComponent } from '../DocComponent';
@@ -15,26 +15,28 @@ import './ButtonBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ContextMenu } from '../ContextMenu';
+import { documentSchema } from '../../../new_fields/documentSchemas';
 
 
 library.add(faEdit as any);
 
 const ButtonSchema = createSchema({
     onClick: ScriptField,
+    buttonParams: listSpec("string"),
     text: "string"
 });
 
-type ButtonDocument = makeInterface<[typeof ButtonSchema]>;
-const ButtonDocument = makeInterface(ButtonSchema);
+type ButtonDocument = makeInterface<[typeof ButtonSchema, typeof documentSchema]>;
+const ButtonDocument = makeInterface(ButtonSchema, documentSchema);
 
 @observer
 export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(ButtonDocument) {
-    public static LayoutString() { return FieldView.LayoutString(ButtonBox); }
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ButtonBox, fieldKey); }
     private dropDisposer?: DragManager.DragDropDisposer;
 
     @computed get dataDoc() {
         return this.props.DataDoc &&
-            (BoolCast(this.props.Document.isTemplateField) || BoolCast(this.props.DataDoc.isTemplateField) ||
+            (this.Document.isTemplateField || BoolCast(this.props.DataDoc.isTemplateField) ||
                 this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document);
     }
 
@@ -52,7 +54,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
         let funcs: ContextMenuProps[] = [];
         funcs.push({
             description: "Clear Script Params", event: () => {
-                let params = Cast(this.props.Document.buttonParams, listSpec("string"));
+                let params = FieldValue(this.Document.buttonParams);
                 params && params.map(p => this.props.Document[p] = undefined);
             }, icon: "trash"
         });
@@ -70,13 +72,13 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
     }
     // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
     render() {
-        let params = Cast(this.props.Document.buttonParams, listSpec("string"));
+        let params = this.Document.buttonParams;
         let missingParams = params && params.filter(p => this.props.Document[p] === undefined);
         params && params.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... 
         return (
             <div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
                 style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
-                <div className="buttonBox-mainButton" style={{ background: StrCast(this.props.Document.backgroundColor), color: StrCast(this.props.Document.color, "black") }} >
+                <div className="buttonBox-mainButton" style={{ background: this.Document.backgroundColor || "", color: this.Document.color || "black" }} >
                     <div className="buttonBox-mainButtonCenter">
                         {(this.Document.text || this.Document.title)}
                     </div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e3ca02fa4..58cb831f8 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -2,14 +2,15 @@ import { random } from "animejs";
 import { computed, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
-import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { listSpec } from "../../../new_fields/Schema";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { percent2frac } from "../../../Utils";
 import { Transform } from "../../util/Transform";
 import { DocComponent } from "../DocComponent";
 import "./CollectionFreeFormDocumentView.scss";
-import { documentSchema, DocumentView, DocumentViewProps } from "./DocumentView";
+import { DocumentView, DocumentViewProps } from "./DocumentView";
 import React = require("react");
+import { PositionDocument } from "../../../new_fields/documentSchemas";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined;
@@ -20,15 +21,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     jitterRotation: number;
     transition?: string;
 }
-export const positionSchema = createSchema({
-    zIndex: "number",
-    x: "number",
-    y: "number",
-    z: "number",
-});
-
-export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>;
-export const PositionDocument = makeInterface(documentSchema, positionSchema);
 
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
@@ -92,8 +84,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     clusterColorFunc = (doc: Doc) => this.clusterColor;
 
-    get layoutDoc() { return Doc.Layout(this.props.Document); }
-
     @observable _animPos: number[] | undefined = undefined;
 
     finalPanelWidth = () => this.dataProvider ? this.dataProvider.width : this.panelWidth();
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index fdcedb3a5..fda6d64f4 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -4,20 +4,20 @@ import { SketchPicker } from 'react-color';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ColorBox.scss";
 import { InkingControl } from "../InkingControl";
-import { DocStaticComponent } from "../DocComponent";
-import { documentSchema } from "./DocumentView";
+import { DocExtendableComponent } from "../DocComponent";
 import { makeInterface } from "../../../new_fields/Schema";
-import { trace, reaction, observable, action, IReactionDisposer } from "mobx";
+import { 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 { documentSchema } from "../../../new_fields/documentSchemas";
 
 type ColorDocument = makeInterface<[typeof documentSchema]>;
 const ColorDocument = makeInterface(documentSchema);
 
 @observer
-export class ColorBox extends DocStaticComponent<FieldViewProps, ColorDocument>(ColorDocument) {
-    public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
+export class ColorBox extends DocExtendableComponent<FieldViewProps, ColorDocument>(ColorDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
 
     _selectedDisposer: IReactionDisposer | undefined;
     _penDisposer: IReactionDisposer | undefined;
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index 22e44948a..7119b0db0 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -7,11 +7,11 @@ import { Utils } from '../../../Utils';
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragLinksAsDocuments } from "../../util/DragManager";
 import { DocComponent } from "../DocComponent";
-import { documentSchema } from "./DocumentView";
 import "./DocuLinkBox.scss";
 import { FieldView, FieldViewProps } from "./FieldView";
 import React = require("react");
 import { DocumentType } from "../../documents/DocumentTypes";
+import { documentSchema } from "../../../new_fields/documentSchemas";
 
 type DocLinkSchema = makeInterface<[typeof documentSchema]>;
 const DocLinkDocument = makeInterface(documentSchema);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9e36f0811..f741dae9a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,29 +1,37 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, runInAction, trace, observable } from "mobx";
+import { action, computed, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
 import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc";
+import { Document } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
-import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
+import { listSpec } from "../../../new_fields/Schema";
 import { ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, Cast, NumCast, PromiseValue, StrCast, FieldValue } from "../../../new_fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { ImageField } from '../../../new_fields/URLField';
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { emptyFunction, returnTrue, Utils, returnTransparent, returnOne } from "../../../Utils";
+import { emptyFunction, returnTransparent, returnTrue, Utils } from "../../../Utils";
+import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from "../../documents/Documents";
+import { DocumentType } from '../../documents/DocumentTypes';
 import { ClientUtils } from '../../util/ClientUtils';
 import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { LinkManager } from '../../util/LinkManager';
+import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from "../../util/SelectionManager";
+import SharingManager from '../../util/SharingManager';
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { CollectionViewType } from '../collections/CollectionBaseView';
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionView } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
+import { DictationOverlay } from '../DictationOverlay';
 import { DocComponent } from "../DocComponent";
 import { EditableView } from '../EditableView';
 import { OverlayView } from '../OverlayView';
@@ -33,13 +41,6 @@ import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import { FormattedTextBox } from './FormattedTextBox';
 import React = require("react");
-import { DocumentType } from '../../documents/DocumentTypes';
-import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
-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);
@@ -67,7 +68,6 @@ library.add(fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCom
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
-    fieldKey: string;
     Document: Doc;
     DataDoc?: Doc;
     fitToBox?: boolean;
@@ -96,41 +96,6 @@ export interface DocumentViewProps {
     layoutKey?: string;
 }
 
-export const documentSchema = createSchema({
-    // layout: "string", // this should be a "string" or Doc, but can't do that in schemas, so best to leave it out
-    title: "string",            // document title (can be on either data document or layout)
-    nativeWidth: "number",      // native width of document which determines how much document contents are scaled when the document's width is set
-    nativeHeight: "number",     // "
-    width: "number",            // width of document in its container's coordinate system
-    height: "number",           // "
-    backgroundColor: "string",  // background color of document
-    opacity: "number",          // opacity of document
-    //links: listSpec(Doc),       // computed (readonly) list of links associated with this document
-    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 the Doc to be dropped.
-    dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
-    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
-    isTemplateField: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed
-    isBackground: "boolean",    // whether document is a background element and ignores input events (can only selet with marquee)
-    type: "string",             // enumerated type of document
-    maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) 
-    lockedPosition: "boolean",  // whether the document can be spatially manipulated
-    inOverlay: "boolean",       // whether the document is rendered in an OverlayView which handles selection/dragging differently
-    borderRounding: "string",   // border radius rounding of document
-    searchFields: "string",     // the search fields to display when this document matches a search in its metadata
-    heading: "number",          // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc)
-    showCaption: "string",      // whether editable caption text is overlayed at the bottom of the document 
-    showTitle: "string",        // whether an editable title banner is displayed at tht top of the document
-    isButton: "boolean",        // whether document functions as a button (overiding native interactions of its content)      
-    ignoreClick: "boolean",     // whether documents ignores input clicks (but does not ignore manipulation and other events) 
-});
-
-
-type Document = makeInterface<[typeof documentSchema]>;
-const Document = makeInterface(documentSchema);
 
 @observer
 export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
@@ -572,13 +537,6 @@ 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(Doc.Layout(this.props.Document));
-    }
-
     // does Document set a layout prop 
     setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)];
     // get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise.
@@ -599,7 +557,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
             ContainingCollectionDoc={this.props.ContainingCollectionDoc}
             Document={this.props.Document}
-            fieldKey={this.props.fieldKey}
             fitToBox={this.props.fitToBox}
             addDocument={this.props.addDocument}
             removeDocument={this.props.removeDocument}
@@ -729,6 +686,4 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 }
 
-Scripting.addGlobal(function toggleDetail(doc: any) {
-    doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layoutCustom" : "layout";
-});
\ No newline at end of file
+Scripting.addGlobal(function toggleDetail(doc: any) { doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layoutCustom" : "layout"; });
\ No newline at end of file
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 7d69b5b51..5108954bb 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -52,9 +52,8 @@ export interface FieldViewProps {
 
 @observer
 export class FieldView extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldType: { name: string }, fieldStr: string = "data") {
-        return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"}/>`;
-        //"<ImageBox {...props} />"
+    public static LayoutString(fieldType: { name: string }, fieldStr: string) {
+        return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"}/>`;  //e.g., "<ImageBox {...props} fieldKey={"dada} />"
     }
 
     @computed
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index fd6a475fb..ae9273639 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -17,7 +17,7 @@ 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); }
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); }
     @observable _foregroundColor = "white";
     _ref: React.RefObject<HTMLButtonElement> = React.createRef();
     _backgroundReaction: IReactionDisposer | undefined;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 995fcee17..5c2d39d98 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -33,7 +33,7 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
 import { TooltipTextMenu } from "../../util/TooltipTextMenu";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DocComponent } from "../DocComponent";
+import { DocExtendableComponent } from "../DocComponent";
 import { DocumentButtonBar } from '../DocumentButtonBar';
 import { DocumentDecorations } from '../DocumentDecorations';
 import { InkingControl } from "../InkingControl";
@@ -44,6 +44,7 @@ import React = require("react");
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ContextMenu } from '../ContextMenu';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { documentSchema } from '../../../new_fields/documentSchemas';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -62,16 +63,14 @@ const richTextSchema = createSchema({
 
 export const GoogleRef = "googleDocId";
 
-type RichTextDocument = makeInterface<[typeof richTextSchema]>;
-const RichTextDocument = makeInterface(richTextSchema);
+type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>;
+const RichTextDocument = makeInterface(richTextSchema, documentSchema);
 
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
 @observer
-export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
-    public static LayoutString(fieldStr: string = "data") {
-        return FieldView.LayoutString(FormattedTextBox, fieldStr);
-    }
+export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
+    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
     public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
     public static Instance: FormattedTextBox;
     private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
@@ -135,17 +134,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         return true;
     }
 
-    constructor(props: FieldViewProps) {
+    constructor(props: any) {
         super(props);
         FormattedTextBox.Instance = this;
     }
 
     public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
 
-    // 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 Doc.Layout(this.props.Document); }
-
     linkOnDeselect: Map<string, string> = new Map();
 
     doLinkOnDeselect() {
@@ -266,8 +261,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     newLayout = Doc.MakeDelegate(draggedDoc);
                     newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`);
                 }
-                this.props.Document.layoutCustom = newLayout;
-                this.props.Document.layoutKey = "layoutCustom";
+                this.Document.layoutCustom = newLayout;
+                this.Document.layoutKey = "layoutCustom";
                 e.stopPropagation();
                 // embed document when dragging with a userDropAction or an embedDoc flag set
             } else if (de.data.userDropAction || de.data.embedDoc) {
@@ -1015,7 +1010,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         let scrollHeight = this._ref.current ? this._ref.current.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.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
+            let nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 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;
@@ -1052,7 +1047,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 onPointerEnter={action(() => this._entered = true)}
                 onPointerLeave={action(() => this._entered = false)}
             >
-                <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.props.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
+                <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
 
                 <div className="formattedTextBox-dictation"
                     onClick={e => {
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 4971f61b7..60f547b1e 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -24,7 +24,7 @@ library.add(faFilm, faTag, faTextHeight);
 
 @observer
 export class IconBox extends React.Component<FieldViewProps> {
-    public static LayoutString() { return FieldView.LayoutString(IconBox); }
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(IconBox, fieldKey); }
 
     @observable _panelWidth: number = 0;
     @observable _panelHeight: number = 0;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 4d623a04f..4c2f8e22d 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -20,12 +20,12 @@ import { ContextMenu } from "../../views/ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocAnnotatableComponent } from '../DocComponent';
 import { InkingControl } from '../InkingControl';
-import { documentSchema } from './DocumentView';
 import FaceRectangles from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { documentSchema } from '../../../new_fields/documentSchemas';
 var requestImageSize = require('../../util/request-image-size');
 var path = require('path');
 const { Howl } = require('howler');
@@ -38,7 +38,9 @@ library.add(faFileAudio, faAsterisk);
 export const pageSchema = createSchema({
     curPage: "number",
     fitWidth: "boolean",
-    rotation: "number"
+    rotation: "number",
+    googlePhotosUrl: "string",
+    googlePhotosTags: "string"
 });
 
 interface Window {
@@ -54,8 +56,8 @@ type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>;
 const ImageDocument = makeInterface(pageSchema, documentSchema);
 
 @observer
-export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument, "annotations") {
-    public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(ImageBox, fieldKey); }
+export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
     private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
     private _dropDisposer?: DragManager.DragDropDisposer;
     @observable private _audioState = 0;
@@ -251,7 +253,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
     audioDown = () => this.recordAudioAnnotation();
 
     considerGooglePhotosLink = () => {
-        const remoteUrl = StrCast(this.props.Document.googlePhotosUrl);
+        const remoteUrl = this.Document.googlePhotosUrl;
         return !remoteUrl ? (null) : (<img
             id={"google-photos"}
             src={"/assets/google_photos.png"}
@@ -260,7 +262,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
     }
 
     considerGooglePhotosTags = () => {
-        const tags = StrCast(this.props.Document.googlePhotosTags);
+        const tags = this.Document.googlePhotosTags;
         return !tags ? (null) : (<img id={"google-tags"} src={"/assets/google_tags.png"} />);
     }
 
@@ -287,7 +289,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
         if (field instanceof ImageField) paths = [this.choosePath(field.url)];
         paths.push(...altpaths);
         // }
-        let interactive = InkingControl.Instance.selectedTool || this.props.Document.isBackground ? "" : "-interactive";
+        let interactive = InkingControl.Instance.selectedTool || this.Document.isBackground ? "" : "-interactive";
         let rotation = NumCast(this.Document.rotation, 0);
         let aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
         let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
@@ -333,7 +335,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
             <CollectionFreeFormView {...this.props}
                 PanelHeight={this.props.PanelHeight}
                 PanelWidth={this.props.PanelWidth}
-                fieldExt={this.fieldExt}
+                annotationsKey={this.annotationsKey}
                 isAnnotationOverlay={true}
                 focus={this.props.focus}
                 isSelected={this.props.isSelected}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 2b46e9dff..35e9e4862 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -25,11 +25,12 @@ export type KVPScript = {
 
 @observer
 export class KeyValueBox extends React.Component<FieldViewProps> {
+    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(KeyValueBox, fieldStr); }
+
     private _mainCont = React.createRef<HTMLDivElement>();
     private _keyHeader = React.createRef<HTMLTableHeaderCellElement>();
-    @observable private rows: KeyValuePair[] = [];
 
-    public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(KeyValueBox, fieldStr); }
+    @observable private rows: KeyValuePair[] = [];
     @observable private _keyInput: string = "";
     @observable private _valueInput: string = "";
     @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index b1110daf4..396a5356a 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -16,18 +16,18 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocAnnotatableComponent } from "../DocComponent";
 import { PDFViewer } from "../pdf/PDFViewer";
-import { documentSchema } from "./DocumentView";
 import { FieldView, FieldViewProps } from './FieldView';
 import { pageSchema } from "./ImageBox";
 import "./PDFBox.scss";
 import React = require("react");
+import { documentSchema } from '../../../new_fields/documentSchemas';
 
 type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
 const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
 
 @observer
-export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>(PdfDocument, "annotations") {
-    public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(PDFBox, fieldKey); }
+export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>(PdfDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); }
     private _keyValue: string = "";
     private _valueValue: string = "";
     private _scriptValue: string = "";
@@ -232,7 +232,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
                     pinToPres={this.props.pinToPres} addDocument={this.addDocument}
                     ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
                     isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
-                    fieldKey={this.props.fieldKey} extensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} />
+                    fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 ? true : false} />
                 {this.settingsPanel()}
             </div>);
     }
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 15fafb022..7ec5d0471 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -31,7 +31,7 @@ library.add(faEdit);
 
 @observer
 export class PresBox extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(PresBox, fieldKey); }
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
     _docListChangedReaction: IReactionDisposer | undefined;
     componentDidMount() {
         this._docListChangedReaction = reaction(() => {
diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx
index ced597b59..99b5810fc 100644
--- a/src/client/views/nodes/QueryBox.tsx
+++ b/src/client/views/nodes/QueryBox.tsx
@@ -18,7 +18,7 @@ library.add(faEdit);
 
 @observer
 export class QueryBox extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(QueryBox, fieldKey); }
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); }
     _docListChangedReaction: IReactionDisposer | undefined;
     componentDidMount() {
     }
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 64871ef41..48a699e58 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,33 +1,32 @@
 import React = require("react");
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, trace } from "mobx";
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faVideo } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from 'request-promise';
+import { Doc } from "../../../new_fields/Doc";
 import { InkTool } from "../../../new_fields/InkField";
-import { makeInterface, createSchema, listSpec } from "../../../new_fields/Schema";
-import { Cast, FieldValue, NumCast, BoolCast, StrCast } from "../../../new_fields/Types";
+import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { Cast, StrCast } from "../../../new_fields/Types";
 import { VideoField } from "../../../new_fields/URLField";
 import { RouteStore } from "../../../server/RouteStore";
-import { Utils, emptyFunction, returnOne } from "../../../Utils";
+import { emptyFunction, returnOne, Utils } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { DocAnnotatableComponent } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
-import { documentSchema } from "./DocumentView";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./VideoBox.scss";
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faVideo } from "@fortawesome/free-solid-svg-icons";
-import { Doc } from "../../../new_fields/Doc";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { positionSchema } from "./CollectionFreeFormDocumentView";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas";
 var path = require('path');
 
 export const timeSchema = createSchema({
-    currentTimecode: "number",
+    currentTimecode: "number",  // the current time of a video or other linear, time-based document.  Note, should really get set on an extension field, but that's more complicated when it needs to be set since the extension doc needs to be found first
 });
 type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>;
 const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
@@ -35,7 +34,7 @@ const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
 library.add(faVideo);
 
 @observer
-export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument, "annotations") {
+export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) {
     static _youtubeIframeCounter: number = 0;
     private _reactionDisposer?: IReactionDisposer;
     private _youtubeReactionDisposer?: IReactionDisposer;
@@ -49,7 +48,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
     @observable _fullScreen = false;
     @observable _playing = false;
     @observable static _showControls: boolean;
-    public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(VideoBox, fieldKey); }
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); }
 
     public get player(): HTMLVideoElement | null {
         return this._videoRef;
@@ -282,24 +281,20 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
         ]]);
     }
 
-    @action
-    onPlayDown = () => this._playing ? this.Pause() : this.Play()
+    onPlayDown = () => this._playing ? this.Pause() : this.Play();
 
-    @action
     onFullDown = (e: React.PointerEvent) => {
         this.FullScreen();
         e.stopPropagation();
         e.preventDefault();
     }
 
-    @action
     onSnapshot = (e: React.PointerEvent) => {
         this.Snapshot();
         e.stopPropagation();
         e.preventDefault();
     }
 
-    @action
     onResetDown = (e: React.PointerEvent) => {
         this.Pause();
         e.stopPropagation();
@@ -309,12 +304,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
         InkingControl.Instance.switchTool(InkTool.Eraser);
     }
 
-    @action
     onResetMove = (e: PointerEvent) => {
         this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY);
         this.Seek(Math.max(0, (this.Document.currentTimecode || 0) + Math.sign(e.movementX) * 0.0333));
         e.stopImmediatePropagation();
     }
+
     @action
     onResetUp = (e: PointerEvent) => {
         document.removeEventListener("pointermove", this.onResetMove, true);
@@ -322,7 +317,6 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
         InkingControl.Instance.switchTool(InkTool.None);
         this._isResetClick < 10 && (this.Document.currentTimecode = 0);
     }
-    @computed get dataDoc() { return this.props.DataDoc && this.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
 
     @computed get youtubeContent() {
         this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
@@ -346,7 +340,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
             <CollectionFreeFormView {...this.props}
                 PanelHeight={this.props.PanelHeight}
                 PanelWidth={this.props.PanelWidth}
-                fieldExt={this.fieldExt}
+                annotationsKey={this.annotationsKey}
                 focus={this.props.focus}
                 isSelected={this.props.isSelected}
                 isAnnotationOverlay={true}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 2184325a9..5af743859 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -19,8 +19,8 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { KeyValueBox } from "./KeyValueBox";
 import "./WebBox.scss";
 import React = require("react");
-import { documentSchema } from "./DocumentView";
 import { DocAnnotatableComponent } from "../DocComponent";
+import { documentSchema } from "../../../new_fields/documentSchemas";
 
 library.add(faStickyNote);
 
@@ -28,13 +28,12 @@ type WebDocument = makeInterface<[typeof documentSchema]>;
 const WebDocument = makeInterface(documentSchema);
 
 @observer
-export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument, "annotations") {
+export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
 
-    public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(WebBox, fieldKey); }
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
     @observable private collapsed: boolean = true;
     @observable private url: string = "";
 
-    get layoutDoc() { return Doc.Layout(this.props.Document); }
     componentWillMount() {
 
         let field = Cast(this.props.Document[this.props.fieldKey], WebField);
@@ -199,7 +198,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
             <CollectionFreeFormView {...this.props}
                 PanelHeight={this.props.PanelHeight}
                 PanelWidth={this.props.PanelWidth}
-                fieldExt={this.fieldExt}
+                annotationsKey={this.annotationsKey}
                 focus={this.props.focus}
                 isSelected={this.props.isSelected}
                 isAnnotationOverlay={true}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 5806dec83..51dc6e8d6 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -24,8 +24,8 @@ import { CollectionFreeFormView } from "../collections/collectionFreeForm/Collec
 import { SelectionManager } from "../../util/SelectionManager";
 import { undoBatch } from "../../util/UndoManager";
 import { DocAnnotatableComponent } from "../DocComponent";
-import { documentSchema } from "../nodes/DocumentView";
 import { DocumentType } from "../../documents/DocumentTypes";
+import { documentSchema } from "../../../new_fields/documentSchemas";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 const pdfjsLib = require("pdfjs-dist");
 
@@ -49,7 +49,6 @@ interface IViewerProps {
     Document: Doc;
     DataDoc?: Doc;
     ContainingCollectionView: Opt<CollectionView>;
-    extensionDoc: Doc;
     PanelWidth: () => number;
     PanelHeight: () => number;
     ContentScaling: () => number;
@@ -73,7 +72,7 @@ interface IViewerProps {
  * Handles rendering and virtualization of the pdf
  */
 @observer
-export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument>(PdfDocument, "annotations") {
+export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument>(PdfDocument) {
     static _annotationStyle: any = addStyleSheet();
     @observable private _pageSizes: { width: number, height: number }[] = [];
     @observable private _annotations: Doc[] = [];
@@ -109,8 +108,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     private _coverPath: any;
 
     @computed get allAnnotations() {
-        return DocListCast(this.props.extensionDoc.annotations).filter(
-            anno => this._script.run({ this: anno }, console.log, true).result);
+        return this.extensionDoc ? DocListCast(this.extensionDoc.annotations).filter(
+            anno => this._script.run({ this: anno }, console.log, true).result) : [];
     }
 
     @computed get nonDocAnnotations() {
@@ -200,7 +199,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         await this.initialLoad();
 
         this._annotationReactionDisposer = reaction(
-            () => this.props.extensionDoc && DocListCast(this.props.extensionDoc.annotations),
+            () => this.extensionDoc && DocListCast(this.extensionDoc.annotations),
             annotations => annotations && annotations.length && this.renderAnnotations(annotations, true),
             { fireImmediately: true });
 
@@ -629,10 +628,10 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     @computed get annotationLayer() {
         return <div className="pdfViewer-annotationLayer" style={{ height: (this.Document.nativeHeight || 0) }} ref={this._annotationLayer}>
             {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
-                <Annotation {...this.props} focus={this.props.focus} anno={anno} key={`${anno[Id]}-annotation`} />)}
+                <Annotation {...this.props} focus={this.props.focus} extensionDoc={this.extensionDoc!} anno={anno} key={`${anno[Id]}-annotation`} />)}
             <div className="pdfViewer-overlay" id="overlay" style={{ transform: `scale(${this._zoomed})` }}>
                 <CollectionFreeFormView {...this.props}
-                    fieldExt={this.fieldExt}
+                    annotationsKey={this.annotationsKey}
                     setPreviewCursor={this.setPreviewCursor}
                     PanelHeight={() => (this.Document.scrollHeight || this.Document.nativeHeight || 0)}
                     PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document.nativeWidth || 0)}
@@ -673,7 +672,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     marqueeing = () => this._marqueeing;
     visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96;
     render() {
-        return (<div className={"pdfViewer-viewer" + (this._zoomed !== 1 ? "-zoomed" : "")}
+        return !this.extensionDoc ? (null) : (<div className={"pdfViewer-viewer" + (this._zoomed !== 1 ? "-zoomed" : "")}
             onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} ref={this._mainCont}>
             {this.pdfViewerDiv}
             {this.annotationLayer}
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 0824808eb..5f758f496 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -5,18 +5,19 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { documentSchema } from '../../../new_fields/documentSchemas';
 import { Id } from "../../../new_fields/FieldSymbols";
-import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnOne } from "../../../Utils";
+import { createSchema, makeInterface } from '../../../new_fields/Schema';
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, returnFalse } from "../../../Utils";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { Transform } from "../../util/Transform";
 import { CollectionViewType } from '../collections/CollectionBaseView';
-import { DocumentView } from "../nodes/DocumentView";
+import { CollectionSchemaPreview } from '../collections/CollectionSchemaView';
+import { DocComponent } from '../DocComponent';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import "./PresElementBox.scss";
 import React = require("react");
-import { CollectionSchemaPreview } from '../collections/CollectionSchemaView';
-
 
 library.add(faArrowUp);
 library.add(fileSolid);
@@ -24,34 +25,34 @@ library.add(faLocationArrow);
 library.add(fileRegular as any);
 library.add(faSearch);
 library.add(faArrowDown);
+
+export const presSchema = createSchema({
+    presentationTargetDoc: Doc,
+    presBox: Doc,
+    presBoxKey: "string",
+    showButton: "boolean",
+    navButton: "boolean",
+    hideTillShownButton: "boolean",
+    fadeButton: "boolean",
+    hideAfterButton: "boolean",
+    groupButton: "boolean",
+    embedOpen: "boolean"
+});
+
+type PresDocument = makeInterface<[typeof presSchema, typeof documentSchema]>;
+const PresDocument = makeInterface(presSchema, documentSchema);
 /**
  * This class models the view a document added to presentation will have in the presentation.
  * It involves some functionality for its buttons and options.
  */
 @observer
-export class PresElementBox extends React.Component<FieldViewProps> {
+export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(PresDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
 
-    public static LayoutString() { return FieldView.LayoutString(PresElementBox); }
-
-    @computed get myIndex() { return DocListCast(this.presentationDoc[this.presentationFieldKey]).indexOf(this.props.Document); }
-    @computed get presentationDoc() { return this.props.Document.presBox as Doc; }
-    @computed get presentationFieldKey() { return StrCast(this.props.Document.presBoxKey); }
+    @computed get indexInPres() { return DocListCast(this.presentationDoc[this.Document.presBoxKey || ""]).indexOf(this.props.Document); }
+    @computed get presentationDoc() { return Cast(this.Document.presBox, Doc) as Doc; }
+    @computed get targetDoc() { return this.Document.presentationTargetDoc as Doc }
     @computed get currentIndex() { return NumCast(this.presentationDoc.selectedDoc); }
-    @computed get showButton() { return BoolCast(this.props.Document.showButton); }
-    @computed get navButton() { return BoolCast(this.props.Document.navButton); }
-    @computed get hideTillShownButton() { return BoolCast(this.props.Document.hideTillShownButton); }
-    @computed get fadeButton() { return BoolCast(this.props.Document.fadeButton); }
-    @computed get hideAfterButton() { return BoolCast(this.props.Document.hideAfterButton); }
-    @computed get groupButton() { return BoolCast(this.props.Document.groupButton); }
-    @computed get embedOpen() { return BoolCast(this.props.Document.embedOpen); }
-
-    set embedOpen(value: boolean) { this.props.Document.embedOpen = value; }
-    set showButton(val: boolean) { this.props.Document.showButton = val; }
-    set navButton(val: boolean) { this.props.Document.navButton = val; }
-    set hideTillShownButton(val: boolean) { this.props.Document.hideTillShownButton = val; }
-    set fadeButton(val: boolean) { this.props.Document.fadeButton = val; }
-    set hideAfterButton(val: boolean) { this.props.Document.hideAfterButton = val; }
-    set groupButton(val: boolean) { this.props.Document.groupButton = val; }
 
     /**
      * The function that is called on click to turn Hiding document till press option on/off.
@@ -60,14 +61,14 @@ export class PresElementBox extends React.Component<FieldViewProps> {
     @action
     onHideDocumentUntilPressClick = (e: React.MouseEvent) => {
         e.stopPropagation();
-        this.hideTillShownButton = !this.hideTillShownButton;
-        if (!this.hideTillShownButton) {
-            if (this.myIndex >= this.currentIndex) {
-                (this.props.Document.presentationTargetDoc as Doc).opacity = 1;
+        this.Document.hideTillShownButton = !this.Document.hideTillShownButton;
+        if (!this.Document.hideTillShownButton) {
+            if (this.indexInPres >= this.currentIndex && this.targetDoc) {
+                this.targetDoc.opacity = 1;
             }
         } else {
-            if (this.presentationDoc.presStatus && this.myIndex > this.currentIndex) {
-                (this.props.Document.presentationTargetDoc as Doc).opacity = 0;
+            if (this.presentationDoc.presStatus && this.indexInPres > this.currentIndex && this.targetDoc) {
+                this.targetDoc.opacity = 0;
             }
         }
     }
@@ -80,15 +81,15 @@ export class PresElementBox extends React.Component<FieldViewProps> {
     @action
     onHideDocumentAfterPresentedClick = (e: React.MouseEvent) => {
         e.stopPropagation();
-        this.hideAfterButton = !this.hideAfterButton;
-        if (!this.hideAfterButton) {
-            if (this.myIndex <= this.currentIndex) {
-                (this.props.Document.presentationTargetDoc as Doc).opacity = 1;
+        this.Document.hideAfterButton = !this.Document.hideAfterButton;
+        if (!this.Document.hideAfterButton) {
+            if (this.indexInPres <= this.currentIndex && this.targetDoc) {
+                this.targetDoc.opacity = 1;
             }
         } else {
-            if (this.fadeButton) this.fadeButton = false;
-            if (this.presentationDoc.presStatus && this.myIndex < this.currentIndex) {
-                (this.props.Document.presentationTargetDoc as Doc).opacity = 0;
+            if (this.Document.fadeButton) this.Document.fadeButton = false;
+            if (this.presentationDoc.presStatus && this.indexInPres < this.currentIndex && this.targetDoc) {
+                this.targetDoc.opacity = 0;
             }
         }
     }
@@ -101,15 +102,15 @@ export class PresElementBox extends React.Component<FieldViewProps> {
     @action
     onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => {
         e.stopPropagation();
-        this.fadeButton = !this.fadeButton;
-        if (!this.fadeButton) {
-            if (this.myIndex <= this.currentIndex) {
-                (this.props.Document.presentationTargetDoc as Doc).opacity = 1;
+        this.Document.fadeButton = !this.Document.fadeButton;
+        if (!this.Document.fadeButton) {
+            if (this.indexInPres <= this.currentIndex && this.targetDoc) {
+                this.targetDoc.opacity = 1;
             }
         } else {
-            this.hideAfterButton = false;
-            if (this.presentationDoc.presStatus && (this.myIndex < this.currentIndex)) {
-                (this.props.Document.presentationTargetDoc as Doc).opacity = 0.5;
+            this.Document.hideAfterButton = false;
+            if (this.presentationDoc.presStatus && (this.indexInPres < this.currentIndex) && this.targetDoc) {
+                this.targetDoc.opacity = 0.5;
             }
         }
     }
@@ -120,10 +121,10 @@ export class PresElementBox extends React.Component<FieldViewProps> {
     @action
     onNavigateDocumentClick = (e: React.MouseEvent) => {
         e.stopPropagation();
-        this.navButton = !this.navButton;
-        if (this.navButton) {
-            this.showButton = false;
-            if (this.currentIndex === this.myIndex) {
+        this.Document.navButton = !this.Document.navButton;
+        if (this.Document.navButton) {
+            this.Document.showButton = false;
+            if (this.currentIndex === this.indexInPres) {
                 this.props.focus(this.props.Document);
             }
         }
@@ -136,12 +137,12 @@ export class PresElementBox extends React.Component<FieldViewProps> {
     onZoomDocumentClick = (e: React.MouseEvent) => {
         e.stopPropagation();
 
-        this.showButton = !this.showButton;
-        if (!this.showButton) {
+        this.Document.showButton = !this.Document.showButton;
+        if (!this.Document.showButton) {
             this.props.Document.viewScale = 1;
         } else {
-            this.navButton = false;
-            if (this.currentIndex === this.myIndex) {
+            this.Document.navButton = false;
+            if (this.currentIndex === this.indexInPres) {
                 this.props.focus(this.props.Document);
             }
         }
@@ -151,13 +152,12 @@ export class PresElementBox extends React.Component<FieldViewProps> {
      */
     ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
 
-    get layoutDoc() { return Doc.Layout(this.props.Document); }
     /**
      * The function that is responsible for rendering the a preview or not for this
      * presentation element.
      */
     renderEmbeddedInline = () => {
-        if (!this.embedOpen || !(this.props.Document.presentationTargetDoc instanceof Doc)) {
+        if (!this.Document.embedOpen || !this.targetDoc) {
             return (null);
         }
 
@@ -170,8 +170,9 @@ export class PresElementBox extends React.Component<FieldViewProps> {
                 width: propDocWidth === 0 ? "auto" : propDocWidth * scale(),
             }}>
                 <CollectionSchemaPreview
-                    fitToBox={StrCast(this.props.Document.presentationTargetDoc.type).indexOf(DocumentType.COL) !== -1}
-                    Document={this.props.Document.presentationTargetDoc}
+                    fitToBox={StrCast(this.targetDoc.type).indexOf(DocumentType.COL) !== -1}
+                    Document={this.targetDoc}
+                    fieldKey={this.props.fieldKey}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     ruleProvider={undefined}
@@ -193,30 +194,27 @@ export class PresElementBox extends React.Component<FieldViewProps> {
     }
 
     render() {
-        let p = this.props;
-
         let treecontainer = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.viewType === CollectionViewType.Tree;
-        let className = "presElementBox-item" + (this.currentIndex === this.myIndex ? " presElementBox-selected" : "");
+        let className = "presElementBox-item" + (this.currentIndex === this.indexInPres ? " presElementBox-selected" : "");
         let pbi = "presElementBox-interaction";
         return (
-            <div className={className} key={p.Document[Id] + this.myIndex}
-                style={{ outlineWidth: Doc.IsBrushed(p.Document.presentationTargetDoc as Doc) ? `1px` : "0px", }}
-                onClick={e => { p.focus(p.Document); e.stopPropagation(); }}>
+            <div className={className} key={this.props.Document[Id] + this.indexInPres}
+                style={{ outlineWidth: Doc.IsBrushed(this.targetDoc) ? `1px` : "0px", }}
+                onClick={e => { this.props.focus(this.props.Document); e.stopPropagation(); }}>
                 {treecontainer ? (null) : <>
                     <strong className="presElementBox-name">
-                        {`${this.myIndex + 1}. ${p.Document.title}`}
+                        {`${this.indexInPres + 1}. ${this.Document.title}`}
                     </strong>
-                    <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument && this.props.removeDocument(p.Document)}>X</button>
+                    <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument && this.props.removeDocument(this.props.Document)}>X</button>
                     <br />
-                </>
-                }
-                <button title="Zoom" className={pbi + (this.showButton ? "-selected" : "")} onPointerDown={(e) => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button>
-                <button title="Navigate" className={pbi + (this.navButton ? "-selected" : "")} onPointerDown={(e) => e.stopPropagation()} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button>
-                <button title="Hide Before" className={pbi + (this.hideTillShownButton ? "-selected" : "")} onPointerDown={(e) => e.stopPropagation()} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button>
-                <button title="Fade After" className={pbi + (this.fadeButton ? "-selected" : "")} onPointerDown={(e) => e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
-                <button title="Hide After" className={pbi + (this.hideAfterButton ? "-selected" : "")} onPointerDown={(e) => e.stopPropagation()} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
-                <button title="Group With Up" className={pbi + (this.groupButton ? "-selected" : "")} onPointerDown={(e) => e.stopPropagation()} onClick={action((e: any) => { e.stopPropagation(); this.groupButton = !this.groupButton; })}><FontAwesomeIcon icon={"arrow-up"} /></button>
-                <button title="Expand Inline" className={pbi + (this.embedOpen ? "-selected" : "")} onPointerDown={(e) => e.stopPropagation()} onClick={action((e: any) => { e.stopPropagation(); this.embedOpen = !this.embedOpen; })}><FontAwesomeIcon icon={"arrow-down"} /></button>
+                </>}
+                <button title="Zoom" className={pbi + (this.Document.showButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button>
+                <button title="Navigate" className={pbi + (this.Document.navButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button>
+                <button title="Hide Before" className={pbi + (this.Document.hideTillShownButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button>
+                <button title="Fade After" className={pbi + (this.Document.fadeButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
+                <button title="Hide After" className={pbi + (this.Document.hideAfterButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
+                <button title="Group With Up" className={pbi + (this.Document.groupButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.Document.groupButton = !this.Document.groupButton; }}><FontAwesomeIcon icon={"arrow-up"} /></button>
+                <button title="Expand Inline" className={pbi + (this.Document.embedOpen ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.Document.embedOpen = !this.Document.embedOpen; }}><FontAwesomeIcon icon={"arrow-down"} /></button>
 
                 <br style={{ lineHeight: 0.1 }} />
                 {this.renderEmbeddedInline()}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index c6f654c33..08cb66d5f 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -645,7 +645,8 @@ export namespace Doc {
         @observable BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
     }
 
-    // returns the active layout document for 'doc'.  
+    // 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  
     export function Layout(doc: Doc) { return Doc.LayoutField(doc) instanceof Doc ? doc[StrCast(doc.layoutKey, "layout")] as Doc : doc; }
     export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; }
     const manager = new DocData();
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
new file mode 100644
index 000000000..8c3b62067
--- /dev/null
+++ b/src/new_fields/documentSchemas.ts
@@ -0,0 +1,51 @@
+import { makeInterface, createSchema, listSpec } from "./Schema";
+import { ScriptField } from "./ScriptField";
+import { Doc } from "./Doc";
+
+export const documentSchema = createSchema({
+    // layout: "string", // this should be a "string" or Doc, but can't do that in schemas, so best to leave it out
+    layoutKey: "string",        // holds the field key for the field that actually holds the current lyoat
+    layoutCustom: Doc,          // used to hold a custom layout (there's nothing special about this field .. any field could hold a custom layout that can be selected by setting 'layoutKey')
+    title: "string",            // document title (can be on either data document or layout)
+    nativeWidth: "number",      // native width of document which determines how much document contents are scaled when the document's width is set
+    nativeHeight: "number",     // "
+    width: "number",            // width of document in its container's coordinate system
+    height: "number",           // "
+    color: "string",            // foreground color of document
+    backgroundColor: "string",  // background color of document
+    opacity: "number",          // opacity of document
+    //links: listSpec(Doc),       // computed (readonly) list of links associated with this document
+    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 the Doc to be dropped.
+    dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
+    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
+    isTemplateField: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed
+    isBackground: "boolean",    // whether document is a background element and ignores input events (can only selet with marquee)
+    type: "string",             // enumerated type of document
+    maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) 
+    lockedPosition: "boolean",  // whether the document can be spatially manipulated
+    inOverlay: "boolean",       // whether the document is rendered in an OverlayView which handles selection/dragging differently
+    borderRounding: "string",   // border radius rounding of document
+    searchFields: "string",     // the search fields to display when this document matches a search in its metadata
+    heading: "number",          // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc)
+    showCaption: "string",      // whether editable caption text is overlayed at the bottom of the document 
+    showTitle: "string",        // whether an editable title banner is displayed at tht top of the document
+    isButton: "boolean",        // whether document functions as a button (overiding native interactions of its content)      
+    ignoreClick: "boolean",     // whether documents ignores input clicks (but does not ignore manipulation and other events) 
+});
+
+export const positionSchema = createSchema({
+    zIndex: "number",
+    x: "number",
+    y: "number",
+    z: "number",
+});
+
+export type Document = makeInterface<[typeof documentSchema]>;
+export const Document = makeInterface(documentSchema);
+
+export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>;
+export const PositionDocument = makeInterface(documentSchema, positionSchema);
-- 
cgit v1.2.3-70-g09d2


From b7353705ee06292e570c9847d72287190f3f42ed Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Tue, 29 Oct 2019 19:09:04 -0400
Subject: started implementing ink select with document decorations

---
 src/client/util/SelectionManager.ts                | 30 +++++++++++
 src/client/views/DocumentDecorations.tsx           | 57 ++++++++++++++-------
 src/client/views/InkSelectDecorations.scss         |  5 ++
 src/client/views/InkSelectDecorations.tsx          | 59 ++++++++++++++++++++++
 src/client/views/MainView.tsx                      |  2 +
 src/client/views/Touchable.tsx                     |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  5 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 18 +++++--
 8 files changed, 152 insertions(+), 26 deletions(-)
 create mode 100644 src/client/views/InkSelectDecorations.scss
 create mode 100644 src/client/views/InkSelectDecorations.tsx

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 2d717ca57..3ae43e029 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -11,6 +11,7 @@ export namespace SelectionManager {
 
         @observable IsDragging: boolean = false;
         @observable SelectedDocuments: Array<DocumentView> = [];
+        @observable SelectedInk: Array<Map<any, any>> = [];
 
 
         @action
@@ -41,6 +42,20 @@ export namespace SelectionManager {
         DeselectAll(): void {
             manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false));
             manager.SelectedDocuments = [];
+            manager.SelectedInk = [];
+        }
+
+        @action
+        SelectInk(ink: Map<any, any>, ctrlPressed: boolean): void {
+            if (manager.SelectedInk.indexOf(ink) === -1) {
+                if (!ctrlPressed) {
+                    this.DeselectAll();
+                }
+
+                manager.SelectedInk.push(ink);
+            } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) {
+                manager.SelectedInk = [ink];
+            }
         }
     }
 
@@ -53,6 +68,10 @@ export namespace SelectionManager {
         manager.SelectDoc(docView, ctrlPressed);
     }
 
+    export function SelectInk(ink: Map<any, any>, ctrlPressed: boolean): void {
+        manager.SelectInk(ink, ctrlPressed);
+    }
+
     export function IsSelected(doc: DocumentView): boolean {
         return manager.SelectedDocuments.indexOf(doc) !== -1;
     }
@@ -75,4 +94,15 @@ export namespace SelectionManager {
     export function SelectedDocuments(): Array<DocumentView> {
         return manager.SelectedDocuments.slice();
     }
+
+    export function SelectedInk(): Array<Map<any, any>> {
+        return manager.SelectedInk.slice();
+    }
+
+    export function AllSelected(): Array<DocumentView | Map<any, any>> {
+        let arr: Array<DocumentView | Map<any, any>> = [];
+        arr = SelectionManager.SelectedDocuments();
+        arr.push(...SelectionManager.SelectedInk());
+        return arr;
+    }
 }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3d73f048d..90d6e1e8d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -25,6 +25,8 @@ import { FormattedTextBox } from "./nodes/FormattedTextBox";
 import { IconBox } from "./nodes/IconBox";
 import React = require("react");
 import { TooltipTextMenu } from '../util/TooltipTextMenu';
+import { InkingCanvas } from './InkingCanvas';
+import { StrokeData } from '../../new_fields/InkField';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -166,23 +168,42 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @computed
     get Bounds(): { x: number, y: number, b: number, r: number } {
         let x = this._forceUpdate;
-        this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
-            if (documentView.props.renderDepth === 0 ||
-                Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) {
-                return bounds;
+        this._lastBox = SelectionManager.AllSelected().reduce((bounds, docViewOrInk) => {
+            if (docViewOrInk instanceof DocumentView) {
+                if (docViewOrInk.props.renderDepth === 0 ||
+                    Doc.AreProtosEqual(docViewOrInk.props.Document, CurrentUserUtils.UserDocument)) {
+                    return bounds;
+                }
+                let transform = (docViewOrInk.props.ScreenToLocalTransform().scale(docViewOrInk.props.ContentScaling())).inverse();
+                if (transform.TranslateX === 0 && transform.TranslateY === 0) {
+                    setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds
+                    return this._lastBox;
+                }
+
+                var [sptX, sptY] = transform.transformPoint(0, 0);
+                let [bptX, bptY] = transform.transformPoint(docViewOrInk.props.PanelWidth(), docViewOrInk.props.PanelHeight());
+                return {
+                    x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
+                    r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
+                };
             }
-            let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();
-            if (transform.TranslateX === 0 && transform.TranslateY === 0) {
-                setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds
-                return this._lastBox;
+            else {
+                let left = bounds.x;
+                let top = bounds.y;
+                let right = bounds.r;
+                let bottom = bounds.b;
+                docViewOrInk.forEach((value: StrokeData, key: string) => {
+                    value.pathData.map(val => {
+                        left = Math.min(val.x, left);
+                        top = Math.min(val.y, top);
+                        right = Math.max(val.x, right);
+                        bottom = Math.max(val.y, bottom);
+                    });
+                });
+                return {
+                    x: left, y: top, r: right, b: bottom
+                };
             }
-
-            var [sptX, sptY] = transform.transformPoint(0, 0);
-            let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
-            return {
-                x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
-                r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
-            };
         }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
         return this._lastBox;
     }
@@ -559,7 +580,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
     render() {
         var bounds = this.Bounds;
-        let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
+        let seldoc = SelectionManager.AllSelected().length ? SelectionManager.AllSelected()[0] : undefined;
         if (bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
             return (null);
         }
@@ -586,7 +607,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 left: bounds.x - this._resizeBorderWidth / 2,
                 top: bounds.y - this._resizeBorderWidth / 2,
                 pointerEvents: this.Interacting ? "none" : "all",
-                zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0,
+                zIndex: SelectionManager.AllSelected().length > 1 ? 900 : 0,
             }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
             </div>
             <div className="documentDecorations-container" ref={this.setTextBar} style={{
@@ -615,7 +636,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-borderRadius" className="documentDecorations-radius" onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}><span className="borderRadiusTooltip" title="Drag Corner Radius"></span></div>
                 <div className="link-button-container">
-                    <DocumentButtonBar views={SelectionManager.SelectedDocuments()} />
+                    {(SelectionManager.SelectedDocuments.length && SelectionManager.SelectedDocuments()[0]) ? <DocumentButtonBar views={SelectionManager.SelectedDocuments()} /> : (null)}
                 </div>
             </div >
         </div>
diff --git a/src/client/views/InkSelectDecorations.scss b/src/client/views/InkSelectDecorations.scss
new file mode 100644
index 000000000..daff58fd6
--- /dev/null
+++ b/src/client/views/InkSelectDecorations.scss
@@ -0,0 +1,5 @@
+.inkSelectDecorations {
+    position: absolute;
+    border: black 1px solid;
+    z-index: 9001;
+}
\ No newline at end of file
diff --git a/src/client/views/InkSelectDecorations.tsx b/src/client/views/InkSelectDecorations.tsx
new file mode 100644
index 000000000..a8eef3305
--- /dev/null
+++ b/src/client/views/InkSelectDecorations.tsx
@@ -0,0 +1,59 @@
+import React = require("react");
+import { Touchable } from "./Touchable";
+import { StrokeData } from "../../new_fields/InkField";
+import { observer } from "mobx-react";
+import { computed, observable, action, runInAction } from "mobx";
+import "./InkSelectDecorations.scss"
+
+@observer
+export default class InkSelectDecorations extends Touchable {
+    static Instance: InkSelectDecorations;
+
+    @observable private _selectedInkNodes: Map<any, any> = new Map();
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+
+        InkSelectDecorations.Instance = this;
+    }
+
+    @action
+    public SetSelected = (inkNodes: Map<any, any>, keepOld: boolean = false) => {
+        if (!keepOld) {
+            this._selectedInkNodes = new Map();
+        }
+        inkNodes.forEach((value: any, key: any) => {
+            runInAction(() => this._selectedInkNodes.set(key, value));
+        });
+    }
+
+    @computed
+    get Bounds(): { x: number, y: number, b: number, r: number } {
+        let left = Number.MAX_VALUE;
+        let top = Number.MAX_VALUE;
+        let right = -Number.MAX_VALUE;
+        let bottom = -Number.MAX_VALUE;
+        this._selectedInkNodes.forEach((value: StrokeData, key: string) => {
+            value.pathData.map(val => {
+                left = Math.min(val.x, left);
+                top = Math.min(val.y, top);
+                right = Math.max(val.x, right);
+                bottom = Math.max(val.y, bottom);
+            });
+        });
+        return { x: left, y: top, b: bottom, r: right };
+    }
+
+    render() {
+        let bounds = this.Bounds;
+        return (
+            <div style={{
+                top: bounds.y, left: bounds.x,
+                height: bounds.b - bounds.y,
+                width: bounds.r - bounds.x
+            }}>
+
+            </div>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0ede0b770..41de32f1f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -37,6 +37,7 @@ import { OverlayView } from './OverlayView';
 import PDFMenu from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
 import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
+import InkSelectDecorations from './InkSelectDecorations';
 
 @observer
 export class MainView extends React.Component {
@@ -512,6 +513,7 @@ export class MainView extends React.Component {
             <SharingManager />
             <GoogleAuthenticationManager />
             <DocumentDecorations />
+            <InkSelectDecorations />
             {this.mainContent}
             <PreviewCursor />
             <ContextMenu />
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index 4955129ba..26779b168 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
 import { action } from 'mobx';
 import { InteractionUtils } from '../util/InteractionUtils';
 
-export abstract class Touchable<T> extends React.Component<T> {
+export abstract class Touchable<T = {}> extends React.Component<T> {
     protected _touchDrag: boolean = false;
     protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 123941b03..c24e52aba 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -103,9 +103,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         added && this.updateCluster(newBox);
         return added;
     }
-    private selectDocuments = (docs: Doc[]) => {
+    private selectDocuments = (docs: Doc[], ink: Map<any, any>[]) => {
         SelectionManager.DeselectAll();
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
+        ink.forEach(i => SelectionManager.SelectInk(i, true));
     }
     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); }
 
@@ -190,7 +191,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
             // 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);
+            this.selectDocuments(eles, []);
             let clusterDocs = SelectionManager.SelectedDocuments();
             SelectionManager.DeselectAll();
             prevSelected.map(dv => SelectionManager.SelectDoc(dv, true));
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 07db6354f..cd9ac7ecc 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -20,6 +20,7 @@ import { CollectionFreeFormView } from "./CollectionFreeFormView";
 import "./MarqueeView.scss";
 import React = require("react");
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
+import InkSelectDecorations from "../../InkSelectDecorations";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -27,7 +28,7 @@ interface MarqueeViewProps {
     container: CollectionFreeFormView;
     addDocument: (doc: Doc) => boolean;
     activeDocuments: () => Doc[];
-    selectDocuments: (docs: Doc[]) => void;
+    selectDocuments: (docs: Doc[], ink: Map<any, any>[]) => void;
     removeDocument: (doc: Doc) => boolean;
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
@@ -190,13 +191,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
 
     @action
     onPointerUp = (e: PointerEvent): void => {
-        if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]);
+        if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document], []);
         if (this._visible) {
             let mselect = this.marqueeSelect();
             if (!e.shiftKey) {
                 SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
             }
-            this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
+            this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document],
+                this.ink ? [this.marqueeInkSelect(this.ink.inkData)] : []);
         }
         if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) {
             MarqueeOptionsMenu.Instance.createCollection = this.collection;
@@ -350,7 +352,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         }
         let newCollection = this.getCollection(selected);
         this.props.addDocument(newCollection);
-        this.props.selectDocuments([newCollection]);
+        this.props.selectDocuments([newCollection], []);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
     }
@@ -422,9 +424,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2);
         ink.forEach((value: StrokeData, key: string, map: any) => {
             if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) {
+                // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling());
                 idata.set(key,
                     {
-                        pathData: value.pathData.map(val => ({ x: val.x + centerShiftX, y: val.y + centerShiftY })),
+                        pathData: value.pathData.map(val => {
+                            let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y);
+                            return { x: tVal[0], y: tVal[1] };
+                            // return { x: val.x + centerShiftX, y: val.y + centerShiftY }
+                        }),
                         color: value.color,
                         width: value.width,
                         tool: value.tool,
@@ -432,6 +439,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     });
             }
         });
+        // InkSelectDecorations.Instance.SetSelected(idata);
         return idata;
     }
 
-- 
cgit v1.2.3-70-g09d2


From ca406ddce0a9b35ca9063f74979c952e080339f1 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Sun, 3 Nov 2019 16:59:13 -0500
Subject: some infrastructure stuff and bug fixes. not really any new features
 yet

---
 src/client/util/SelectionManager.ts                | 13 +++++-----
 src/client/views/DocumentDecorations.tsx           | 29 ++++++++++++++--------
 src/client/views/InkingStroke.tsx                  |  6 +++++
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 12 ++++++---
 src/client/views/nodes/DocumentContentsView.tsx    |  4 ++-
 7 files changed, 46 insertions(+), 22 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 3ae43e029..2a57c67bd 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -4,6 +4,7 @@ import { DocumentView } from "../views/nodes/DocumentView";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { NumCast, StrCast } from "../../new_fields/Types";
 import { InkingControl } from "../views/InkingControl";
+import { InkDocAndStroke } from "../views/InkingStroke";
 
 export namespace SelectionManager {
 
@@ -11,7 +12,7 @@ export namespace SelectionManager {
 
         @observable IsDragging: boolean = false;
         @observable SelectedDocuments: Array<DocumentView> = [];
-        @observable SelectedInk: Array<Map<any, any>> = [];
+        @observable SelectedInk: Array<{ Document: Doc, Ink: Map<any, any> }> = [];
 
 
         @action
@@ -46,7 +47,7 @@ export namespace SelectionManager {
         }
 
         @action
-        SelectInk(ink: Map<any, any>, ctrlPressed: boolean): void {
+        SelectInk(ink: { Document: Doc, Ink: Map<any, any> }, ctrlPressed: boolean): void {
             if (manager.SelectedInk.indexOf(ink) === -1) {
                 if (!ctrlPressed) {
                     this.DeselectAll();
@@ -68,7 +69,7 @@ export namespace SelectionManager {
         manager.SelectDoc(docView, ctrlPressed);
     }
 
-    export function SelectInk(ink: Map<any, any>, ctrlPressed: boolean): void {
+    export function SelectInk(ink: { Document: Doc, Ink: Map<any, any> }, ctrlPressed: boolean): void {
         manager.SelectInk(ink, ctrlPressed);
     }
 
@@ -95,12 +96,12 @@ export namespace SelectionManager {
         return manager.SelectedDocuments.slice();
     }
 
-    export function SelectedInk(): Array<Map<any, any>> {
+    export function SelectedInk(): Array<{ Document: Doc, Ink: Map<any, any> }> {
         return manager.SelectedInk.slice();
     }
 
-    export function AllSelected(): Array<DocumentView | Map<any, any>> {
-        let arr: Array<DocumentView | Map<any, any>> = [];
+    export function AllSelected(): Array<DocumentView | InkDocAndStroke> {
+        let arr: Array<DocumentView | InkDocAndStroke> = [];
         arr = SelectionManager.SelectedDocuments();
         arr.push(...SelectionManager.SelectedInk());
         return arr;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index cdb49350f..c3020eacb 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -186,8 +186,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 let top = bounds.y;
                 let right = bounds.r;
                 let bottom = bounds.b;
-                docViewOrInk.forEach((value: StrokeData, key: string) => {
+                let ink;
+                docViewOrInk.Ink.forEach((value: StrokeData, key: string) => {
                     value.pathData.map(val => {
+                        ink = docViewOrInk.Document.ink;
                         left = Math.min(val.x, left);
                         top = Math.min(val.y, top);
                         right = Math.max(val.x, right);
@@ -224,7 +226,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         document.removeEventListener("pointerup", this.onBackgroundUp);
         document.removeEventListener("pointermove", this.onTitleMove);
         document.removeEventListener("pointerup", this.onTitleUp);
-        DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, {
+        DragManager.StartDocumentDrag(SelectionManager.AllSelected().map(docOrInk => docOrInk instanceof DocumentView ? docOrInk.ContentDiv! : (document.createElement("div"))), dragData, e.x, e.y, {
             handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) },
             hideSource: true
         });
@@ -548,15 +550,21 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
     @computed
     get selectionTitle(): string {
-        if (SelectionManager.SelectedDocuments().length === 1) {
-            let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey];
-            if (typeof field === "string") {
-                return field;
+        if (SelectionManager.AllSelected().length === 1) {
+            let selected = SelectionManager.AllSelected()[0];
+            if (selected instanceof DocumentView) {
+                let field = selected.props.Document[this._fieldKey];
+                if (typeof field === "string") {
+                    return field;
+                }
+                else if (typeof field === "number") {
+                    return field.toString();
+                }
             }
-            else if (typeof field === "number") {
-                return field.toString();
+            else {
+                return "-ink strokes-";
             }
-        } else if (SelectionManager.SelectedDocuments().length > 1) {
+        } else if (SelectionManager.AllSelected().length > 1) {
             return "-multiple-";
         }
         return "-unset-";
@@ -581,7 +589,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         }
         let minimizeIcon = (
             <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>
-                {SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
+                {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
+                {(SelectionManager.SelectedDocuments().length === 1 && SelectionManager.SelectedInk().length === 0) ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
             </div>);
 
         bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 332c22512..a097a7991 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -5,6 +5,7 @@ import React = require("react");
 import { InkTool } from "../../new_fields/InkField";
 import "./InkingStroke.scss";
 import { AudioBox } from "./nodes/AudioBox";
+import { Doc } from "../../new_fields/Doc";
 
 
 interface StrokeProps {
@@ -20,6 +21,11 @@ interface StrokeProps {
     deleteCallback: (index: string) => void;
 }
 
+export type InkDocAndStroke = {
+    Document: Doc;
+    Ink: Map<any, any>;
+};
+
 @observer
 export class InkingStroke extends React.Component<StrokeProps> {
 
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 937a4949e..3ff99b9f4 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -617,7 +617,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
     }
 
-    get layoutDoc() { return this._document && Doc.Layout(this._document);}
+    get layoutDoc() { return this._document && Doc.Layout(this._document); }
     panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc.width), NumCast(this.layoutDoc.nativeWidth)), this._panelWidth) : this._panelWidth;
     panelHeight = () => this._panelHeight;
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5e2973368..9acffc952 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -107,7 +107,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         added && this.updateCluster(newBox);
         return added;
     }
-    private selectDocuments = (docs: Doc[], ink: Map<any, any>[]) => {
+    private selectDocuments = (docs: Doc[], ink: { Document: Doc, Ink: Map<any, any> }[]) => {
         SelectionManager.DeselectAll();
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
         ink.forEach(i => SelectionManager.SelectInk(i, true));
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 414238ccd..138168fed 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -28,7 +28,7 @@ interface MarqueeViewProps {
     getTransform: () => Transform;
     addDocument: (doc: Doc) => boolean;
     activeDocuments: () => Doc[];
-    selectDocuments: (docs: Doc[], ink: Map<any, any>[]) => void;
+    selectDocuments: (docs: Doc[], ink: { Document: Doc, Ink: Map<any, any> }[]) => void;
     removeDocument: (doc: Doc) => boolean;
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
@@ -198,8 +198,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             if (!e.shiftKey) {
                 SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document);
             }
-            this.props.selectDocuments(mselect.length ? mselect : [this.props.Document],
-                this.ink ? [this.marqueeInkSelect(this.ink.inkData)] : []);
+            let inkselect = this.ink ? this.marqueeInkSelect(this.ink.inkData) : new Map();
+            let inks = inkselect.size ? [{ Document: this.inkDoc, Ink: inkselect }] : [];
+            let docs = mselect.length ? mselect : (inks.length ? [] : [this.props.Document]);
+            this.props.selectDocuments(docs, inks);
         }
         if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) {
             MarqueeOptionsMenu.Instance.createCollection = this.collection;
@@ -266,6 +268,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
     }
 
+    get inkDoc() {
+        return this.props.extensionDoc;
+    }
+
     get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored.
         return this.props.extensionDoc && Cast(this.props.extensionDoc.ink, InkField);
     }
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 779d25cdd..96271cfe1 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -30,6 +30,7 @@ import { DocuLinkBox } from "./DocuLinkBox";
 import { PresElementBox } from "../presentationview/PresElementBox";
 import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
+import { InkingStroke } from "../InkingStroke";
 import React = require("react");
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -97,7 +98,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                 components={{
                     FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, FieldView,
                     CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
-                    PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox, ColorBox, DocuLinkBox
+                    PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox,
+                    ColorBox, DocuLinkBox
                 }}
                 bindings={this.CreateBindings()}
                 jsx={this.layout}
-- 
cgit v1.2.3-70-g09d2


From 5c6dc8fb25c2ac65a9efa534ee86211ac6d68301 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 8 Nov 2019 15:14:56 -0500
Subject: moved schemaPreview into ContentFittingDOcumentView.  fixed dragging
 link anchors

---
 src/client/views/DocumentDecorations.tsx           |   2 +-
 .../views/collections/CollectionSchemaView.scss    |  22 ---
 .../views/collections/CollectionSchemaView.tsx     | 203 ++++-----------------
 .../views/collections/CollectionStackingView.tsx   |   6 +-
 .../views/collections/CollectionTreeView.tsx       |   4 +-
 .../views/nodes/ContentFittingDocumentView.scss    |  23 +++
 .../views/nodes/ContentFittingDocumentView.tsx     | 117 ++++++++++++
 src/client/views/nodes/DocumentView.tsx            |  84 +++++----
 .../views/presentationview/PresElementBox.tsx      |   4 +-
 9 files changed, 223 insertions(+), 242 deletions(-)
 create mode 100644 src/client/views/nodes/ContentFittingDocumentView.scss
 create mode 100644 src/client/views/nodes/ContentFittingDocumentView.tsx

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b46caf3ea..0336440d5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -556,7 +556,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     render() {
         var bounds = this.Bounds;
         let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
-        if (bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
+        if (SelectionManager.GetIsDragging() || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
             return (null);
         }
         let minimizeIcon = (
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index 36c6c7b0e..cb95dcbbc 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -39,28 +39,6 @@
     }
 }
 
-.collectionSchemaView-previewRegion {
-    position: relative;
-    height: auto !important;
-
-    .collectionSchemaView-previewDoc {
-        position: absolute;
-        display: inline;
-    }
-
-    .collectionSchemaView-input {
-        position: absolute;
-        max-width: 150px;
-        width: 100%;
-        bottom: 0px;
-    }
-
-    .documentView-node:first-child {
-        position: relative;
-        background: $light-color;
-    }
-}
-
 .ReactTable {
     width: 100%;
     background: white;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b840dc5f8..203c68463 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1,36 +1,34 @@
 import React = require("react");
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCog, faPlus, faTable, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons';
+import { faCog, faPlus, faSortDown, faSortUp, faTable } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, trace, untracked } from "mobx";
+import { action, computed, observable, untracked } from "mobx";
 import { observer } from "mobx-react";
-import ReactTable, { CellInfo, ComponentPropsGetterR, Column, RowInfo, ResizedChangeFunction, Resize, SortingRule } from "react-table";
+import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table";
 import "react-table/react-table.css";
-import { emptyFunction, returnOne, returnEmptyString } from "../../../Utils";
 import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
-import { Docs, DocumentOptions } from "../../documents/Documents";
+import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { Docs, DocumentOptions } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
 import { Gateway } from "../../northstar/manager/Gateway";
-import { DragManager } from "../../util/DragManager";
-import { CompileScript, ts, Transformer } from "../../util/Scripting";
+import { CompileScript, Transformer, ts } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
+import { undoBatch } from "../../util/UndoManager";
 import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
 import { ContextMenu } from "../ContextMenu";
 import '../DocumentDecorations.scss';
-import { DocumentView } from "../nodes/DocumentView";
+import { CellProps, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDocCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells";
+import { CollectionSchemaAddColumnHeader, CollectionSchemaHeader } from "./CollectionSchemaHeaders";
+import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
 import "./CollectionSchemaView.scss";
 import { CollectionSubView } from "./CollectionSubView";
 import { CollectionView } from "./CollectionView";
-import { undoBatch } from "../../util/UndoManager";
-import { CollectionSchemaHeader, CollectionSchemaAddColumnHeader } from "./CollectionSchemaHeaders";
-import { CellProps, CollectionSchemaCell, CollectionSchemaNumberCell, CollectionSchemaStringCell, CollectionSchemaBooleanCell, CollectionSchemaCheckboxCell, CollectionSchemaDocCell } from "./CollectionSchemaCells";
-import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
-import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
-import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { DocumentType } from "../../documents/DocumentTypes";
+import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
 
 library.add(faCog, faPlus, faSortUp, faSortDown);
 library.add(faTable);
@@ -72,14 +70,14 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
         super.CreateDropTarget(ele);
     }
 
-    isFocused = (doc: Doc): boolean => !this.props.isSelected() ? false : doc === this._focusedTable;
+    isFocused = (doc: Doc): boolean => this.props.isSelected() && doc === this._focusedTable;
 
     @action setFocused = (doc: Doc) => this._focusedTable = doc;
 
     @action setPreviewDoc = (doc: Doc) => this.previewDoc = doc;
 
     @undoBatch
-    @action setPreviewScript = (script: string) => this.previewScript = script;
+    @action setPreviewScript = (script: string) => this.previewScript = script
 
     //toggles preview side-panel of schema
     @action
@@ -123,9 +121,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
 
     @computed
     get previewDocument(): Doc | undefined {
-        let selected = this.previewDoc;
-        let pdc = selected ? (this.previewScript && this.previewScript !== "this" ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
-        return pdc;
+        return this.previewDoc ? (this.previewScript && this.previewScript !== "this" ? FieldValue(Cast(this.previewDoc[this.previewScript], Doc)) : this.previewDoc) : undefined;
     }
 
     getPreviewTransform = (): Transform => {
@@ -142,7 +138,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     get previewPanel() {
         let layoutDoc = this.previewDocument ? Doc.expandTemplateLayout(this.previewDocument, this.props.DataDoc) : undefined;
         return <div ref={this.createTarget}>
-            <CollectionSchemaPreview
+            <ContentFittingDocumentView
                 Document={layoutDoc}
                 DataDocument={this.previewDocument !== this.props.DataDoc ? this.props.DataDoc : undefined}
                 fieldKey={this.props.fieldKey}
@@ -298,11 +294,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
         let tableIsFocused = this.props.isFocused(this.props.Document);
         let focusedRow = this._focusedCell.row;
         let focusedCol = this._focusedCell.col;
-        let isEditable = !this._headerIsEditing;// && this.props.isSelected();
-
-        let children = this.childDocs;
+        let isEditable = !this._headerIsEditing;
 
-        if (children.reduce((found, doc) => found || doc.type === "collection", false)) {
+        if (this.childDocs.reduce((found, doc) => found || doc.type === "collection", false)) {
             columns.push(
                 {
                     expander: true,
@@ -423,14 +417,12 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     }
 
     private getTdProps: ComponentPropsGetterR = (state, rowInfo, column, instance) => {
-        if (!rowInfo) return {};
-        if (!column) return {};
+        if (!rowInfo || column) return {};
 
         let row = rowInfo.index;
         //@ts-ignore
         let col = this.columns.map(c => c.heading).indexOf(column!.id);
         let isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document);
-        let isEditing = this.props.isFocused(this.props.Document) && this._cellIsEditing;
         // TODO: editing border doesn't work :(
         return {
             style: {
@@ -439,21 +431,20 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
         };
     }
 
-    @action onExpandCollection = (collection: Doc) => this._openCollections.push(collection[Id]);
-
     @action
     onCloseCollection = (collection: Doc): void => {
         let index = this._openCollections.findIndex(col => col === collection[Id]);
         if (index > -1) this._openCollections.splice(index, 1);
     }
 
+    @action onExpandCollection = (collection: Doc) => this._openCollections.push(collection[Id]);
     @action setCellIsEditing = (isEditing: boolean) => this._cellIsEditing = isEditing;
     @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing;
 
     onPointerDown = (e: React.PointerEvent): void => {
         this.props.setFocused(this.props.Document);
-        if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
-            if (this.props.isSelected()) e.stopPropagation();
+        if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey && this.props.isSelected()) {
+            e.stopPropagation();
         }
     }
 
@@ -497,19 +488,12 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     @action
     createColumn = () => {
         let index = 0;
-        let columns = this.columns;
-        let found = columns.findIndex(col => col.heading.toUpperCase() === "New field".toUpperCase()) > -1;
-        if (!found) {
-            columns.push(new SchemaHeaderField("New field", "#f1efeb"));
-            this.columns = columns;
-            return;
-        }
+        let found = this.columns.findIndex(col => col.heading.toUpperCase() === "New field".toUpperCase()) > -1;
         while (found) {
             index++;
-            found = columns.findIndex(col => col.heading.toUpperCase() === ("New field (" + index + ")").toUpperCase()) > -1;
+            found = this.columns.findIndex(col => col.heading.toUpperCase() === ("New field (" + index + ")").toUpperCase()) > -1;
         }
-        columns.push(new SchemaHeaderField("New field (" + index + ")", "#f1efeb"));
-        this.columns = columns;
+        this.columns.push(new SchemaHeaderField(`New field ${index ? "(" + index + ")" : ""}`, "#f1efeb"));
     }
 
     @undoBatch
@@ -602,7 +586,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     }
 
     @action
-    setColumns = (columns: SchemaHeaderField[]) => this.columns = columns;
+    setColumns = (columns: SchemaHeaderField[]) => this.columns = columns
 
     @undoBatch
     reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => {
@@ -648,11 +632,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
         let textWrapped = this.textWrappedRows;
         let index = textWrapped.findIndex(id => doc[Id] === id);
 
-        if (index > -1) {
-            textWrapped.splice(index, 1);
-        } else {
-            textWrapped.push(doc[Id]);
-        }
+        index > -1 ? textWrapped.splice(index, 1) : textWrapped.push(doc[Id]);
 
         this.textWrappedRows = textWrapped;
     }
@@ -682,13 +662,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
             expanded={expanded}
             resized={this.resized}
             onResizedChange={this.onResizedChange}
-            SubComponent={hasCollectionChild ?
-                row => {
-                    if (row.original.type === "collection") {
-                        return <div className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} childDocs={undefined} /></div>;
-                    }
-                }
-                : undefined}
+            SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== "collection") ? (null) :
+                <div className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} childDocs={undefined} /></div>}
 
         />;
     }
@@ -804,125 +779,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     }
 
     render() {
-        return <div className="collectionSchemaView-table" onPointerDown={this.onPointerDown} onWheel={e => this.props.active() && e.stopPropagation()}
-            onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
+        return <div className="collectionSchemaView-table" onPointerDown={this.onPointerDown} onWheel={e => this.props.active() && e.stopPropagation()} onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
             {this.reactTable}
             <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
         </div>;
     }
-}
-
-
-interface CollectionSchemaPreviewProps {
-    Document?: Doc;
-    DataDocument?: Doc;
-    childDocs?: Doc[];
-    renderDepth: number;
-    fitToBox?: boolean;
-    fieldKey: string;
-    PanelWidth: () => number;
-    PanelHeight: () => number;
-    ruleProvider: Doc | undefined;
-    focus?: (doc: Doc) => void;
-    showOverlays?: (doc: Doc) => { title?: string, caption?: string };
-    CollectionView?: CollectionView;
-    CollectionDoc?: Doc;
-    onClick?: ScriptField;
-    getTransform: () => Transform;
-    addDocument: (document: Doc) => boolean;
-    moveDocument: (document: Doc, target: Doc, addDoc: ((doc: Doc) => boolean)) => boolean;
-    removeDocument: (document: Doc) => boolean;
-    active: () => boolean;
-    whenActiveChanged: (isActive: boolean) => void;
-    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
-    pinToPres: (document: Doc) => void;
-    setPreviewScript: (script: string) => void;
-    previewScript?: string;
-}
-
-@observer
-export class CollectionSchemaPreview extends React.Component<CollectionSchemaPreviewProps>{
-    private dropDisposer?: DragManager.DragDropDisposer;
-    private get layoutDoc() { return this.props.Document && Doc.Layout(this.props.Document); }
-    private get nativeWidth() { return NumCast(this.layoutDoc!.nativeWidth, this.props.PanelWidth()); }
-    private get nativeHeight() { return NumCast(this.layoutDoc!.nativeHeight, this.props.PanelHeight()); }
-    private contentScaling = () => {
-        let wscale = this.props.PanelWidth() / (this.nativeWidth ? this.nativeWidth : this.props.PanelWidth());
-        if (wscale * this.nativeHeight > this.props.PanelHeight()) {
-            return this.props.PanelHeight() / (this.nativeHeight ? this.nativeHeight : this.props.PanelHeight());
-        }
-        return wscale;
-    }
-    private createTarget = (ele: HTMLDivElement) => {
-        this.dropDisposer && this.dropDisposer();
-        if (ele) {
-            this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
-        }
-    }
-
-    @undoBatch
-    @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        if (de.data instanceof DragManager.DocumentDragData) {
-            this.props.childDocs && this.props.childDocs.map(otherdoc => {
-                let target = Doc.GetProto(otherdoc);
-                target.layout = ComputedField.MakeFunction("this.image_data[0]");
-                target.layoutCustom = Doc.MakeDelegate(de.data.draggedDocuments[0]);
-            });
-            e.stopPropagation();
-        }
-        return true;
-    }
-    private PanelWidth = () => this.nativeWidth && (!this.props.Document || !this.props.Document.fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth();
-    private PanelHeight = () => this.nativeHeight && (!this.props.Document || !this.props.Document.fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight();
-    private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, 0).scale(1 / this.contentScaling());
-    get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document.fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; }
-
-    @computed get borderRounding() { return StrCast(this.props.Document!.borderRounding); }
-
-    render() {
-        let input = this.props.previewScript === undefined ? (null) :
-            <div ref={this.createTarget}><input className="collectionSchemaView-input" value={this.props.previewScript} onChange={e => this.props.setPreviewScript(e.currentTarget.value)}
-                style={{ left: `calc(50% - ${Math.min(75, (this.props.Document ? this.PanelWidth() / 2 : 75))}px)` }} /></div>;
-        return (<div className="collectionSchemaView-previewRegion"
-            style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }}>
-            {!this.props.Document || !this.props.PanelWidth ? (null) : (
-                <div className="collectionSchemaView-previewDoc"
-                    style={{
-                        transform: `translate(${this.centeringOffset}px, 0px)`,
-                        borderRadius: this.borderRounding,
-                        height: this.props.PanelHeight(),
-                        width: this.props.PanelWidth()
-                    }}>
-                    <DocumentView {...this.props}
-                        DataDoc={this.props.DataDocument}
-                        Document={this.props.Document}
-                        fitToBox={this.props.fitToBox}
-                        onClick={this.props.onClick}
-                        ruleProvider={this.props.ruleProvider}
-                        showOverlays={this.props.showOverlays}
-                        addDocument={this.props.addDocument}
-                        removeDocument={this.props.removeDocument}
-                        moveDocument={this.props.moveDocument}
-                        whenActiveChanged={this.props.whenActiveChanged}
-                        ContainingCollectionView={this.props.CollectionView}
-                        ContainingCollectionDoc={this.props.CollectionDoc}
-                        addDocTab={this.props.addDocTab}
-                        pinToPres={this.props.pinToPres}
-                        parentActive={this.props.active}
-                        ScreenToLocalTransform={this.getTransform}
-                        renderDepth={this.props.renderDepth + 1}
-                        ContentScaling={this.contentScaling}
-                        PanelWidth={this.PanelWidth}
-                        PanelHeight={this.PanelHeight}
-                        focus={this.props.focus || emptyFunction}
-                        backgroundColor={returnEmptyString}
-                        bringToFront={emptyFunction}
-                        zoomToScale={emptyFunction}
-                        getScale={returnOne}
-                    />
-                </div>)}
-            {input}
-        </div>);
-    }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 37d897088..15033e51a 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -16,7 +16,7 @@ import { DragManager } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
 import { EditableView } from "../EditableView";
-import { CollectionSchemaPreview } from "./CollectionSchemaView";
+import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
 import "./CollectionStackingView.scss";
 import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
 import { CollectionSubView } from "./CollectionSubView";
@@ -165,7 +165,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         let layoutDoc = Doc.Layout(doc);
         let height = () => this.getDocHeight(doc);
         let finalDxf = () => dxf().scale(this.columnWidth / layoutDoc[WidthSym]());
-        return <CollectionSchemaPreview
+        return <ContentFittingDocumentView
             Document={doc}
             DataDocument={dataDoc}
             fieldKey={this.props.fieldKey}
@@ -189,7 +189,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             pinToPres={this.props.pinToPres}
             setPreviewScript={emptyFunction}
             previewScript={undefined}>
-        </CollectionSchemaPreview>;
+        </ContentFittingDocumentView>;
     }
     getDocHeight(d?: Doc) {
         if (!d) return 0;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 89cc69ccf..8726726bb 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -24,7 +24,7 @@ import { MainView } from '../MainView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { Templates } from '../Templates';
 import { CollectionViewType } from './CollectionView';
-import { CollectionSchemaPreview } from './CollectionSchemaView';
+import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
 import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import React = require("react");
@@ -314,7 +314,7 @@ class TreeView extends React.Component<TreeViewProps> {
         } else {
             let layoutDoc = Doc.Layout(this.props.document);
             return <div ref={this._dref} style={{ display: "inline-block", height: this.docHeight() }} key={this.props.document[Id] + this.props.document.title}>
-                <CollectionSchemaPreview
+                <ContentFittingDocumentView
                     Document={layoutDoc}
                     DataDocument={this.templateDataDoc}
                     fieldKey={this.fieldKey}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.scss b/src/client/views/nodes/ContentFittingDocumentView.scss
new file mode 100644
index 000000000..796e67269
--- /dev/null
+++ b/src/client/views/nodes/ContentFittingDocumentView.scss
@@ -0,0 +1,23 @@
+@import "../globalCssVariables";
+
+.contentFittingDocumentView {
+    position: relative;
+    height: auto !important;
+
+    .contentFittingDocumentView-previewDoc {
+        position: absolute;
+        display: inline;
+    }
+
+    .contentFittingDocumentView-input {
+        position: absolute;
+        max-width: 150px;
+        width: 100%;
+        bottom: 0px;
+    }
+
+    .documentView-node:first-child {
+        position: relative;
+        background: $light-color;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
new file mode 100644
index 000000000..f41c4fc91
--- /dev/null
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -0,0 +1,117 @@
+import React = require("react");
+import { action, computed } from "mobx";
+import { observer } from "mobx-react";
+import "react-table/react-table.css";
+import { Doc } from "../../../new_fields/Doc";
+import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
+import { NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnOne } from "../../../Utils";
+import { DragManager } from "../../util/DragManager";
+import { Transform } from "../../util/Transform";
+import { undoBatch } from "../../util/UndoManager";
+import '../DocumentDecorations.scss';
+import { DocumentView } from "../nodes/DocumentView";
+import "./ContentFittingDocumentView.scss";
+import { CollectionView } from "../collections/CollectionView";
+
+interface ContentFittingDocumentViewProps {
+    Document?: Doc;
+    DataDocument?: Doc;
+    childDocs?: Doc[];
+    renderDepth: number;
+    fitToBox?: boolean;
+    fieldKey: string;
+    PanelWidth: () => number;
+    PanelHeight: () => number;
+    ruleProvider: Doc | undefined;
+    focus?: (doc: Doc) => void;
+    showOverlays?: (doc: Doc) => { title?: string, caption?: string };
+    CollectionView?: CollectionView;
+    CollectionDoc?: Doc;
+    onClick?: ScriptField;
+    getTransform: () => Transform;
+    addDocument: (document: Doc) => boolean;
+    moveDocument: (document: Doc, target: Doc, addDoc: ((doc: Doc) => boolean)) => boolean;
+    removeDocument: (document: Doc) => boolean;
+    active: () => boolean;
+    whenActiveChanged: (isActive: boolean) => void;
+    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
+    pinToPres: (document: Doc) => void;
+    setPreviewScript: (script: string) => void;
+    previewScript?: string;
+}
+
+@observer
+export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{
+    private get layoutDoc() { return this.props.Document && Doc.Layout(this.props.Document); }
+    private get nativeWidth() { return NumCast(this.layoutDoc!.nativeWidth, this.props.PanelWidth()); }
+    private get nativeHeight() { return NumCast(this.layoutDoc!.nativeHeight, this.props.PanelHeight()); }
+    private contentScaling = () => {
+        let wscale = this.props.PanelWidth() / (this.nativeWidth ? this.nativeWidth : this.props.PanelWidth());
+        if (wscale * this.nativeHeight > this.props.PanelHeight()) {
+            return this.props.PanelHeight() / (this.nativeHeight ? this.nativeHeight : this.props.PanelHeight());
+        }
+        return wscale;
+    }
+
+    @undoBatch
+    @action
+    drop = (e: Event, de: DragManager.DropEvent) => {
+        if (de.data instanceof DragManager.DocumentDragData) {
+            this.props.childDocs && this.props.childDocs.map(otherdoc => {
+                let target = Doc.GetProto(otherdoc);
+                target.layout = ComputedField.MakeFunction("this.image_data[0]");
+                target.layoutCustom = Doc.MakeDelegate(de.data.draggedDocuments[0]);
+            });
+            e.stopPropagation();
+        }
+        return true;
+    }
+    private PanelWidth = () => this.nativeWidth && (!this.props.Document || !this.props.Document.fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth();
+    private PanelHeight = () => this.nativeHeight && (!this.props.Document || !this.props.Document.fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight();
+    private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, 0).scale(1 / this.contentScaling());
+    private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document.fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; }
+
+    @computed get borderRounding() { return StrCast(this.props.Document!.borderRounding); }
+
+    render() {
+        return (<div className="contentFittingDocumentView" style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }}>
+            {!this.props.Document || !this.props.PanelWidth ? (null) : (
+                <div className="contentFittingDocumentView-previewDoc"
+                    style={{
+                        transform: `translate(${this.centeringOffset}px, 0px)`,
+                        borderRadius: this.borderRounding,
+                        height: this.props.PanelHeight(),
+                        width: this.props.PanelWidth()
+                    }}>
+                    <DocumentView {...this.props}
+                        DataDoc={this.props.DataDocument}
+                        Document={this.props.Document}
+                        fitToBox={this.props.fitToBox}
+                        onClick={this.props.onClick}
+                        ruleProvider={this.props.ruleProvider}
+                        showOverlays={this.props.showOverlays}
+                        addDocument={this.props.addDocument}
+                        removeDocument={this.props.removeDocument}
+                        moveDocument={this.props.moveDocument}
+                        whenActiveChanged={this.props.whenActiveChanged}
+                        ContainingCollectionView={this.props.CollectionView}
+                        ContainingCollectionDoc={this.props.CollectionDoc}
+                        addDocTab={this.props.addDocTab}
+                        pinToPres={this.props.pinToPres}
+                        parentActive={this.props.active}
+                        ScreenToLocalTransform={this.getTransform}
+                        renderDepth={this.props.renderDepth + 1}
+                        ContentScaling={this.contentScaling}
+                        PanelWidth={this.PanelWidth}
+                        PanelHeight={this.PanelHeight}
+                        focus={this.props.focus || emptyFunction}
+                        backgroundColor={returnEmptyString}
+                        bringToFront={emptyFunction}
+                        zoomToScale={emptyFunction}
+                        getScale={returnOne}
+                    />
+                </div>)}
+        </div>);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 54a687c34..93052ea73 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -577,27 +577,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true;
     }
 
-    render() {
-        if (!this.props.Document) return (null);
-        trace();
-        const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined;
-        const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
-        const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
-        const colorSet = this.setsLayoutProp("backgroundColor");
-        const clusterCol = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.clusterOverridesDefaultBackground;
-        const backgroundColor = this.Document.isBackground || (clusterCol && !colorSet) ?
-            this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) :
-            ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
-
-        const nativeWidth = this.layoutDoc.fitWidth ? this.props.PanelWidth() - 2 : this.nativeWidth > 0 && !this.layoutDoc.ignoreAspect ? `${this.nativeWidth}px` : "100%";
-        const nativeHeight = this.layoutDoc.fitWidth ? this.props.PanelHeight() - 2 : this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
+    @computed get innards() {
         const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
         const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle");
         const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption");
         const showTextTitle = showTitle && StrCast(this.Document.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined;
-        const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
-        const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding;
-        const localScale = this.props.ScreenToLocalTransform().Scale * fullDegree;
         const searchHighlight = (!this.Document.searchFields ? (null) :
             <div className="documentView-searchHighlight" style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
                 {this.Document.searchFields}
@@ -623,29 +607,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     SetValue={(value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true}
                 />
             </div>);
-        let animheight = animDims ? animDims[1] : nativeHeight;
-        let animwidth = animDims ? animDims[0] : nativeWidth;
-
-        const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
-        const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"];
-        let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear;
-        return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
-            ref={this._mainCont}
-            style={{
-                transition: this.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition),
-                pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all",
-                color: StrCast(this.Document.color),
-                outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
-                border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
-                background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor,
-                width: animwidth,
-                height: animheight,
-                transform: `scale(${this.layoutDoc.fitWidth ? 1 : this.props.ContentScaling()})`,
-                opacity: this.Document.opacity
-            }}
-            onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
-            onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)}
-        >
+        return <>
             {this.Document.links && DocListCast(this.Document.links).filter((d) => !DocListCast(this.layoutDoc.hiddenLinks).some(hidden => Doc.AreProtosEqual(hidden, d))).filter(this.isNonTemporalLink).map((d, i) =>
                 <div className="documentView-docuLinkWrapper" key={`${d[Id]}`} style={{ transform: `scale(${this.layoutDoc.fitWidth ? 1 : 1 / this.props.ContentScaling()})` }}>
                     <DocumentView {...this.props} Document={d} layoutKey={this.linkEndpoint(d)} backgroundColor={returnTransparent} removeDocument={undoBatch(doc => Doc.AddDocToList(this.layoutDoc, "hiddenLinks", doc))} />
@@ -668,6 +630,48 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     {searchHighlight}
                 </div>
             }
+        </>
+    }
+    render() {
+        if (!this.props.Document) return (null);
+        trace();
+        const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined;
+        const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
+        const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
+        const colorSet = this.setsLayoutProp("backgroundColor");
+        const clusterCol = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.clusterOverridesDefaultBackground;
+        const backgroundColor = this.Document.isBackground || (clusterCol && !colorSet) ?
+            this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) :
+            ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
+
+        const nativeWidth = this.layoutDoc.fitWidth ? this.props.PanelWidth() - 2 : this.nativeWidth > 0 && !this.layoutDoc.ignoreAspect ? `${this.nativeWidth}px` : "100%";
+        const nativeHeight = this.layoutDoc.fitWidth ? this.props.PanelHeight() - 2 : this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
+        const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
+        const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding;
+        const localScale = this.props.ScreenToLocalTransform().Scale * fullDegree;
+
+        let animheight = animDims ? animDims[1] : nativeHeight;
+        let animwidth = animDims ? animDims[0] : nativeWidth;
+
+        const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
+        const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"];
+        let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear;
+        return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont}
+            onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
+            onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)}
+            style={{
+                transition: this.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition),
+                pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all",
+                color: StrCast(this.Document.color),
+                outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
+                border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
+                background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor,
+                width: animwidth,
+                height: animheight,
+                transform: `scale(${this.layoutDoc.fitWidth ? 1 : this.props.ContentScaling()})`,
+                opacity: this.Document.opacity
+            }} >
+            {this.innards}
         </div>;
     }
 }
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 7a1b4f9fb..17b2094ec 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -13,7 +13,7 @@ import { emptyFunction, returnFalse } from "../../../Utils";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { Transform } from "../../util/Transform";
 import { CollectionViewType } from '../collections/CollectionView';
-import { CollectionSchemaPreview } from '../collections/CollectionSchemaView';
+import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
 import { DocComponent } from '../DocComponent';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import "./PresElementBox.scss";
@@ -169,7 +169,7 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
                 height: propDocHeight === 0 ? NumCast(this.layoutDoc.height) - NumCast(this.layoutDoc.collapsedHeight) : propDocHeight * scale(),
                 width: propDocWidth === 0 ? "auto" : propDocWidth * scale(),
             }}>
-                <CollectionSchemaPreview
+                <ContentFittingDocumentView
                     fitToBox={StrCast(this.targetDoc.type).indexOf(DocumentType.COL) !== -1}
                     Document={this.targetDoc}
                     fieldKey={this.props.fieldKey}
-- 
cgit v1.2.3-70-g09d2


From ab5a893493e150337f0b193344f73bc7aa12afe5 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Sat, 9 Nov 2019 14:13:46 -0500
Subject: a

---
 src/client/views/DocumentDecorations.tsx | 5 -----
 1 file changed, 5 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 495e1d2b2..e208e5f3b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -583,13 +583,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
     render() {
         var bounds = this.Bounds;
-<<<<<<< HEAD
-        let seldoc = SelectionManager.AllSelected().length ? SelectionManager.AllSelected()[0] : undefined;
-        if (bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
-=======
         let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
         if (SelectionManager.GetIsDragging() || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
->>>>>>> 5c6dc8fb25c2ac65a9efa534ee86211ac6d68301
             return (null);
         }
         let minimizeIcon = (
-- 
cgit v1.2.3-70-g09d2


From c7c18eeea36b35ee9172a120352af84fe21f267b Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Sun, 10 Nov 2019 16:27:56 -0500
Subject: inks are now dox

---
 src/client/documents/Documents.ts                  |  14 ++-
 src/client/util/SelectionManager.ts                |  32 ------
 src/client/views/DocumentDecorations.tsx           |  80 ++++++---------
 src/client/views/InkSelectDecorations.tsx          |  16 +--
 src/client/views/InkingCanvas.tsx                  |  12 +--
 src/client/views/InkingStroke.tsx                  | 107 +++++++++------------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  64 +++++++++---
 .../collections/collectionFreeForm/MarqueeView.tsx |  92 +++++++++---------
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/new_fields/InkField.ts                         |  20 ++--
 src/new_fields/documentSchemas.ts                  |   1 +
 11 files changed, 207 insertions(+), 233 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2c6b40cb9..a074d267e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -48,6 +48,8 @@ import { PresElementBox } from "../views/presentationview/PresElementBox";
 import { QueryBox } from "../views/nodes/QueryBox";
 import { ColorBox } from "../views/nodes/ColorBox";
 import { DocuLinkBox } from "../views/nodes/DocuLinkBox";
+import { InkingStroke } from "../views/InkingStroke";
+import { InkField } from "../../new_fields/InkField";
 var requestImageSize = require('../util/request-image-size');
 var path = require('path');
 
@@ -107,6 +109,7 @@ export interface DocumentOptions {
     sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
     targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
     dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
+    strokeWidth?: number;
     // [key: string]: Opt<Field>;
 }
 
@@ -209,6 +212,9 @@ export namespace Docs {
             [DocumentType.PRESELEMENT, {
                 layout: { view: PresElementBox, dataField: data }
             }],
+            [DocumentType.INK, {
+                layout: { view: InkingStroke, dataField: data }
+            }]
         ]);
 
         // All document prototypes are initialized with at least these values
@@ -411,8 +417,12 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.TEXT), "", options);
         }
 
-        export function InkDocument(options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.INK), "", options);
+        export function InkDocument(color: string, tool: number, strokeWidth: number, points: { x: number, y: number }[], options: DocumentOptions = {}) {
+            let doc = InstanceFromProto(Prototypes.get(DocumentType.INK), new InkField(points), options);
+            doc.color = color;
+            doc.strokeWidth = strokeWidth;
+            doc.tool = tool;
+            return doc;
         }
 
         export function IconDocument(icon: string, options: DocumentOptions = {}) {
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 2a57c67bd..ca61f9014 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -3,8 +3,6 @@ import { Doc, Opt } from "../../new_fields/Doc";
 import { DocumentView } from "../views/nodes/DocumentView";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { NumCast, StrCast } from "../../new_fields/Types";
-import { InkingControl } from "../views/InkingControl";
-import { InkDocAndStroke } from "../views/InkingStroke";
 
 export namespace SelectionManager {
 
@@ -12,7 +10,6 @@ export namespace SelectionManager {
 
         @observable IsDragging: boolean = false;
         @observable SelectedDocuments: Array<DocumentView> = [];
-        @observable SelectedInk: Array<{ Document: Doc, Ink: Map<any, any> }> = [];
 
 
         @action
@@ -43,20 +40,6 @@ export namespace SelectionManager {
         DeselectAll(): void {
             manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false));
             manager.SelectedDocuments = [];
-            manager.SelectedInk = [];
-        }
-
-        @action
-        SelectInk(ink: { Document: Doc, Ink: Map<any, any> }, ctrlPressed: boolean): void {
-            if (manager.SelectedInk.indexOf(ink) === -1) {
-                if (!ctrlPressed) {
-                    this.DeselectAll();
-                }
-
-                manager.SelectedInk.push(ink);
-            } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) {
-                manager.SelectedInk = [ink];
-            }
         }
     }
 
@@ -69,10 +52,6 @@ export namespace SelectionManager {
         manager.SelectDoc(docView, ctrlPressed);
     }
 
-    export function SelectInk(ink: { Document: Doc, Ink: Map<any, any> }, ctrlPressed: boolean): void {
-        manager.SelectInk(ink, ctrlPressed);
-    }
-
     export function IsSelected(doc: DocumentView): boolean {
         return manager.SelectedDocuments.indexOf(doc) !== -1;
     }
@@ -95,15 +74,4 @@ export namespace SelectionManager {
     export function SelectedDocuments(): Array<DocumentView> {
         return manager.SelectedDocuments.slice();
     }
-
-    export function SelectedInk(): Array<{ Document: Doc, Ink: Map<any, any> }> {
-        return manager.SelectedInk.slice();
-    }
-
-    export function AllSelected(): Array<DocumentView | InkDocAndStroke> {
-        let arr: Array<DocumentView | InkDocAndStroke> = [];
-        arr = SelectionManager.SelectedDocuments();
-        arr.push(...SelectionManager.SelectedInk());
-        return arr;
-    }
 }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e208e5f3b..10764a9ce 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -24,7 +24,7 @@ import { DocumentView } from "./nodes/DocumentView";
 import { FieldView } from "./nodes/FieldView";
 import { IconBox } from "./nodes/IconBox";
 import React = require("react");
-import { StrokeData } from '../../new_fields/InkField';
+import { PointData } from '../../new_fields/InkField';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -162,44 +162,23 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @computed
     get Bounds(): { x: number, y: number, b: number, r: number } {
         let x = this._forceUpdate;
-        this._lastBox = SelectionManager.AllSelected().reduce((bounds, docViewOrInk) => {
-            if (docViewOrInk instanceof DocumentView) {
-                if (docViewOrInk.props.renderDepth === 0 ||
-                    Doc.AreProtosEqual(docViewOrInk.props.Document, CurrentUserUtils.UserDocument)) {
-                    return bounds;
-                }
-                let transform = (docViewOrInk.props.ScreenToLocalTransform().scale(docViewOrInk.props.ContentScaling())).inverse();
-                if (transform.TranslateX === 0 && transform.TranslateY === 0) {
-                    setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds
-                    return this._lastBox;
-                }
-
-                var [sptX, sptY] = transform.transformPoint(0, 0);
-                let [bptX, bptY] = transform.transformPoint(docViewOrInk.props.PanelWidth(), docViewOrInk.props.PanelHeight());
-                return {
-                    x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
-                    r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
-                };
+        this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, docView) => {
+            if (docView.props.renderDepth === 0 ||
+                Doc.AreProtosEqual(docView.props.Document, CurrentUserUtils.UserDocument)) {
+                return bounds;
             }
-            else {
-                let left = bounds.x;
-                let top = bounds.y;
-                let right = bounds.r;
-                let bottom = bounds.b;
-                let ink;
-                docViewOrInk.Ink.forEach((value: StrokeData, key: string) => {
-                    value.pathData.map(val => {
-                        ink = docViewOrInk.Document.ink;
-                        left = Math.min(val.x, left);
-                        top = Math.min(val.y, top);
-                        right = Math.max(val.x, right);
-                        bottom = Math.max(val.y, bottom);
-                    });
-                });
-                return {
-                    x: left, y: top, r: right, b: bottom
-                };
+            let transform = (docView.props.ScreenToLocalTransform().scale(docView.props.ContentScaling())).inverse();
+            if (transform.TranslateX === 0 && transform.TranslateY === 0) {
+                setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds
+                return this._lastBox;
             }
+
+            var [sptX, sptY] = transform.transformPoint(0, 0);
+            let [bptX, bptY] = transform.transformPoint(docView.props.PanelWidth(), docView.props.PanelHeight());
+            return {
+                x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
+                r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
+            };
         }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
         return this._lastBox;
     }
@@ -226,7 +205,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         document.removeEventListener("pointerup", this.onBackgroundUp);
         document.removeEventListener("pointermove", this.onTitleMove);
         document.removeEventListener("pointerup", this.onTitleUp);
-        DragManager.StartDocumentDrag(SelectionManager.AllSelected().map(docOrInk => docOrInk instanceof DocumentView ? docOrInk.ContentDiv! : (document.createElement("div"))), dragData, e.x, e.y, {
+        DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, {
             handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) },
             hideSource: true
         });
@@ -550,21 +529,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
     @computed
     get selectionTitle(): string {
-        if (SelectionManager.AllSelected().length === 1) {
-            let selected = SelectionManager.AllSelected()[0];
-            if (selected instanceof DocumentView) {
-                let field = selected.props.Document[this._fieldKey];
-                if (typeof field === "string") {
-                    return field;
-                }
-                else if (typeof field === "number") {
-                    return field.toString();
-                }
+        if (SelectionManager.SelectedDocuments().length === 1) {
+            let selected = SelectionManager.SelectedDocuments()[0];
+            let field = selected.props.Document[this._fieldKey];
+            if (typeof field === "string") {
+                return field;
             }
-            else {
-                return "-ink strokes-";
+            else if (typeof field === "number") {
+                return field.toString();
             }
-        } else if (SelectionManager.AllSelected().length > 1) {
+        } else if (SelectionManager.SelectedDocuments().length > 1) {
             return "-multiple-";
         }
         return "-unset-";
@@ -590,7 +564,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let minimizeIcon = (
             <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>
                 {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
-                {(SelectionManager.SelectedDocuments().length === 1 && SelectionManager.SelectedInk().length === 0) ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
+                {(SelectionManager.SelectedDocuments().length === 1) ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
             </div>);
 
         bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
@@ -611,7 +585,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 left: bounds.x - this._resizeBorderWidth / 2,
                 top: bounds.y - this._resizeBorderWidth / 2,
                 pointerEvents: this.Interacting ? "none" : "all",
-                zIndex: SelectionManager.AllSelected().length > 1 ? 900 : 0,
+                zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0,
             }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
             </div>
             <div className="documentDecorations-container" ref={this.setTextBar} style={{
diff --git a/src/client/views/InkSelectDecorations.tsx b/src/client/views/InkSelectDecorations.tsx
index a8eef3305..95ccc1777 100644
--- a/src/client/views/InkSelectDecorations.tsx
+++ b/src/client/views/InkSelectDecorations.tsx
@@ -1,6 +1,6 @@
 import React = require("react");
 import { Touchable } from "./Touchable";
-import { StrokeData } from "../../new_fields/InkField";
+import { PointData } from "../../new_fields/InkField";
 import { observer } from "mobx-react";
 import { computed, observable, action, runInAction } from "mobx";
 import "./InkSelectDecorations.scss"
@@ -33,13 +33,13 @@ export default class InkSelectDecorations extends Touchable {
         let top = Number.MAX_VALUE;
         let right = -Number.MAX_VALUE;
         let bottom = -Number.MAX_VALUE;
-        this._selectedInkNodes.forEach((value: StrokeData, key: string) => {
-            value.pathData.map(val => {
-                left = Math.min(val.x, left);
-                top = Math.min(val.y, top);
-                right = Math.max(val.x, right);
-                bottom = Math.max(val.y, bottom);
-            });
+        this._selectedInkNodes.forEach((value: PointData, key: string) => {
+            // value.pathData.map(val => {
+            //     left = Math.min(val.x, left);
+            //     top = Math.min(val.y, top);
+            //     right = Math.max(val.x, right);
+            //     bottom = Math.max(val.y, bottom);
+            // });
         });
         return { x: left, y: top, b: bottom, r: right };
     }
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index a0ea37300..e5253c377 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -7,7 +7,7 @@ import { InkingControl } from "./InkingControl";
 import { InkingStroke } from "./InkingStroke";
 import React = require("react");
 import { UndoManager } from "../util/UndoManager";
-import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
+import { PointData, InkField, InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
 import { Touchable } from "./Touchable";
@@ -26,15 +26,15 @@ export class InkingCanvas extends Touchable<InkCanvasProps> {
     maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome
     @observable inkMidX: number = 0;
     @observable inkMidY: number = 0;
-    private previousState?: Map<string, StrokeData>;
+    private previousState?: Map<string, PointData>;
     private _currentStrokeId: string = "";
-    public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean {
+    public static IntersectStrokeRect(stroke: PointData, selRect: { left: number, top: number, width: number, height: number }): boolean {
         return stroke.pathData.reduce((inside: boolean, val) => inside ||
             (selRect.left < val.x && selRect.left + selRect.width > val.x &&
                 selRect.top < val.y && selRect.top + selRect.height > val.y)
             , false);
     }
-    public static StrokeRect(stroke: StrokeData): { left: number, top: number, right: number, bottom: number } {
+    public static StrokeRect(stroke: PointData): { left: number, top: number, right: number, bottom: number } {
         return stroke.pathData.reduce((bounds: { left: number, top: number, right: number, bottom: number }, val) =>
             ({
                 left: Math.min(bounds.left, val.x), top: Math.min(bounds.top, val.y),
@@ -58,12 +58,12 @@ export class InkingCanvas extends Touchable<InkCanvasProps> {
     }
 
     @computed
-    get inkData(): Map<string, StrokeData> {
+    get inkData(): Map<string, PointData> {
         let map = Cast(this.props.AnnotationDocument[this.props.inkFieldKey], InkField);
         return !map ? new Map : new Map(map.inkData);
     }
 
-    set inkData(value: Map<string, StrokeData>) {
+    set inkData(value: Map<string, PointData>) {
         this.props.AnnotationDocument[this.props.inkFieldKey] = new InkField(value);
     }
 
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 824f40b1f..411b0d3a0 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,79 +1,66 @@
 import { observer } from "mobx-react";
-import { observable, trace, runInAction } from "mobx";
+import { observable, trace, runInAction, computed } from "mobx";
 import { InkingControl } from "./InkingControl";
 import React = require("react");
-import { InkTool } from "../../new_fields/InkField";
+import { InkTool, InkField, InkData } from "../../new_fields/InkField";
 import "./InkingStroke.scss";
 import { AudioBox } from "./nodes/AudioBox";
-import { Doc } from "../../new_fields/Doc";
-import { createSchema, makeInterface } from "../../new_fields/Schema";
+import { Doc, FieldResult } from "../../new_fields/Doc";
+import { createSchema, makeInterface, listSpec } from "../../new_fields/Schema";
 import { documentSchema } from "../../new_fields/documentSchemas";
 import { DocExtendableComponent } from "./DocComponent";
 import { FieldViewProps, FieldView } from "./nodes/FieldView";
-
-
-interface StrokeProps {
-    offsetX: number;
-    offsetY: number;
-    id: string;
-    count: number;
-    line: Array<{ x: number, y: number }>;
-    color: string;
-    width: string;
-    tool: InkTool;
-    creationTime: number;
-    deleteCallback: (index: string) => void;
-}
+import { Transform } from "../util/Transform";
+import { Cast, FieldValue } from "../../new_fields/Types";
+import { List } from "../../new_fields/List";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
 
+export function CreatePolyline(points: { x: number, y: number }[], left: number, top: number, color?: string, width?: number) {
+    let pts = points.reduce((acc: string, pt: { x: number, y: number }) => acc + `${pt.x - left},${pt.y - top} `, "");
+    return (
+        <polyline
+            points={pts}
+            style={{
+                fill: "none",
+                stroke: color ?? InkingControl.Instance.selectedColor,
+                strokeWidth: width ?? InkingControl.Instance.selectedWidth
+            }}
+        />
+    );
+}
+
 @observer
-export class InkingStroke extends DocExtendableComponent<FieldViewProps & StrokeProps, InkDocument>(InkDocument) {
+export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocument>(InkDocument) {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
 
-    @observable private _strokeTool: InkTool = this.props.tool;
-    @observable private _strokeColor: string = this.props.color;
-    @observable private _strokeWidth: string = this.props.width;
-
-    deleteStroke = (e: React.PointerEvent): void => {
-        if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) {
-            this.props.deleteCallback(this.props.id);
-            e.stopPropagation();
-            e.preventDefault();
-        }
-        if (InkingControl.Instance.selectedTool === InkTool.Scrubber && e.buttons === 1) {
-            AudioBox.SetScrubTime(this.props.creationTime);
-            e.stopPropagation();
-            e.preventDefault();
-        }
-    }
-
-    parseData = (line: Array<{ x: number, y: number }>): string => {
-        return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L ");
-    }
-
-    createStyle() {
-        switch (this._strokeTool) {
-            // add more tool styles here
-            default:
-                return {
-                    fill: "none",
-                    stroke: this._strokeColor,
-                    strokeWidth: this._strokeWidth + "px",
-                };
-        }
-    }
+    @computed get PanelWidth() { return this.props.PanelWidth(); }
+    @computed get PanelHeight() { return this.props.PanelHeight(); }
 
     render() {
-        let pathStyle = this.createStyle();
-        let pathData = this.parseData(this.props.line);
-        let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes
-        let marker = this.props.tool === InkTool.Highlighter ? "-marker" : "";
-
-        let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ||
-            InkingControl.Instance.selectedTool === InkTool.Scrubber ? "all" : "none";
-        return (<path className={`inkingStroke${marker}`} d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }}
-            strokeLinejoin="round" strokeLinecap="round" onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />);
+        // let pathData = this.parseData(this.props.line);
+        let data: InkData = Cast(this.Document.data, InkField) ?.inkData ?? [];
+        let xs = data.map(p => p.x);
+        let ys = data.map(p => p.y);
+        let left = Math.min(...xs);
+        let top = Math.min(...ys);
+        let right = Math.max(...xs);
+        let bottom = Math.max(...ys);
+        let points = CreatePolyline(data, 0, 0, this.Document.color, this.Document.strokeWidth);
+        let width = right - left;
+        let height = bottom - top;
+        let scaleX = this.PanelWidth / width;
+        let scaleY = this.PanelHeight / height;
+        // let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes
+        return (
+            <svg width={width} height={height} style={{
+                transformOrigin: "top left",
+                transform: `translate(${left}px, ${top}px) scale(${scaleX}, ${scaleY})`,
+                mixBlendMode: this.Document.tool === InkTool.Highlighter ? "multiply" : "unset"
+            }}>
+                {points}
+            </svg>
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8294eaaec..21981c25e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -5,7 +5,7 @@ import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
-import { InkField, StrokeData, InkTool } from "../../../../new_fields/InkField";
+import { InkField, PointData, InkTool } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
@@ -40,6 +40,7 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import PDFMenu from "../../pdf/PDFMenu";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { InkingControl } from "../../InkingControl";
+import { InkingStroke, CreatePolyline } from "../../InkingStroke";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -108,10 +109,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         added && this.updateCluster(newBox);
         return added;
     }
-    private selectDocuments = (docs: Doc[], ink: { Document: Doc, Ink: Map<any, any> }[]) => {
+    private selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
-        ink.forEach(i => SelectionManager.SelectInk(i, true));
     }
     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); }
 
@@ -284,14 +284,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 e.preventDefault();
 
                 if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
-                    this._points.push({ x: e.pageX, y: e.pageY });
+                    let point = this.getTransform().transformPoint(e.pageX, e.pageY);
+                    this._points.push({ x: point[0], y: point[1] });
                 }
             }
         }
     }
 
+    @action
     onPointerUp = (e: PointerEvent): void => {
         if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) return;
+
+        if (this._points.length > 1) {
+            let B = this.svgBounds;
+            let points = this._points.map(p => ({ x: p.x - B.left, y: p.y - B.top }));
+            let inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { width: B.width, height: B.height, x: B.left, y: B.top });
+            this.addDocument(inkDoc);
+            this._points = [];
+        }
+
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         document.removeEventListener("touchmove", this.onTouch);
@@ -324,11 +335,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }, [[minx, maxx], [miny, maxy]]);
             let ink = this.extensionDoc && Cast(this.extensionDoc.ink, InkField);
             if (ink && ink.inkData) {
-                ink.inkData.forEach((value: StrokeData, key: string) => {
-                    let bounds = InkingCanvas.StrokeRect(value);
-                    ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)];
-                    ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)];
-                });
+                // ink.inkData.forEach((value: PointData, key: string) => {
+                //     let bounds = InkingCanvas.StrokeRect(value);
+                //     ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)];
+                //     ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)];
+                // });
             }
 
             let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
@@ -363,8 +374,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }
                 this.pan(e);
             }
-            if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
-                this._points.push({ x: e.clientX, y: e.clientY });
+            else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
+                let point = this.getTransform().transformPoint(e.clientX, e.clientY);
+                this._points.push({ x: point[0], y: point[1] });
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
             e.preventDefault();
@@ -470,7 +482,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
         else if (this.props.active()) {
             e.stopPropagation();
-            this.zoom(e.clientX, e.clientY, e.deltaY)
+            this.zoom(e.clientX, e.clientY, e.deltaY);
         }
     }
 
@@ -790,6 +802,31 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             ...this.views,
         ];
     }
+
+    @computed get svgBounds() {
+        let xs = this._points.map(p => p.x);
+        let ys = this._points.map(p => p.y);
+        let right = Math.max(...xs);
+        let left = Math.min(...xs);
+        let bottom = Math.max(...ys);
+        let top = Math.min(...ys);
+        return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top };
+    }
+
+    @computed get currentStroke() {
+        if (this._points.length <= 1) {
+            return (null);
+        }
+
+        let B = this.svgBounds;
+
+        return (
+            <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)` }}>
+                {CreatePolyline(this._points, B.left, B.top)}
+            </svg>
+        );
+    }
+
     render() {
         // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
         this.Document.fitX = this.contentBounds && this.contentBounds.x;
@@ -808,9 +845,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
                         {!this.extensionDoc ? (null) :
                             // <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.extensionDoc} inkFieldKey={"ink"} >
-                            this.childViews
+                            this.childViews()
                             // </InkingCanvas>
                         }
+                        {this.currentStroke}
                         <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
                     </CollectionFreeFormViewPannableContents>
                 </MarqueeView>
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 138168fed..b5f6f095e 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../../../new_fields/Doc";
-import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { InkField, PointData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { listSpec } from "../../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
@@ -198,10 +198,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             if (!e.shiftKey) {
                 SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document);
             }
-            let inkselect = this.ink ? this.marqueeInkSelect(this.ink.inkData) : new Map();
-            let inks = inkselect.size ? [{ Document: this.inkDoc, Ink: inkselect }] : [];
-            let docs = mselect.length ? mselect : (inks.length ? [] : [this.props.Document]);
-            this.props.selectDocuments(docs, inks);
+            // let inkselect = this.ink ? this.marqueeInkSelect(this.ink.inkData) : new Map();
+            // let inks = inkselect.size ? [{ Document: this.inkDoc, Ink: inkselect }] : [];
+            let docs = mselect.length ? mselect : [this.props.Document];
+            this.props.selectDocuments(docs, []);
         }
         if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) {
             MarqueeOptionsMenu.Instance.createCollection = this.collection;
@@ -217,7 +217,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             this.hideMarquee();
             MarqueeOptionsMenu.Instance.fadeOut(true);
             document.removeEventListener("pointerdown", hideMarquee);
-        }
+        };
         document.addEventListener("pointerdown", hideMarquee);
 
         if (e.altKey) {
@@ -294,7 +294,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     delete = () => {
         this.marqueeSelect(false).map(d => this.props.removeDocument(d));
         if (this.ink) {
-            this.marqueeInkDelete(this.ink.inkData);
+            // this.marqueeInkDelete(this.ink.inkData);
         }
         SelectionManager.DeselectAll();
         this.cleanupInteractions(false);
@@ -336,8 +336,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             title: "a nested collection",
         });
         let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
-        dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
-        this.marqueeInkDelete(inkData);
+        // dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
+        // this.marqueeInkDelete(inkData);
         this.hideMarquee();
         return newCollection;
     }
@@ -422,45 +422,45 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             this.cleanupInteractions(false);
         }
     }
-    @action
-    marqueeInkSelect(ink: Map<any, any>) {
-        let idata = new Map();
-        let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
-        let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2);
-        ink.forEach((value: StrokeData, key: string, map: any) => {
-            if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) {
-                // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling());
-                idata.set(key,
-                    {
-                        pathData: value.pathData.map(val => {
-                            let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y);
-                            return { x: tVal[0], y: tVal[1] };
-                            // return { x: val.x + centerShiftX, y: val.y + centerShiftY }
-                        }),
-                        color: value.color,
-                        width: value.width,
-                        tool: value.tool,
-                        page: -1
-                    });
-            }
-        });
-        // InkSelectDecorations.Instance.SetSelected(idata);
-        return idata;
-    }
+    // @action
+    // marqueeInkSelect(ink: Map<any, any>) {
+    //     let idata = new Map();
+    //     let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
+    //     let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2);
+    //     ink.forEach((value: PointData, key: string, map: any) => {
+    //         if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) {
+    //             // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling());
+    //             idata.set(key,
+    //                 {
+    //                     pathData: value.pathData.map(val => {
+    //                         let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y);
+    //                         return { x: tVal[0], y: tVal[1] };
+    //                         // return { x: val.x + centerShiftX, y: val.y + centerShiftY }
+    //                     }),
+    //                     color: value.color,
+    //                     width: value.width,
+    //                     tool: value.tool,
+    //                     page: -1
+    //                 });
+    //         }
+    //     });
+    //     // InkSelectDecorations.Instance.SetSelected(idata);
+    //     return idata;
+    // }
 
-    @action
-    marqueeInkDelete(ink?: Map<any, any>) {
-        // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB.
-        // ink.forEach((value: StrokeData, key: string, map: any) =>
-        //     InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key));
+    // @action
+    // marqueeInkDelete(ink?: Map<any, any>) {
+    //     // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB.
+    //     // ink.forEach((value: StrokeData, key: string, map: any) =>
+    //     //     InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key));
 
-        if (ink) {
-            let idata = new Map();
-            ink.forEach((value: StrokeData, key: string, map: any) =>
-                !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value));
-            this.ink = new InkField(idata);
-        }
-    }
+    //     if (ink) {
+    //         let idata = new Map();
+    //         ink.forEach((value: PointData, key: string, map: any) =>
+    //             !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value));
+    //         this.ink = new InkField(idata);
+    //     }
+    // }
 
     marqueeSelect(selectBackgrounds: boolean = true) {
         let selRect = this.Bounds;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 62529a5fb..5c89472ce 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -632,7 +632,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     {searchHighlight}
                 </div>
             }
-        </>
+        </>;
     }
     render() {
         if (!this.props.Document) return (null);
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index d94834e91..2d8bb582a 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -12,16 +12,12 @@ export enum InkTool {
     Scrubber
 }
 
-export interface StrokeData {
-    pathData: Array<{ x: number, y: number }>;
-    color: string;
-    width: string;
-    tool: InkTool;
-    displayTimecode: number;
-    creationTime: number;
+export interface PointData {
+    x: number;
+    y: number;
 }
 
-export type InkData = Map<string, StrokeData>;
+export type InkData = Array<PointData>;
 
 const pointSchema = createSimpleSchema({
     x: true, y: true
@@ -34,16 +30,16 @@ const strokeDataSchema = createSimpleSchema({
 
 @Deserializable("ink")
 export class InkField extends ObjectField {
-    @serializable(map(object(strokeDataSchema)))
+    @serializable(list(object(strokeDataSchema)))
     readonly inkData: InkData;
 
-    constructor(data?: InkData) {
+    constructor(data: InkData) {
         super();
-        this.inkData = data || new Map;
+        this.inkData = data;
     }
 
     [Copy]() {
-        return new InkField(DeepCopy(this.inkData));
+        return new InkField(this.inkData);
     }
 
     [ToScriptString]() {
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index e2730914f..0b28561bf 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -43,6 +43,7 @@ export const documentSchema = createSchema({
     isAnimating: "boolean",     // whether the document is in the midst of animating between two layouts (used by icons to de/iconify documents).
     animateToDimensions: listSpec("number"), // layout information about the target rectangle a document is animating towards 
     scrollToLinkID: "string",   // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc.
+    strokeWidth: "number"
 });
 
 export const positionSchema = createSchema({
-- 
cgit v1.2.3-70-g09d2


From 09aee20c101e91a5e582534d616b640a34493ac6 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Sun, 10 Nov 2019 16:37:08 -0500
Subject: nice

---
 src/client/views/DocumentDecorations.tsx | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 10764a9ce..391736e4a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -162,19 +162,19 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @computed
     get Bounds(): { x: number, y: number, b: number, r: number } {
         let x = this._forceUpdate;
-        this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, docView) => {
-            if (docView.props.renderDepth === 0 ||
-                Doc.AreProtosEqual(docView.props.Document, CurrentUserUtils.UserDocument)) {
+        this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
+            if (documentView.props.renderDepth === 0 ||
+                Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) {
                 return bounds;
             }
-            let transform = (docView.props.ScreenToLocalTransform().scale(docView.props.ContentScaling())).inverse();
+            let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();
             if (transform.TranslateX === 0 && transform.TranslateY === 0) {
                 setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds
                 return this._lastBox;
             }
 
             var [sptX, sptY] = transform.transformPoint(0, 0);
-            let [bptX, bptY] = transform.transformPoint(docView.props.PanelWidth(), docView.props.PanelHeight());
+            let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
             return {
                 x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
                 r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
@@ -205,7 +205,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         document.removeEventListener("pointerup", this.onBackgroundUp);
         document.removeEventListener("pointermove", this.onTitleMove);
         document.removeEventListener("pointerup", this.onTitleUp);
-        DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, {
+        DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(documentView => documentView.ContentDiv!), dragData, e.x, e.y, {
             handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) },
             hideSource: true
         });
@@ -564,7 +564,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let minimizeIcon = (
             <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>
                 {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
-                {(SelectionManager.SelectedDocuments().length === 1) ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
+                {SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
             </div>);
 
         bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
@@ -614,7 +614,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-borderRadius" className="documentDecorations-radius" onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}><span className="borderRadiusTooltip" title="Drag Corner Radius"></span></div>
                 <div className="link-button-container">
-                    {(SelectionManager.SelectedDocuments.length && SelectionManager.SelectedDocuments()[0]) ? <DocumentButtonBar views={SelectionManager.SelectedDocuments()} /> : (null)}
+                    <DocumentButtonBar views={SelectionManager.SelectedDocuments()} />
                 </div>
             </div >
         </div>
-- 
cgit v1.2.3-70-g09d2


From d7150995d62c498ab8435de986b90d98bdca020c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 12 Nov 2019 15:07:03 -0500
Subject: a bunch of cleanup and bug fixes to text links and doculink buttons

---
 src/client/documents/Documents.ts                  |  5 +-
 src/client/util/LinkManager.ts                     |  2 -
 src/client/util/RichTextSchema.tsx                 |  4 +-
 src/client/util/TooltipTextMenu.tsx                | 23 +++---
 src/client/views/DocumentDecorations.tsx           | 10 ++-
 src/client/views/GlobalKeyHandler.ts               |  2 +-
 src/client/views/Main.tsx                          | 24 -------
 src/client/views/MainView.scss                     |  2 +-
 src/client/views/MainView.tsx                      |  8 +--
 src/client/views/linking/LinkMenuItem.tsx          |  1 -
 src/client/views/nodes/FormattedTextBox.tsx        |  9 ++-
 src/client/views/nodes/FormattedTextBoxComment.tsx | 83 ++++++++++++----------
 src/client/views/nodes/PDFBox.scss                 |  2 +
 src/client/views/nodes/PDFBox.tsx                  | 76 +++++++++++---------
 src/client/views/pdf/Annotation.tsx                |  6 +-
 src/client/views/pdf/PDFViewer.tsx                 | 14 +---
 src/new_fields/Doc.ts                              |  2 +-
 .../authentication/models/current_user_utils.ts    |  1 -
 18 files changed, 127 insertions(+), 147 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d1fcabc4a..ba9f87025 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -257,6 +257,9 @@ export namespace Docs {
             return PrototypeMap.get(type)!;
         }
 
+        /**
+         * A collection of all links in the database.  Ideally, this would be a search, but for now all links are cached here.
+         */
         export function MainLinkDocument() {
             return Prototypes.get(DocumentType.LINKDOC);
         }
@@ -703,6 +706,7 @@ export namespace DocUtils {
 
             linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title;
             linkDocProto.linkDescription = description;
+            linkDocProto.isPrototype = true;
 
             linkDocProto.anchor1 = source.doc;
             linkDocProto.anchor2 = target.doc;
@@ -714,7 +718,6 @@ export namespace DocUtils {
             linkDocProto.anchor2Timecode = target.doc.currentTimecode;
             linkDocProto.layoutKey1 = DocuLinkBox.LayoutString("anchor1");
             linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2");
-            linkDocProto.borderRounding = "20";
             linkDocProto.width = linkDocProto.height = 0;
             linkDocProto.isBackground = true;
             linkDocProto.isButton = true;
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index ee2f2dadc..eedc4967d 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -34,8 +34,6 @@ export class LinkManager {
     // the linkmanagerdoc stores a list of docs representing all linkdocs in 'allLinks' and a list of strings representing all group types in 'allGroupTypes'
     // lists of strings representing the metadata keys for each group type is stored under a key that is the same as the group type 
     public get LinkManagerDoc(): Doc | undefined {
-        // return FieldValue(Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc));
-
         return Docs.Prototypes.MainLinkDocument();
     }
 
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index a5e0ca720..76b8aeaa1 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -305,8 +305,8 @@ export const marks: { [index: string]: MarkSpec } = {
         }],
         toDOM(node: any) {
             return node.attrs.docref && node.attrs.title ?
-                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
-                ["a", { ...node.attrs }, 0];
+                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] :
+                ["a", { ...node.attrs, title: `${node.attrs.title}` }, 0];
         }
     },
 
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 9ce7acec8..38471a955 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -17,7 +17,7 @@ import { DragManager } from "./DragManager";
 import { LinkManager } from "./LinkManager";
 import { schema } from "./RichTextSchema";
 import "./TooltipTextMenu.scss";
-import { Cast, NumCast } from '../../new_fields/Types';
+import { Cast, NumCast, StrCast } from '../../new_fields/Types';
 import { updateBullets } from './ProsemirrorExampleTransfer';
 import { DocumentDecorations } from '../views/DocumentDecorations';
 const { toggleMark, setBlockType } = require("prosemirror-commands");
@@ -284,7 +284,7 @@ export class TooltipTextMenu {
                                     if (proto && docView) {
                                         proto.sourceContext = docView.props.ContainingCollectionDoc;
                                     }
-                                    let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab");
+                                    let text = this.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), ctrlKey ? "onRight" : "inTab");
                                     if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) {
                                         proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link
                                     }
@@ -374,25 +374,20 @@ export class TooltipTextMenu {
     //     let link = state.schema.mark(state.schema.marks.link, { href: target, location: location });
     // }
 
-    makeLink = (targetDoc: Doc, location: string): string => {
-        let target = Utils.prepend("/doc/" + targetDoc[Id]);
+    makeLink = (targetDoc: Doc, title: string, location: string): string => {
+        let link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + targetDoc[Id]), title: title, location: location });
+        this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
+            addMark(this.view.state.selection.from, this.view.state.selection.to, link));
         let node = this.view.state.selection.$from.nodeAfter;
-        let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location, guid: targetDoc[Id] });
-        this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
-        this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
-        node = this.view.state.selection.$from.nodeAfter;
-        link = node && node.marks.find(m => m.type.name === "link");
-        if (node) {
-            if (node.text) {
-                return node.text;
-            }
+        if (node && node.text) {
+            return node.text;
         }
         return "";
     }
 
     deleteLink = () => {
         let node = this.view.state.selection.$from.nodeAfter;
-        let link = node && node.marks.find(m => m.type.name === "link");
+        let link = node && node.marks.find(m => m.type === this.view.state.schema.marks.link);
         let href = link!.attrs.href;
         if (href) {
             if (href.indexOf(Utils.prepend("/doc/")) === 0) {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 0336440d5..55c211d1d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -24,6 +24,7 @@ import { DocumentView } from "./nodes/DocumentView";
 import { FieldView } from "./nodes/FieldView";
 import { IconBox } from "./nodes/IconBox";
 import React = require("react");
+import { DocumentType } from '../documents/DocumentTypes';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -112,7 +113,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             }
             else {
                 if (SelectionManager.SelectedDocuments().length > 0) {
-                    SelectionManager.SelectedDocuments()[0].props.Document.customTitle = true;
+                    SelectionManager.SelectedDocuments()[0].props.Document.customTitle = !this._title.startsWith("-");
                     let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey];
                     if (typeof field === "number") {
                         SelectionManager.SelectedDocuments().forEach(d => {
@@ -174,6 +175,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
             var [sptX, sptY] = transform.transformPoint(0, 0);
             let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
+            if (documentView.props.Document.type === DocumentType.LINK) {
+                let rect = documentView.ContentDiv!.getElementsByClassName("docuLinkBox-cont")[0].getBoundingClientRect();
+                sptX = rect.left;
+                sptY = rect.top;
+                bptX = rect.right;
+                bptY = rect.bottom;
+            }
             return {
                 x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
                 r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 9ca9fc163..8f397e331 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -165,7 +165,7 @@ export default class KeyManager {
                     }
                 }
                 break;
-            case "c":
+            case "t":
                 PromiseValue(Cast(CurrentUserUtils.UserDocument.Create, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
                 if (MainView.Instance.flyoutWidth === 240) {
                     MainView.Instance.flyoutWidth = 0;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index a91a2b69e..b21eb9c8f 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -3,34 +3,11 @@ import { Docs } from "../documents/Documents";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 import * as ReactDOM from 'react-dom';
 import * as React from 'react';
-import { Cast } from "../../new_fields/Types";
-import { Doc, DocListCastAsync } from "../../new_fields/Doc";
-import { List } from "../../new_fields/List";
 import { DocServer } from "../DocServer";
 import { AssignAllExtensions } from "../../extensions/General/Extensions";
 
 AssignAllExtensions();
 
-let swapDocs = async () => {
-    let oldDoc = await Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc);
-    // Docs.Prototypes.MainLinkDocument().allLinks = new List<Doc>();
-    if (oldDoc) {
-        let links = await DocListCastAsync(oldDoc.allLinks);
-        // if (links && DocListCast(links)) {
-        if (links && links.length) {
-            let data = await DocListCastAsync(Docs.Prototypes.MainLinkDocument().allLinks);
-            if (data) {
-                data.push(...links.filter(i => data!.indexOf(i) === -1));
-                Docs.Prototypes.MainLinkDocument().allLinks = new List<Doc>(data.filter((i, idx) => data!.indexOf(i) === idx));
-            }
-            else {
-                Docs.Prototypes.MainLinkDocument().allLinks = new List<Doc>(links);
-            }
-        }
-        CurrentUserUtils.UserDocument.linkManagerDoc = undefined;
-    }
-};
-
 (async () => {
     const info = await CurrentUserUtils.loadCurrentUser();
     DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email);
@@ -38,7 +15,6 @@ let swapDocs = async () => {
     if (info.id !== "__guest__") {
         // a guest will not have an id registered
         await CurrentUserUtils.loadUserDocument(info);
-        await swapDocs();
     }
     document.getElementById('root')!.addEventListener('wheel', event => {
         if (event.ctrlKey) {
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 21b135c49..a858a73c7 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -13,7 +13,7 @@
     left: 250px;
 }
 
-.mainView-container {
+#mainView-container {
     width: 100%;
     height: 100%;
     position: absolute;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 773da05df..83dbb433b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -37,7 +37,6 @@ import { OverlayView } from './OverlayView';
 import PDFMenu from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
 import { Scripting } from '../util/Scripting';
-import { LinkManager } from '../util/LinkManager';
 import { AudioBox } from './nodes/AudioBox';
 
 @observer
@@ -197,11 +196,6 @@ export class MainView extends React.Component {
         var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] };
         let mainDoc = Docs.Create.DockDocument([freeformDoc], JSON.stringify(dockingLayout), {}, id);
         if (this.userDoc && ((workspaces = Cast(this.userDoc.workspaces, Doc)) instanceof Doc)) {
-            if (!this.userDoc.linkManagerDoc) {
-                let linkManagerDoc = new Doc();
-                linkManagerDoc.allLinks = new List<Doc>([]);
-                this.userDoc.linkManagerDoc = linkManagerDoc;
-            }
             Doc.AddDocToList(workspaces, "data", mainDoc);
             mainDoc.title = `Workspace ${DocListCast(workspaces.data).length}`;
         }
@@ -504,7 +498,7 @@ export class MainView extends React.Component {
     }
 
     render() {
-        return (<div className="mainView-container">
+        return (<div id="mainView-container">
             <DictationOverlay />
             <SharingManager />
             <GoogleAuthenticationManager />
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index a6ee9c2c6..238660de3 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -8,7 +8,6 @@ import { Cast, StrCast } from '../../../new_fields/Types';
 import { DragLinkAsDocument } from '../../util/DragManager';
 import { LinkManager } from '../../util/LinkManager';
 import { ContextMenu } from '../ContextMenu';
-import { MainView } from '../MainView';
 import { LinkFollowBox } from './LinkFollowBox';
 import './LinkMenu.scss';
 import React = require("react");
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index abf26826c..a0f8523a2 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -842,6 +842,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
         this._editorView && this._editorView.destroy();
     }
     onPointerDown = (e: React.PointerEvent): void => {
+        FormattedTextBoxComment.textBox = this;
         let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
         pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos));
         if (this.props.onClick && e.button === 0) {
@@ -1018,11 +1019,13 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
     }
 
     render() {
+        trace();
         let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
-        let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground
-            ? "none" : "all";
+        let interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground;
         if (this.props.isSelected()) {
             FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props);
+        } else if (FormattedTextBoxComment.textBox === this) {
+            FormattedTextBoxComment.Hide();
         }
         return (
             <div className={`formattedTextBox-cont`} ref={this._ref}
@@ -1031,7 +1034,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
                     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",
-                    pointerEvents: interactive,
+                    pointerEvents: interactive ? "none" : "all",
                     fontSize: this._ruleFontSize ? this._ruleFontSize : NumCast(this.layoutDoc.fontSize, 13),
                     fontFamily: this._ruleFontFamily ? this._ruleFontFamily : StrCast(this.layoutDoc.fontFamily, "Crimson Text"),
                 }}
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index bde278be3..2a7467108 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -6,8 +6,9 @@ import { Doc } from "../../../new_fields/Doc";
 import { schema } from "../../util/RichTextSchema";
 import { DocServer } from "../../DocServer";
 import { Utils } from "../../../Utils";
-import { StrCast } from "../../../new_fields/Types";
+import { StrCast, Cast, FieldValue } from "../../../new_fields/Types";
 import { FormattedTextBox } from "./FormattedTextBox";
+import { DocUtils } from "../../documents/Documents";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -61,6 +62,7 @@ export class FormattedTextBoxComment {
             FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
             FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
             FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
+            FormattedTextBoxComment.tooltip.style.maxWidth = "350px";
             FormattedTextBoxComment.tooltip.appendChild(input);
             FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
                 let keep = e.target && (e.target as any).type === "checkbox" ? true : false;
@@ -91,62 +93,67 @@ export class FormattedTextBoxComment {
         let state = view.state;
         // Don't do anything if the document/selection didn't change
         if (lastState && lastState.doc.eq(state.doc) &&
-            lastState.selection.eq(state.selection)) return;
+            lastState.selection.eq(state.selection)) {
+            return;
+        }
 
-        if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props || !FormattedTextBoxComment.textBox.props.isSelected()) return;
+        const textBox = FormattedTextBoxComment.textBox;
+        if (!textBox || !textBox.props) {
+            return;
+        }
         let set = "none";
-        if (FormattedTextBoxComment.textBox && state.selection.$from) {
-            let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
+        let nbef = 0;
+        // this section checks to see if the insertion point is over text entered by a different user.  If so, it sets ths comment text to indicate the user and the modification date
+        if (state.selection.$from) {
+            nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
             let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
-            const spos = state.selection.$from.pos - nbef;
-            const epos = state.selection.$from.pos + naft;
-            let child = state.selection.$from.nodeBefore;
-            let mark = child && findOtherUserMark(child.marks);
             let noselection = view.state.selection.$from === view.state.selection.$to;
+            let child: any = null;
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+            let mark = child && findOtherUserMark(child.marks);
             if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
-                FormattedTextBoxComment.SetState(this, mark.attrs.opened, spos, epos, mark);
+                FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, mark.attrs.opened, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
             }
-            if (mark && child && nbef && naft) {
-                FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified;
-                // These are in screen coordinates
-                // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
-                let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
-                // The box in which the tooltip is positioned, to use as base
-                let box = (document.getElementById("main-div") as any).getBoundingClientRect();
-                // Find a center-ish x position from the selection endpoints (when
-                // crossing lines, end may be more to the left)
-                let left = Math.max((start.left + end.left) / 2, start.left + 3);
-                FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
-                FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+            if (mark && child && ((nbef && naft) || !noselection)) {
+                FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString();
                 set = "";
             }
         }
+        // this checks if the selection is a hyperlink.  If so, it displays the target doc's text for internal links, and the url of the target for external links. 
         if (set === "none" && state.selection.$from) {
-            FormattedTextBoxComment.textBox = undefined;
-            let nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
+            nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
             let naft = findEndOfMark(state.selection.$from, view, findLinkMark);
-            let child = state.selection.$from.nodeBefore;
+            let child: any = null;
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
             let mark = child && findLinkMark(child.marks);
             if (mark && child && nbef && naft) {
-                FormattedTextBoxComment.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href);
+                FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
                 if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
                     let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    docTarget && DocServer.GetRefField(docTarget).then(linkDoc =>
-                        (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc).title)));
+                    docTarget && DocServer.GetRefField(docTarget).then(linkDoc => {
+                        if (linkDoc instanceof Doc) {
+                            let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc));
+                            let ext = (target && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field.  probably need an 'overviewLayout' and then just display the target Document ....
+                            let text = ext && StrCast(ext.text);
+                            ext && (FormattedTextBoxComment.tooltipText.textContent = "=> " + (text || StrCast(ext.title)));
+                        }
+                    });
                 }
-                // These are in screen coordinates
-                // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
-                let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
-                // The box in which the tooltip is positioned, to use as base
-                let box = (document.getElementById("main-div") as any).getBoundingClientRect();
-                // Find a center-ish x position from the selection endpoints (when
-                // crossing lines, end may be more to the left)
-                let left = Math.max((start.left + end.left) / 2, start.left + 3);
-                FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
-                FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
                 set = "";
             }
         }
+        if (set !== "none") {
+            // These are in screen coordinates
+            // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
+            let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+            // The box in which the tooltip is positioned, to use as base
+            let box = (document.getElementById("mainView-container") as any).getBoundingClientRect();
+            // Find a center-ish x position from the selection endpoints (when
+            // crossing lines, end may be more to the left)
+            let left = Math.max((start.left + end.left) / 2, start.left + 3);
+            FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
+            FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+        }
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
     }
 
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 963205206..2d92c9581 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -48,6 +48,7 @@
     }
     .pdfViewer-text {
         .textLayer {
+            will-change: transform;
             span {
                 user-select: none;
             }
@@ -59,6 +60,7 @@
     pointer-events: all;
     .pdfViewer-text {
         .textLayer {
+            will-change: transform;
             span {
                 user-select: text;
             }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 3baa6eb09..8e0515f8a 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, runInAction, reaction, IReactionDisposer, trace } from 'mobx';
+import { action, observable, runInAction, reaction, IReactionDisposer, trace, untracked, computed } from 'mobx';
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
@@ -32,7 +32,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     private _valueValue: string = "";
     private _scriptValue: string = "";
     private _searchString: string = "";
-    private _initialScale: number | undefined;  // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not.  Getting bigger after startup won't make it automatically be live.
+    private _initialScale: number = 0;  // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not.  Getting bigger after startup won't make it automatically be live.
     private _everActive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title
     private _pdfViewer: PDFViewer | undefined;
     private _searchRef = React.createRef<HTMLInputElement>();
@@ -46,6 +46,11 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
     @observable private _pageControls = false;
 
+    constructor(props: any) {
+        super(props);
+        this._initialScale = this.props.ScreenToLocalTransform().Scale;
+    }
+
     componentWillUnmount() {
         this._selectReactionDisposer && this._selectReactionDisposer();
     }
@@ -190,39 +195,46 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
         ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" });
     }
 
-    render() {
+    @computed get renderTitleBox() {
+        let classname = "pdfBox-cont" + (this.active() ? "-interactive" : "");
+        return <div className="pdfBox-title-outer" >
+            <div className={classname} >
+                <strong className="pdfBox-title" >{` ${this.props.Document.title}`}</strong>
+            </div>
+        </div>;
+    }
+
+    @computed get renderPdfView() {
         trace();
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
         let classname = "pdfBox-cont" + (this.active() ? "-interactive" : "");
-        let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf;
-        if (this._initialScale === undefined) this._initialScale = this.props.ScreenToLocalTransform().Scale;
+        return <div className={classname} style={{
+            width: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
+            height: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
+            transform: `scale(${this.props.Document.fitWidth ? this.props.ContentScaling() : 1})`
+        }} onContextMenu={this.specificContextMenu} onPointerDown={e => {
+            let hit = document.elementFromPoint(e.clientX, e.clientY);
+            if (hit && hit.localName === "span" && this.props.isSelected()) {  // drag selecting text stops propagation
+                e.button === 0 && e.stopPropagation();
+            }
+        }}>
+            <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded}
+                setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
+                renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
+                Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
+                addDocTab={this.props.addDocTab} focus={this.props.focus}
+                pinToPres={this.props.pinToPres} addDocument={this.addDocument}
+                ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
+                isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
+                fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 ? true : false} />
+            {this.settingsPanel()}
+        </div>;
+    }
+
+    render() {
+        const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null);
         if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true;
-        return (!this.extensionDoc || noPdf || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ?
-            <div className="pdfBox-title-outer" >
-                <div className={classname} >
-                    <strong className="pdfBox-title" >{` ${this.props.Document.title}`}</strong>
-                </div>
-            </div> :
-            <div className={classname} style={{
-                width: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
-                height: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
-                transform: `scale(${this.props.Document.fitWidth ? this.props.ContentScaling() : 1})`
-            }} onContextMenu={this.specificContextMenu} onPointerDown={e => {
-                let hit = document.elementFromPoint(e.clientX, e.clientY);
-                if (hit && hit.localName === "span" && this.props.isSelected()) {  // drag selecting text stops propagation
-                    e.button === 0 && e.stopPropagation();
-                }
-            }}>
-                <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded}
-                    setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
-                    renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
-                    Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
-                    addDocTab={this.props.addDocTab} focus={this.props.focus}
-                    pinToPres={this.props.pinToPres} addDocument={this.addDocument}
-                    ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
-                    isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
-                    fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 ? true : false} />
-                {this.settingsPanel()}
-            </div>);
+        return !pdfUrl || !this._pdf || !this.extensionDoc || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ?
+            this.renderTitleBox : this.renderPdfView;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 2d8f47666..936af9ab8 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -52,11 +52,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
 
         this._brushDisposer = reaction(
             () => FieldValue(Cast(this.props.document.group, Doc)) && Doc.isBrushedHighlightedDegree(FieldValue(Cast(this.props.document.group, Doc))!),
-            (brushed) => {
-                if (brushed !== undefined) {
-                    runInAction(() => this._brushed = brushed !== 0);
-                }
-            }
+            brushed => brushed !== undefined && runInAction(() => this._brushed = brushed !== 0)
         );
     }
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 060ba8613..0cb671156 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -199,7 +199,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
 
         this._annotationReactionDisposer = reaction(
             () => this.extensionDoc && DocListCast(this.extensionDoc.annotations),
-            annotations => annotations && annotations.length && this.renderAnnotations(annotations, true),
+            annotations => annotations && annotations.length && (this._annotations = annotations),
             { fireImmediately: true });
 
         this._filterReactionDisposer = reaction(
@@ -297,18 +297,6 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         this.Index = -1;
         return mainAnnoDoc;
     }
-
-    @action
-    renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => {
-        if (removeOldAnnotations) {
-            this._annotations = annotations;
-        }
-        else {
-            this._annotations.push(...annotations);
-            this._annotations = new Array<Doc>(...this._annotations);
-        }
-    }
-
     @action
     prevAnnotation = () => {
         this.Index = Math.max(this.Index - 1, 0);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 6aad4a6be..3bf1129b5 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -669,8 +669,8 @@ export namespace Doc {
         return doc;
     }
 
-    export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; }
 
+    export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; }
     export function linkFollowUnhighlight() {
         Doc.UnhighlightAll();
         document.removeEventListener("pointerdown", linkFollowUnhighlight);
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 56ea5bfe1..833e44bf6 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -15,7 +15,6 @@ import { RouteStore } from "../../RouteStore";
 import { InkingControl } from "../../../client/views/InkingControl";
 import { DragManager } from "../../../client/util/DragManager";
 import { nullAudio } from "../../../new_fields/URLField";
-import { LinkManager } from "../../../client/util/LinkManager";
 
 export class CurrentUserUtils {
     private static curr_id: string;
-- 
cgit v1.2.3-70-g09d2


From c9fbdb9cfd5fcf35f7a599706cb41d4e7e586e19 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 14 Nov 2019 16:23:57 -0500
Subject: added optional sidebar for text notes

---
 src/client/views/DocumentDecorations.tsx           | 11 +-----
 src/client/views/nodes/FormattedTextBox.scss       | 12 +++++++
 src/client/views/nodes/FormattedTextBox.tsx        | 39 +++++++++++++++++++---
 src/client/views/nodes/FormattedTextBoxComment.tsx |  5 ++-
 4 files changed, 49 insertions(+), 18 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 55c211d1d..49921170f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -157,22 +157,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         this.onBackgroundUp(e);
     }
 
-    @observable _forceUpdate = 0;
-    _lastBox = { x: 0, y: 0, r: 0, b: 0 };
     @computed
     get Bounds(): { x: number, y: number, b: number, r: number } {
-        let x = this._forceUpdate;
-        this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
+        return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
             if (documentView.props.renderDepth === 0 ||
                 Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) {
                 return bounds;
             }
             let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();
-            if (transform.TranslateX === 0 && transform.TranslateY === 0) {
-                setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds
-                return this._lastBox;
-            }
-
             var [sptX, sptY] = transform.transformPoint(0, 0);
             let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
             if (documentView.props.Document.type === DocumentType.LINK) {
@@ -187,7 +179,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
             };
         }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
-        return this._lastBox;
     }
 
     onBackgroundDown = (e: React.PointerEvent): void => {
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index a4acd3b82..98b57bd84 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -36,6 +36,18 @@
     }
 }
 
+.collectionfreeformview-container {
+    position: relative;
+}
+.formattedTextBox-outer {
+    position: relative;
+    overflow: auto;
+    display: inline-block;
+    padding: 10px 10px;
+    width: 100%;
+    height: 100%;
+}
+
 .formattedTextBox-inner-rounded {
     height: 70%;
     width: 85%;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 6d60e8f77..b7df3fb0e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -18,7 +18,7 @@ import { RichTextField } from "../../../new_fields/RichTextField";
 import { RichTextUtils } from '../../../new_fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
 import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
-import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from '../../../Utils';
+import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnOne } from '../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -33,7 +33,7 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
 import { TooltipTextMenu } from "../../util/TooltipTextMenu";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DocExtendableComponent } from "../DocComponent";
+import { DocAnnotatableComponent } from "../DocComponent";
 import { DocumentButtonBar } from '../DocumentButtonBar';
 import { DocumentDecorations } from '../DocumentDecorations';
 import { InkingControl } from "../InkingControl";
@@ -46,6 +46,7 @@ import { ContextMenu } from '../ContextMenu';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { documentSchema } from '../../../new_fields/documentSchemas';
 import { AudioBox } from './AudioBox';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -70,7 +71,7 @@ const RichTextDocument = makeInterface(richTextSchema, documentSchema);
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
 @observer
-export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
+export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
     public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
     public static Instance: FormattedTextBox;
@@ -359,6 +360,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
 
     specificContextMenu = (e: React.MouseEvent): void => {
         let funcs: ContextMenuProps[] = [];
+        funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%"; }, icon: "expand-arrows-alt" });
         funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" });
         ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
             funcs.push({
@@ -1010,6 +1012,9 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
         }
     }
 
+    @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidth, "0%"); }
+    @computed get sidebarWidth() { return Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); }
+    @computed get annotationsKey() { return "annotations"; }
     render() {
         trace();
         let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
@@ -1042,8 +1047,32 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
                 onPointerEnter={action(() => this._entered = true)}
                 onPointerLeave={action(() => this._entered = false)}
             >
-                <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
-
+                <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }}>
+                    <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
+                </div>
+                {this.sidebarWidthPercent === "0%" ? (null) : <div style={{ borderLeft: "solid 1px black", width: `${this.sidebarWidthPercent}`, height: "100%", display: "inline-block" }}>
+                    <CollectionFreeFormView {...this.props}
+                        PanelHeight={() => this.props.PanelHeight()}
+                        PanelWidth={() => this.sidebarWidth}
+                        annotationsKey={this.annotationsKey}
+                        isAnnotationOverlay={true}
+                        focus={this.props.focus}
+                        isSelected={this.props.isSelected}
+                        select={emptyFunction}
+                        active={this.active}
+                        ContentScaling={returnOne}
+                        whenActiveChanged={this.whenActiveChanged}
+                        removeDocument={this.removeDocument}
+                        moveDocument={this.moveDocument}
+                        addDocument={this.addDocument}
+                        CollectionView={undefined}
+                        ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth), 0)}
+                        ruleProvider={undefined}
+                        renderDepth={this.props.renderDepth + 1}
+                        ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                        chromeCollapsed={true}>
+                    </CollectionFreeFormView>
+                </div>}
                 <div className="formattedTextBox-dictation"
                     onClick={e => {
                         this._recording ? this.stopDictation(true) : this.recordDictation();
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 04958525c..c076fd34a 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -87,7 +87,7 @@ export class FormattedTextBoxComment {
                     DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
                         (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight"));
                 } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
-                    textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { width: 200, height: 400 }), undefined, "onRight");
+                    textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, width: 200, height: 400 }), undefined, "onRight");
                 }
                 FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened;
                 textBox && FormattedTextBoxComment.start !== undefined && textBox.setAnnotation(
@@ -97,7 +97,6 @@ export class FormattedTextBoxComment {
             };
             root && root.appendChild(FormattedTextBoxComment.tooltip);
         }
-        //this.update(view, undefined);
     }
 
     public static Hide() {
@@ -223,5 +222,5 @@ export class FormattedTextBoxComment {
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
     }
 
-    destroy() { }//FormattedTextBoxComment.tooltip.style.display = "none"; }
+    destroy() { }
 }
-- 
cgit v1.2.3-70-g09d2


From 79c66275c60001e6d7aa997d9802ec0fed893205 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 15 Nov 2019 15:03:08 -0500
Subject: fixed toolbar disappearing with multiple text boxes.

---
 src/client/util/TooltipTextMenu.tsx         | 6 ++----
 src/client/views/DocumentDecorations.tsx    | 4 ++--
 src/client/views/nodes/FormattedTextBox.tsx | 2 +-
 3 files changed, 5 insertions(+), 7 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 0dc63668b..14f8b2bd4 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,6 +1,4 @@
-import { library, dom } from '@fortawesome/fontawesome-svg-core';
-import { faListUl } from '@fortawesome/free-solid-svg-icons';
-import { action, observable } from "mobx";
+import { action } from "mobx";
 import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css
 import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
 import { wrapInList } from 'prosemirror-schema-list';
@@ -1482,7 +1480,7 @@ export class TooltipTextMenu {
     }
 
     destroy() {
-        this.wrapper.remove();
+        // this.wrapper.remove();
     }
 }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 49921170f..890c32bcb 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -548,8 +548,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         }
     }
     public showTextBar = () => {
-        if (this.TextBar) {
-            TooltipTextMenu.Toolbar && Array.from(this.TextBar.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && this.TextBar.appendChild(TooltipTextMenu.Toolbar);
+        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/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0a535045b..35212b732 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -17,7 +17,7 @@ import { Copy, Id } from '../../../new_fields/FieldSymbols';
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { RichTextUtils } from '../../../new_fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnOne } from '../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../DocServer";
-- 
cgit v1.2.3-70-g09d2

