From 461af95c312fc7aff85551c014fb93dc7a89cecd Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Sun, 10 Nov 2019 16:28:48 -0500
Subject: first commit

---
 src/client/views/MainView.tsx | 5 +++++
 1 file changed, 5 insertions(+)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 773da05df..294e781b7 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -39,6 +39,7 @@ import { PreviewCursor } from './PreviewCursor';
 import { Scripting } from '../util/Scripting';
 import { LinkManager } from '../util/LinkManager';
 import { AudioBox } from './nodes/AudioBox';
+import SettingsManager from '../util/SettingsManager';
 
 @observer
 export class MainView extends React.Component {
@@ -412,6 +413,9 @@ export class MainView extends React.Component {
                     zoomToScale={emptyFunction}
                     getScale={returnOne}>
                 </DocumentView>
+                <button className="mainView-settings" key="settings" onClick={() => SettingsManager.Instance.open()}>
+                    Settings
+                </button>
                 <button className="mainView-logout" key="logout" onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}>
                     {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
                 </button>
@@ -507,6 +511,7 @@ export class MainView extends React.Component {
         return (<div className="mainView-container">
             <DictationOverlay />
             <SharingManager />
+            <SettingsManager />
             <GoogleAuthenticationManager />
             <DocumentDecorations />
             {this.mainContent}
-- 
cgit v1.2.3-70-g09d2


From cef6852d597ce67637466afb36c3498dc84211f6 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Wed, 8 Jan 2020 19:48:28 -0500
Subject: gestures are now overlayed and can span collections/panes!

---
 src/client/documents/Documents.ts                  |   3 +-
 src/client/views/CollectionLinearView.tsx          |   4 +-
 src/client/views/GestureOverlay.scss               |   8 +
 src/client/views/GestureOverlay.tsx                | 159 +++++++++++++++++++
 src/client/views/InkSelectDecorations.scss         |   5 -
 src/client/views/InkSelectDecorations.tsx          |  55 -------
 src/client/views/InkingStroke.tsx                  |   6 +-
 src/client/views/MainView.scss                     |   7 +-
 src/client/views/MainView.tsx                      |   7 +-
 .../views/collections/CollectionStackingView.tsx   |   6 +-
 src/client/views/collections/CollectionSubView.tsx |  13 +-
 .../views/collections/CollectionTreeView.scss      |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 171 +++++++--------------
 src/client/views/nodes/DocumentView.tsx            |  19 ++-
 src/pen-gestures/GestureUtils.ts                   |  28 +++-
 15 files changed, 299 insertions(+), 198 deletions(-)
 create mode 100644 src/client/views/GestureOverlay.scss
 create mode 100644 src/client/views/GestureOverlay.tsx
 delete mode 100644 src/client/views/InkSelectDecorations.scss
 delete mode 100644 src/client/views/InkSelectDecorations.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e149963b9..1853be529 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -29,7 +29,7 @@ import { listSpec } from "../../new_fields/Schema";
 import { DocServer } from "../DocServer";
 import { dropActionType } from "../util/DragManager";
 import { DateField } from "../../new_fields/DateField";
-import { UndoManager } from "../util/UndoManager";
+import { UndoManager, undoBatch } from "../util/UndoManager";
 import { YoutubeBox } from "../apis/youtube/YoutubeBox";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import { LinkManager } from "../util/LinkManager";
@@ -730,6 +730,7 @@ export namespace DocUtils {
         });
     }
 
+    @undoBatch
     export function MakeLink(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, title: string = "", description: string = "", id?: string) {
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
         if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 5ca861f71..5d6a58656 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -36,7 +36,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
             { fireImmediately: true }
         );
     }
-    protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+    protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
         this._dropDisposer && this._dropDisposer();
         if (ele) {
             this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
@@ -55,7 +55,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     render() {
         const guid = Utils.GenerateGuid();
         return <div className="collectionLinearView-outer">
-            <div className="collectionLinearView" ref={this.createDropTarget} >
+            <div className="collectionLinearView" ref={this.createDropAndGestureTarget} >
                 <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.isExpanded)} ref={this.addMenuToggle}
                     onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} />
                 <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
new file mode 100644
index 000000000..cbc1b6e7d
--- /dev/null
+++ b/src/client/views/GestureOverlay.scss
@@ -0,0 +1,8 @@
+.gestureOverlay-cont {
+    width: 100vw;
+    height: 100vw;
+    position: absolute;
+    top: 0;
+    left: 0;
+    touch-action: none;
+}
\ No newline at end of file
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
new file mode 100644
index 000000000..c88e3f7ae
--- /dev/null
+++ b/src/client/views/GestureOverlay.tsx
@@ -0,0 +1,159 @@
+import React = require("react");
+import { Touchable } from "./Touchable";
+import { observer } from "mobx-react";
+import "./GestureOverlay.scss"
+import { computed, observable, action } from "mobx";
+import { CreatePolyline } from "./InkingStroke";
+import { GestureUtils } from "../../pen-gestures/GestureUtils";
+import { InteractionUtils } from "../util/InteractionUtils";
+import { InkingControl } from "./InkingControl";
+import { InkTool } from "../../new_fields/InkField";
+import { Doc } from "../../new_fields/Doc";
+import { LinkManager } from "../util/LinkManager";
+import { DocUtils } from "../documents/Documents";
+import { undoBatch } from "../util/UndoManager";
+
+@observer
+export default class GestureOverlay extends Touchable {
+    static Instance: GestureOverlay;
+
+    @observable private _points: { X: number, Y: number }[] = [];
+
+    private _d1: Doc | undefined;
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+
+        GestureOverlay.Instance = this;
+    }
+
+    @action
+    onPointerDown = (e: React.PointerEvent) => {
+        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+            this._points.push({ X: e.clientX, Y: e.clientY });
+            e.stopPropagation();
+            e.preventDefault();
+
+            document.removeEventListener("pointermove", this.onPointerMove);
+            document.removeEventListener("pointerup", this.onPointerUp);
+            document.addEventListener("pointermove", this.onPointerMove);
+            document.addEventListener("pointerup", this.onPointerUp);
+        }
+    }
+
+    @action
+    onPointerMove = (e: PointerEvent) => {
+        this._points.push({ X: e.clientX, Y: e.clientY });
+        e.stopPropagation();
+        e.preventDefault();
+    }
+
+    @action
+    onPointerUp = (e: PointerEvent) => {
+        if (this._points.length > 1) {
+            const B = this.svgBounds;
+            const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
+
+            const result = GestureUtils.GestureRecognizer.Recognize(new Array(points));
+            let actionPerformed = false;
+            if (result && result.Score > 0.7) {
+                switch (result.Name) {
+                    case GestureUtils.Gestures.Box:
+                        const target = document.elementFromPoint(this._points[0].X, this._points[0].Y);
+                        target?.dispatchEvent(new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
+                            {
+                                bubbles: true,
+                                detail: {
+                                    points: this._points,
+                                    gesture: GestureUtils.Gestures.Box,
+                                    bounds: B
+                                }
+                            }));
+                        actionPerformed = true;
+                        break;
+                    case GestureUtils.Gestures.Line:
+                        const ep1 = this._points[0];
+                        const ep2 = this._points[this._points.length - 1];
+                        const target1 = document.elementFromPoint(ep1.X, ep1.Y);
+                        const target2 = document.elementFromPoint(ep2.X, ep2.Y);
+                        const callback = (doc: Doc) => {
+                            if (!this._d1) {
+                                this._d1 = doc;
+                            }
+                            else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
+                                DocUtils.MakeLink({ doc: this._d1 }, { doc: doc });
+                                actionPerformed = true;
+                            }
+                        }
+                        const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
+                            {
+                                bubbles: true,
+                                detail: {
+                                    points: this._points,
+                                    gesture: GestureUtils.Gestures.Line,
+                                    bounds: B,
+                                    callbackFn: callback
+                                }
+                            })
+                        target1?.dispatchEvent(ge);
+                        target2?.dispatchEvent(ge);
+                        break;
+                }
+                if (actionPerformed) {
+                    this._points = [];
+                }
+            }
+
+            if (!actionPerformed) {
+                const target = document.elementFromPoint(this._points[0].X, this._points[0].Y);
+                target?.dispatchEvent(
+                    new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
+                        {
+                            bubbles: true,
+                            detail: {
+                                points: this._points,
+                                gesture: GestureUtils.Gestures.Stroke,
+                                bounds: B
+                            }
+                        }
+                    )
+                )
+                this._points = [];
+            }
+        }
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+    }
+
+    @computed get svgBounds() {
+        const xs = this._points.map(p => p.X);
+        const ys = this._points.map(p => p.Y);
+        const right = Math.max(...xs);
+        const left = Math.min(...xs);
+        const bottom = Math.max(...ys);
+        const 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);
+        }
+
+        const B = this.svgBounds;
+
+        return (
+            <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
+                {CreatePolyline(this._points, B.left, B.top)}
+            </svg>
+        );
+    }
+
+    render() {
+        return (
+            <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown}>
+                {this.props.children}
+                {this.currentStroke}
+            </div>);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/InkSelectDecorations.scss b/src/client/views/InkSelectDecorations.scss
deleted file mode 100644
index daff58fd6..000000000
--- a/src/client/views/InkSelectDecorations.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.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
deleted file mode 100644
index 3ad50762d..000000000
--- a/src/client/views/InkSelectDecorations.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React = require("react");
-import { Touchable } from "./Touchable";
-import { PointData } 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 } {
-        const left = Number.MAX_VALUE;
-        const top = Number.MAX_VALUE;
-        const right = -Number.MAX_VALUE;
-        const bottom = -Number.MAX_VALUE;
-        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 };
-    }
-
-    render() {
-        const bounds = this.Bounds;
-        return <div style={{
-            top: bounds.y, left: bounds.x,
-            height: bounds.b - bounds.y,
-            width: bounds.r - bounds.x
-        }} />;
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index a413eebc9..8b346d5d9 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -9,6 +9,7 @@ import { InkingControl } from "./InkingControl";
 import "./InkingStroke.scss";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
 import React = require("react");
+import { TraceMobx } from "../../new_fields/util";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
@@ -35,6 +36,7 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
     @computed get PanelHeight() { return this.props.PanelHeight(); }
 
     render() {
+        TraceMobx();
         const data: InkData = Cast(this.Document.data, InkField)?.inkData ?? [];
         const xs = data.map(p => p.X);
         const ys = data.map(p => p.Y);
@@ -42,7 +44,7 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
         const top = Math.min(...ys);
         const right = Math.max(...xs);
         const bottom = Math.max(...ys);
-        const points = CreatePolyline(data, 0, 0, this.Document.color, this.Document.strokeWidth);
+        const points = CreatePolyline(data, left, top, this.Document.color, this.Document.strokeWidth);
         const width = right - left;
         const height = bottom - top;
         const scaleX = this.PanelWidth / width;
@@ -50,7 +52,7 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
         return (
             <svg width={width} height={height} style={{
                 transformOrigin: "top left",
-                transform: `translate(${left}px, ${top}px) scale(${scaleX}, ${scaleY})`,
+                transform: `scale(${scaleX}, ${scaleY})`,
                 mixBlendMode: this.Document.tool === InkTool.Highlighter ? "multiply" : "unset",
                 pointerEvents: "all"
             }}>
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 4c8c95529..2a1cc3303 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -9,8 +9,8 @@
 
 .mainContent-div {
     position: relative;
-    width:100%;
-    height:100%;
+    width: 100%;
+    height: 100%;
 }
 
 // add nodes menu. Note that the + button is actually an input label, not an actual button.
@@ -28,6 +28,7 @@
     top: 0;
     left: 0;
     z-index: 1;
+    touch-action: none;
 }
 
 .mainView-mainContent {
@@ -66,7 +67,7 @@
 
 .mainView-libraryFlyout {
     height: 100%;
-    width:100%;
+    width: 100%;
     position: absolute;
     display: flex;
     flex-direction: column;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a1196ee1c..14c1803c0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -36,7 +36,7 @@ import { OverlayView } from './OverlayView';
 import PDFMenu from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
 import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
-import InkSelectDecorations from './InkSelectDecorations';
+import GestureOverlay from './GestureOverlay';
 import { Scripting } from '../util/Scripting';
 import { AudioBox } from './nodes/AudioBox';
 import { TraceMobx } from '../../new_fields/util';
@@ -510,8 +510,9 @@ export class MainView extends React.Component {
             <SharingManager />
             <GoogleAuthenticationManager />
             <DocumentDecorations />
-            <InkSelectDecorations />
-            {this.mainContent}
+            <GestureOverlay>
+                {this.mainContent}
+            </GestureOverlay>
             <PreviewCursor />
             <ContextMenu />
             <PDFMenu />
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e71e11b48..992820fc7 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -153,7 +153,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     createRef = (ele: HTMLDivElement | null) => {
         this._masonryGridRef = ele;
-        this.createDropTarget(ele!); //so the whole grid is the drop target?
+        this.createDropAndGestureTarget(ele!); //so the whole grid is the drop target?
     }
 
     overlays = (doc: Doc) => {
@@ -309,7 +309,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             docList={docList}
             parent={this}
             type={type}
-            createDropTarget={this.createDropTarget}
+            createDropTarget={this.createDropAndGestureTarget}
             screenToLocalTransform={this.props.ScreenToLocalTransform}
         />;
     }
@@ -342,7 +342,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             docList={docList}
             parent={this}
             type={type}
-            createDropTarget={this.createDropTarget}
+            createDropTarget={this.createDropAndGestureTarget}
             screenToLocalTransform={this.props.ScreenToLocalTransform}
             setDocHeight={this.setDocHeight}
         />;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 062521690..73dc7edc6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -23,6 +23,7 @@ import { basename } from 'path';
 import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
 import { Networking } from "../../Network";
+import { GestureUtils } from "../../../pen-gestures/GestureUtils";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc) => boolean;
@@ -47,15 +48,18 @@ export interface SubCollectionViewProps extends CollectionViewProps {
 export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
     class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) {
         private dropDisposer?: DragManager.DragDropDisposer;
+        private gestureDisposer?: GestureUtils.GestureEventDisposer;
         private _childLayoutDisposer?: IReactionDisposer;
-        protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+        protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer && this.dropDisposer();
+            this.gestureDisposer && this.gestureDisposer();
             if (ele) {
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+                this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
             }
         }
         protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view
-            this.createDropTarget(ele);
+            this.createDropAndGestureTarget(ele);
         }
 
         componentDidMount() {
@@ -129,6 +133,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             }
         }
 
+        @undoBatch
+        protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
+
+        }
+
         @undoBatch
         @action
         protected drop(e: Event, de: DragManager.DropEvent): boolean {
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 0b9dc2eb2..2fa6813d7 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -7,9 +7,9 @@
     border-radius: inherit;
     box-sizing: border-box;
     height: 100%;
-    width:100%;
+    width: 100%;
     position: relative;
-    top:0;
+    top: 0;
     padding-left: 10px;
     padding-right: 10px;
     background: $light-color-secondary;
@@ -17,6 +17,7 @@
     overflow: auto;
     user-select: none;
     cursor: default;
+    touch-action: pan-y;
 
     ul {
         list-style: none;
@@ -115,6 +116,7 @@
 .treeViewItem-header {
     border: transparent 1px solid;
     display: flex;
+
     .editableView-container-editing-oneLine {
         min-width: 15px;
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index eb5a074bb..9656debf3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -272,11 +272,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return clusterColor;
     }
 
-    @observable private _points: { X: number, Y: number }[] = [];
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        if (e.nativeEvent.cancelBubble) return;
+        if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+            return;
+        }
         this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
         if (e.button === 0 && !e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) {
             document.removeEventListener("pointermove", this.onPointerMove);
@@ -284,14 +285,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             document.addEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointerup", this.onPointerUp);
             // if physically using a pen or we're in pen or highlighter mode
-            if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
-                e.stopPropagation();
-                e.preventDefault();
-                const point = this.getTransform().transformPoint(e.pageX, e.pageY);
-                this._points.push({ X: point[0], Y: point[1] });
-            }
+            // if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+            //     e.stopPropagation();
+            //     e.preventDefault();
+            //     const point = this.getTransform().transformPoint(e.pageX, e.pageY);
+            //     this._points.push({ X: point[0], Y: point[1] });
+            // }
             // if not using a pen and in no ink mode
-            else if (InkingControl.Instance.selectedTool === InkTool.None) {
+            if (InkingControl.Instance.selectedTool === InkTool.None) {
                 this._lastX = e.pageX;
                 this._lastY = e.pageY;
             }
@@ -332,13 +333,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 document.addEventListener("touchmove", this.onTouch);
                 document.removeEventListener("touchend", this.onTouchEnd);
                 document.addEventListener("touchend", this.onTouchEnd);
-                if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) {
-                    e.stopPropagation();
-                    e.preventDefault();
-                    const point = this.getTransform().transformPoint(pt.pageX, pt.pageY);
-                    this._points.push({ X: point[0], Y: point[1] });
-                }
-                else if (InkingControl.Instance.selectedTool === InkTool.None) {
+                // if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) {
+                //     e.stopPropagation();
+                //     e.preventDefault();
+                //     const point = this.getTransform().transformPoint(pt.pageX, pt.pageY);
+                //     this._points.push({ X: point[0], Y: point[1] });
+                // }
+                if (InkingControl.Instance.selectedTool === InkTool.None) {
                     this._lastX = pt.pageX;
                     this._lastY = pt.pageY;
                     e.stopPropagation();
@@ -352,72 +353,44 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
     }
 
-    @action
-    onPointerUp = (e: PointerEvent): void => {
-        if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && this._points.length <= 1) return;
-
-        if (this._points.length > 1) {
-            const B = this.svgBounds;
-            const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
-
-            const result = GestureUtils.GestureRecognizer.Recognize(new Array(points));
-            let actionPerformed = false;
-            if (result && result.Score > 0.7) {
-                switch (result.Name) {
-                    case GestureUtils.Gestures.Box:
-                        const bounds = { x: Math.min(...this._points.map(p => p.X)), r: Math.max(...this._points.map(p => p.X)), y: Math.min(...this._points.map(p => p.Y)), b: Math.max(...this._points.map(p => p.Y)) };
-                        const sel = this.getActiveDocuments().filter(doc => {
-                            const l = NumCast(doc.x);
-                            const r = l + doc[WidthSym]();
-                            const t = NumCast(doc.y);
-                            const b = t + doc[HeightSym]();
-                            const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t);
-                            if (pass) {
-                                doc.x = l - B.left - B.width / 2;
-                                doc.y = t - B.top - B.height / 2;
-                            }
-                            return pass;
-                        });
-                        this.addDocument(Docs.Create.FreeformDocument(sel, { x: B.left, y: B.top, width: B.width, height: B.height, panX: 0, panY: 0 }));
-                        sel.forEach(d => this.props.removeDocument(d));
-                        actionPerformed = true;
-                        break;
-                    case GestureUtils.Gestures.Line:
-                        const ep1 = this._points[0];
-                        const ep2 = this._points[this._points.length - 1];
-                        let d1: Doc | undefined;
-                        let d2: Doc | undefined;
-                        this.getActiveDocuments().map(doc => {
-                            const l = NumCast(doc.x);
-                            const r = l + doc[WidthSym]();
-                            const t = NumCast(doc.y);
-                            const b = t + doc[HeightSym]();
-                            if (!d1 && l < ep1.X && r > ep1.X && t < ep1.Y && b > ep1.Y) {
-                                d1 = doc;
-                            }
-                            else if (!d2 && l < ep2.X && r > ep2.X && t < ep2.Y && b > ep2.Y) {
-                                d2 = doc;
-                            }
-                        });
-                        if (d1 && d2) {
-                            if (!LinkManager.Instance.doesLinkExist(d1, d2)) {
-                                DocUtils.MakeLink({ doc: d1 }, { doc: d2 });
-                                actionPerformed = true;
-                            }
-                        }
-                        break;
-                }
-                if (actionPerformed) {
-                    this._points = [];
-                }
-            }
-
-            if (!actionPerformed) {
-                const 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 });
+    @undoBatch
+    onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
+        switch (ge.gesture) {
+            case GestureUtils.Gestures.Stroke:
+                const points = ge.points;
+                const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
+                const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, width: B.width, height: B.height });
                 this.addDocument(inkDoc);
-                this._points = [];
-            }
+                e.stopPropagation();
+                break;
+            case GestureUtils.Gestures.Box:
+                const lt = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
+                const rb = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
+                const bounds = { x: lt[0], r: rb[0], y: lt[1], b: rb[1] };
+                const bWidth = bounds.r - bounds.x;
+                const bHeight = bounds.b - bounds.y;
+                const sel = this.getActiveDocuments().filter(doc => {
+                    const l = NumCast(doc.x);
+                    const r = l + doc[WidthSym]();
+                    const t = NumCast(doc.y);
+                    const b = t + doc[HeightSym]();
+                    const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t);
+                    if (pass) {
+                        doc.x = l - bounds.x - bWidth / 2;
+                        doc.y = t - bounds.y - bHeight / 2;
+                    }
+                    return pass;
+                });
+                this.addDocument(Docs.Create.FreeformDocument(sel, { x: bounds.x, y: bounds.y, width: bWidth, height: bHeight, panX: 0, panY: 0 }));
+                sel.forEach(d => this.props.removeDocument(d));
+                break;
+
         }
+    }
+
+    @action
+    onPointerUp = (e: PointerEvent): void => {
+        if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) return;
 
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
@@ -473,11 +446,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
         if (!e.cancelBubble) {
             const selectedTool = InkingControl.Instance.selectedTool;
-            if (selectedTool === InkTool.Highlighter || selectedTool === InkTool.Pen || InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
-                const point = this.getTransform().transformPoint(e.clientX, e.clientY);
-                this._points.push({ X: point[0], Y: point[1] });
-            }
-            else if (selectedTool === InkTool.None) {
+            if (selectedTool === InkTool.None) {
                 if (this._hitCluster && this.tryDragCluster(e)) {
                     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();
@@ -508,10 +477,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     }
                     this.pan(pt);
                 }
-                else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
-                    const point = this.getTransform().transformPoint(pt.clientX, pt.clientY);
-                    this._points.push({ X: point[0], Y: point[1] });
-                }
             }
             e.stopPropagation();
             e.preventDefault();
@@ -942,34 +907,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ];
     }
 
-    @computed get svgBounds() {
-        const xs = this._points.map(p => p.X);
-        const ys = this._points.map(p => p.Y);
-        const right = Math.max(...xs);
-        const left = Math.min(...xs);
-        const bottom = Math.max(...ys);
-        const 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);
-        }
-
-        const B = this.svgBounds;
-
-        return (
-            <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, position: "absolute", zIndex: 30000 }}>
-                {CreatePolyline(this._points, B.left, B.top)}
-            </svg>
-        );
-    }
-
     children = () => {
         const eles: JSX.Element[] = [];
         this.extensionDoc && (eles.push(...this.childViews()));
-        this.currentStroke && (eles.push(this.currentStroke));
+        // this.currentStroke && (eles.push(this.currentStroke));
         eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
         return eles;
     }
@@ -998,7 +939,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         if (!this.extensionDoc) return (null);
         // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
-        return <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+        return <div className={"collectionfreeformview-container"} ref={this.createDropAndGestureTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
             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} onTouchStart={this.onTouchStart}>
             {!this.Document.LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a14f69f71..b544319d7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -45,6 +45,7 @@ import { InkTool } from '../../../new_fields/InkField';
 import { TraceMobx } from '../../../new_fields/util';
 import { List } from '../../../new_fields/List';
 import { FormattedTextBoxComment } from './FormattedTextBoxComment';
+import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 
 library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -94,6 +95,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private _hitTemplateDrag = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
+    private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
     private _titleRef = React.createRef<EditableView>();
 
     public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
@@ -107,6 +109,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @action
     componentDidMount() {
         this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
+        this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)));
 
         !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this);
     }
@@ -365,10 +368,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         // console.log(e.button)
         // console.log(e.nativeEvent)
         // continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document)
+        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+            return;
+        }
         if (!InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE)) {
-            if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
-                e.stopPropagation();
-            }
+            e.stopPropagation();
             return;
         }
         if ((!e.nativeEvent.cancelBubble || this.Document.onClick || this.Document.onDragStart)) {
@@ -424,6 +428,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._lastTap = Date.now();
     }
 
+    onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
+        switch (ge.gesture) {
+            case GestureUtils.Gestures.Line:
+                ge.callbackFn && ge.callbackFn(this.props.Document);
+                e.stopPropagation();
+                break;
+        }
+    }
+
     @undoBatch
     deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); }
 
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 59a85b66b..4edcfa623 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -1,19 +1,43 @@
 import { NDollarRecognizer } from "./ndollar";
 import { Type } from "typescript";
-import { InkField } from "../new_fields/InkField";
+import { InkField, PointData } from "../new_fields/InkField";
 import { Docs } from "../client/documents/Documents";
 import { Doc, WidthSym, HeightSym } from "../new_fields/Doc";
 import { NumCast } from "../new_fields/Types";
 import { CollectionFreeFormView } from "../client/views/collections/collectionFreeForm/CollectionFreeFormView";
+import { Rect } from "react-measure";
 
 export namespace GestureUtils {
     namespace GestureDataTypes {
         export type BoxData = Array<Doc>;
     }
 
+    export class GestureEvent {
+        constructor(
+            readonly gesture: Gestures,
+            readonly points: PointData[],
+            readonly bounds: Rect,
+            readonly callbackFn?: Function
+        ) { }
+    }
+
+    export interface GestureEventDisposer { (): void; }
+
+    export function MakeGestureTarget(
+        element: HTMLElement,
+        func: (e: Event, ge: GestureEvent) => void
+    ): GestureEventDisposer {
+        const handler = (e: Event) => func(e, (e as CustomEvent<GestureEvent>).detail);
+        element.addEventListener("dashOnGesture", handler);
+        return () => {
+            element.removeEventListener("dashOnGesture", handler);
+        }
+    }
+
     export enum Gestures {
         Box = "box",
-        Line = "line"
+        Line = "line",
+        Stroke = "stroke"
     }
 
     export const GestureRecognizer = new NDollarRecognizer(false);
-- 
cgit v1.2.3-70-g09d2


From e3466bdb86fdc3096e86f2437a0c8e2000b2252a Mon Sep 17 00:00:00 2001
From: Andy Rickert <andyrickert@Christina-Paxson.local>
Date: Wed, 8 Jan 2020 22:32:09 -0800
Subject: menu appears on click

---
 src/client/views/MainView.tsx            |  2 +
 src/client/views/Touchable.tsx           |  9 ++++-
 src/client/views/globalCssVariables.scss |  2 +
 src/client/views/nodes/DocumentView.tsx  | 69 +++++++++++++++++++++++++++++---
 4 files changed, 75 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a1196ee1c..5bf5dbcc1 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -40,6 +40,7 @@ import InkSelectDecorations from './InkSelectDecorations';
 import { Scripting } from '../util/Scripting';
 import { AudioBox } from './nodes/AudioBox';
 import { TraceMobx } from '../../new_fields/util';
+import { RadialMenu } from './nodes/RadialMenu';
 
 @observer
 export class MainView extends React.Component {
@@ -514,6 +515,7 @@ export class MainView extends React.Component {
             {this.mainContent}
             <PreviewCursor />
             <ContextMenu />
+            <RadialMenu/>
             <PDFMenu />
             <MarqueeOptionsMenu />
             <OverlayView />
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index 251cd41e5..49b2116f1 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -1,11 +1,13 @@
 import * as React from 'react';
 import { action } from 'mobx';
 import { InteractionUtils } from '../util/InteractionUtils';
+import { RadialMenu } from './nodes/RadialMenu';
 
 const HOLD_DURATION = 1000;
 
 export abstract class Touchable<T = {}> extends React.Component<T> {
-    private holdTimer: NodeJS.Timeout | undefined;
+    //private holdTimer: NodeJS.Timeout | undefined;
+    holdTimer: NodeJS.Timeout | undefined;
 
     protected _touchDrag: boolean = false;
     protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
@@ -54,6 +56,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
         this._touchDrag = true;
         if (this.holdTimer) {
+            console.log("CLEAR")
             clearTimeout(this.holdTimer);
         }
         switch (myTouches.length) {
@@ -89,6 +92,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         }
         if (this.holdTimer) {
             clearTimeout(this.holdTimer);
+            console.log("clear");
         }
         this._touchDrag = false;
         e.stopPropagation();
@@ -136,5 +140,8 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         console.log("Hold");
         e.stopPropagation();
         e.preventDefault();
+        document.removeEventListener("touchmove", this.onTouch);
+        document.removeEventListener("touchend", this.onTouchEnd);
     }
+
 }
\ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 6dffee586..019f931f9 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -25,6 +25,8 @@ $search-thumnail-size: 175;
 
 // dragged items
 $contextMenu-zindex: 100000; // context menu shows up over everything
+$radialMenu-zindex: 100000; // context menu shows up over everything
+
 $mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
 $docDecorations-zindex: 998; // then doc decorations appear over everything else
 $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e1b68cdf7..d5442bd48 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -45,6 +45,9 @@ import { InkTool } from '../../../new_fields/InkField';
 import { TraceMobx } from '../../../new_fields/util';
 import { List } from '../../../new_fields/List';
 import { FormattedTextBoxComment } from './FormattedTextBoxComment';
+import { RadialMenu } from './RadialMenu';
+import { RadialMenuProps } from './RadialMenuItem';
+
 
 library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -85,7 +88,6 @@ export interface DocumentViewProps {
     radialMenu?: String[];
 }
 
-
 @observer
 export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
     private _downX: number = 0;
@@ -105,12 +107,65 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get nativeHeight() { return this.layoutDoc.nativeHeight || 0; }
     @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; }
 
-    constructor(props: any) {
-        super(props);
-    }
 
     handle1PointerHoldStart= (e: React.TouchEvent): any =>{
-        console.log("yeet")
+        this.onRadialMenu(e);
+
+        document.removeEventListener("touchmove", this.onTouch);
+        document.removeEventListener("touchend", this.onTouchEnd);
+        document.addEventListener("touchmove", this.handle1PointerHoldMove);
+        document.addEventListener("touchend", this.handle1PointerHoldEnd);
+    }
+
+    handle1PointerHoldMove = (e: TouchEvent): void => {
+        console.log("YUH")
+        document.removeEventListener("touchmove", this.handle1PointerHoldMove);
+        document.addEventListener("touchmove", this.handle1PointerHoldMove);
+        document.removeEventListener("touchend", this.handle1PointerHoldEnd);
+        document.addEventListener("touchend", this.handle1PointerHoldEnd);
+    }
+
+    handle1PointerHoldEnd = (e: TouchEvent): void => {
+        RadialMenu.Instance.closeMenu();
+        document.removeEventListener("touchmove", this.handle1PointerHoldMove);
+        document.removeEventListener("touchend", this.handle1PointerHoldEnd);
+    }
+
+    @action
+    onRadialMenu = async (event: React.TouchEvent): Promise<void> => {
+        console.log("YUH")
+        // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
+        // if (e.button === 0) {
+        //     e.preventDefault();
+        //     return;
+        // }
+        let e = event.touches[0];
+        // if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 ||
+        //     // event.isDefaultPrevented()) {
+        //     // event.preventDefault();
+        //     return;
+        // }
+        // event.preventDefault();
+
+        let rm = RadialMenu.Instance;
+        rm.openMenu();
+
+        rm.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" });
+        runInAction(() => {
+            // cm.addItem({
+            //     description: "Share",
+            //     event: () => SharingManager.Instance.open(this),
+            //     icon: "external-link-alt"
+            // });
+
+            if (!this.topMost) {
+                // DocumentViews should stop propagation of this event
+            }
+            RadialMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+            if (!SelectionManager.IsSelected(this, true)) {
+                SelectionManager.SelectDoc(this, false);
+            }
+        });
     }
     
     @action
@@ -371,6 +426,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
+
         // console.log(e.button)
         // console.log(e.nativeEvent)
         // continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document)
@@ -388,7 +444,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             //     || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InteractionUtils.IsType(e, InteractionUtils.ERASERTYPE)) {
             //     return;
             // }
-
             this._downX = e.clientX;
             this._downY = e.clientY;
             this._hitTemplateDrag = false;
@@ -404,11 +459,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointerup", this.onPointerUp);
+
             if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
         }
     }
 
     onPointerMove = (e: PointerEvent): void => {
+
         if ((e as any).formattedHandled) { e.stopPropagation(); return; }
         if (e.cancelBubble && this.active) {
             document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
-- 
cgit v1.2.3-70-g09d2


From 36eed6cc3a3387a1f7755a848aea4e19e2645e14 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Thu, 9 Jan 2020 19:55:52 -0500
Subject: some stuff that ive played around with today. mobile interface, five
 fingers, other stuff

---
 package-lock.json                                  | 27 +++++++++
 package.json                                       |  1 +
 src/client/views/GestureOverlay.scss               |  2 +-
 src/client/views/GestureOverlay.tsx                | 69 +++++++++++++---------
 src/client/views/InkingControl.tsx                 | 21 ++++---
 src/client/views/MainView.tsx                      |  4 +-
 src/client/views/Palette.scss                      | 18 ++++++
 src/client/views/Palette.tsx                       | 25 ++++++++
 src/client/views/Touchable.tsx                     | 18 ++++++
 .../views/collections/CollectionSchemaView.scss    | 14 +++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 54 +++++++++++++++++
 src/client/views/nodes/DocumentView.tsx            | 17 +-----
 src/mobile/ImageUpload.tsx                         | 31 +++++++---
 src/mobile/MobileInterface.tsx                     | 69 ++++++++++++++++++++++
 src/new_fields/InkField.ts                         |  3 +-
 src/pen-gestures/GestureUtils.ts                   |  3 +-
 .../authentication/models/current_user_utils.ts    | 26 ++++++++
 17 files changed, 335 insertions(+), 67 deletions(-)
 create mode 100644 src/client/views/Palette.scss
 create mode 100644 src/client/views/Palette.tsx
 create mode 100644 src/mobile/MobileInterface.tsx

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

diff --git a/package-lock.json b/package-lock.json
index d807d75a1..244b2e7e0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2040,6 +2040,11 @@
             "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
             "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
         },
+        "avl": {
+            "version": "1.4.4",
+            "resolved": "https://registry.npmjs.org/avl/-/avl-1.4.4.tgz",
+            "integrity": "sha512-vMjF4jhT2N0naO1fsBTA/lZ2AB70ECBv41na7oBe8B5PCgMVvNrWG+VRKMlbG5Mk5UGbMxzUD9fXK/IBsmmO5A=="
+        },
         "awesome-typescript-loader": {
             "version": "5.2.1",
             "resolved": "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-5.2.1.tgz",
@@ -2314,6 +2319,14 @@
                 "tweetnacl": "^0.14.3"
             }
         },
+        "bentley-ottman-sweepline": {
+            "version": "0.1.4",
+            "resolved": "https://registry.npmjs.org/bentley-ottman-sweepline/-/bentley-ottman-sweepline-0.1.4.tgz",
+            "integrity": "sha512-Qptw9uoNLM0zLiJfA+9PqFrF5b6pMiJNyoWgqONq4/wVml4NC2N8S4RXYXn4xXR4JEBhHF+euVwwML7D9X1LqA==",
+            "requires": {
+                "avl": "^1.4.0"
+            }
+        },
         "better-assert": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
@@ -4316,6 +4329,14 @@
                 "minimalistic-crypto-utils": "^1.0.0"
             }
         },
+        "emit-logger": {
+            "version": "github:chocolateboy/emit-logger#b9d25a2d939e42f29c940861e9648bd0fb810070",
+            "from": "github:chocolateboy/emit-logger#better-emitter-name",
+            "requires": {
+                "chalk": "^1.1.1",
+                "moment": "^2.10.6"
+            }
+        },
         "emoji-regex": {
             "version": "7.0.3",
             "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
@@ -7065,6 +7086,7 @@
                 "babel-runtime": "^6.22.0",
                 "bluebird": "^3.4.7",
                 "boolify-string": "^2.0.2",
+                "emit-logger": "github:chocolateboy/emit-logger#better-emitter-name",
                 "lodash": "^4.17.4",
                 "semver": "^5.0.3",
                 "source-map-support": "^0.5.9"
@@ -8545,6 +8567,11 @@
                 }
             }
         },
+        "moment": {
+            "version": "2.24.0",
+            "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
+            "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
+        },
         "mongodb": {
             "version": "3.2.7",
             "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.2.7.tgz",
diff --git a/package.json b/package.json
index 9bd2273f8..2a72fac80 100644
--- a/package.json
+++ b/package.json
@@ -121,6 +121,7 @@
         "async": "^2.6.2",
         "babel-runtime": "^6.26.0",
         "bcrypt-nodejs": "0.0.3",
+        "bentley-ottman-sweepline": "^0.1.4",
         "bluebird": "^3.5.3",
         "body-parser": "^1.18.3",
         "bootstrap": "^4.3.1",
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index cbc1b6e7d..31601efd4 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -1,6 +1,6 @@
 .gestureOverlay-cont {
     width: 100vw;
-    height: 100vw;
+    height: 100vh;
     position: absolute;
     top: 0;
     left: 0;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index c88e3f7ae..848927912 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -43,9 +43,43 @@ export default class GestureOverlay extends Touchable {
 
     @action
     onPointerMove = (e: PointerEvent) => {
-        this._points.push({ X: e.clientX, Y: e.clientY });
-        e.stopPropagation();
-        e.preventDefault();
+        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+            this._points.push({ X: e.clientX, Y: e.clientY });
+            e.stopPropagation();
+            e.preventDefault();
+        }
+    }
+
+    handleLineGesture = (): boolean => {
+        let actionPerformed = false;
+        const B = this.svgBounds;
+        const ep1 = this._points[0];
+        const ep2 = this._points[this._points.length - 1];
+
+        const target1 = document.elementFromPoint(ep1.X, ep1.Y);
+        const target2 = document.elementFromPoint(ep2.X, ep2.Y);
+        const callback = (doc: Doc) => {
+            if (!this._d1) {
+                this._d1 = doc;
+            }
+            else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
+                DocUtils.MakeLink({ doc: this._d1 }, { doc: doc });
+                actionPerformed = true;
+            }
+        }
+        const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
+            {
+                bubbles: true,
+                detail: {
+                    points: this._points,
+                    gesture: GestureUtils.Gestures.Line,
+                    bounds: B,
+                    callbackFn: callback
+                }
+            });
+        target1?.dispatchEvent(ge);
+        target2?.dispatchEvent(ge);
+        return actionPerformed;
     }
 
     @action
@@ -72,31 +106,10 @@ export default class GestureOverlay extends Touchable {
                         actionPerformed = true;
                         break;
                     case GestureUtils.Gestures.Line:
-                        const ep1 = this._points[0];
-                        const ep2 = this._points[this._points.length - 1];
-                        const target1 = document.elementFromPoint(ep1.X, ep1.Y);
-                        const target2 = document.elementFromPoint(ep2.X, ep2.Y);
-                        const callback = (doc: Doc) => {
-                            if (!this._d1) {
-                                this._d1 = doc;
-                            }
-                            else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
-                                DocUtils.MakeLink({ doc: this._d1 }, { doc: doc });
-                                actionPerformed = true;
-                            }
-                        }
-                        const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
-                            {
-                                bubbles: true,
-                                detail: {
-                                    points: this._points,
-                                    gesture: GestureUtils.Gestures.Line,
-                                    bounds: B,
-                                    callbackFn: callback
-                                }
-                            })
-                        target1?.dispatchEvent(ge);
-                        target2?.dispatchEvent(ge);
+                        actionPerformed = this.handleLineGesture();
+                        break;
+                    case GestureUtils.Gestures.Scribble:
+                        console.log("scribble");
                         break;
                 }
                 if (actionPerformed) {
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index e33f193b8..243123352 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -4,19 +4,18 @@ import { Doc } from "../../new_fields/Doc";
 import { InkTool } from "../../new_fields/InkField";
 import { List } from "../../new_fields/List";
 import { listSpec } from "../../new_fields/Schema";
-import { Cast, NumCast, StrCast } from "../../new_fields/Types";
+import { Cast, NumCast, StrCast, FieldValue } from "../../new_fields/Types";
 import { Utils } from "../../Utils";
 import { Scripting } from "../util/Scripting";
 import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 
-
 export class InkingControl {
     @observable static Instance: InkingControl;
-    @observable private _selectedTool: InkTool = InkTool.None;
-    @observable private _selectedColor: string = "rgb(244, 67, 54)";
-    @observable private _selectedWidth: string = "5";
+    @computed private get _selectedTool(): InkTool { return FieldValue(NumCast(CurrentUserUtils.UserDocument.inkTool)) ?? InkTool.None; }
+    @computed private get _selectedColor(): string { return FieldValue(StrCast(CurrentUserUtils.UserDocument.inkColor)) ?? "rgb(244, 67, 54)"; }
+    @computed private get _selectedWidth(): string { return FieldValue(StrCast(CurrentUserUtils.UserDocument.inkWidth)) ?? "5"; }
     @observable public _open: boolean = false;
 
     constructor() {
@@ -24,7 +23,8 @@ export class InkingControl {
     }
 
     switchTool = action((tool: InkTool): void => {
-        this._selectedTool = tool;
+        // this._selectedTool = tool;
+        CurrentUserUtils.UserDocument.inkTool = tool;
     });
     decimalToHexString(number: number) {
         if (number < 0) {
@@ -36,7 +36,7 @@ export class InkingControl {
 
     @undoBatch
     switchColor = action((color: ColorState): void => {
-        this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
+        CurrentUserUtils.UserDocument.inkColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
 
         if (InkingControl.Instance.selectedTool === InkTool.None) {
             const selected = SelectionManager.SelectedDocuments();
@@ -99,7 +99,8 @@ export class InkingControl {
     });
     @action
     switchWidth = (width: string): void => {
-        this._selectedWidth = width;
+        // this._selectedWidth = width;
+        CurrentUserUtils.UserDocument.inkWidth = width;
     }
 
     @computed
@@ -114,7 +115,8 @@ export class InkingControl {
 
     @action
     updateSelectedColor(value: string) {
-        this._selectedColor = value;
+        // this._selectedColor = value;
+        CurrentUserUtils.UserDocument.inkColor = value;
     }
 
     @computed
@@ -127,6 +129,7 @@ Scripting.addGlobal(function activatePen(pen: any, width: any, color: any) { Ink
 Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
 Scripting.addGlobal(function activateEraser(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Eraser : InkTool.None); });
 Scripting.addGlobal(function activateScrubber(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Scrubber : InkTool.None); });
+Scripting.addGlobal(function activateStamp(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Stamp : InkTool.None); });
 Scripting.addGlobal(function deactivateInk() { return InkingControl.Instance.switchTool(InkTool.None); });
 Scripting.addGlobal(function setInkWidth(width: any) { return InkingControl.Instance.switchWidth(width); });
 Scripting.addGlobal(function setInkColor(color: any) { return InkingControl.Instance.updateSelectedColor(color); });
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 14c1803c0..f463ca306 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,7 +1,7 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import {
     faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
-    faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt
+    faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt, faPhone, faStamp
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -135,6 +135,8 @@ export class MainView extends React.Component {
         library.add(faChevronRight);
         library.add(faEllipsisV);
         library.add(faMusic);
+        library.add(faPhone);
+        library.add(faStamp);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/Palette.scss b/src/client/views/Palette.scss
new file mode 100644
index 000000000..60004c81f
--- /dev/null
+++ b/src/client/views/Palette.scss
@@ -0,0 +1,18 @@
+.palette-container {
+    .palette-thumb {
+        width: 300px;
+        height: 300px;
+        touch-action: pan-x;
+        overflow: scroll;
+
+        .palette-thumbContent {
+            width: 100%;
+            height: 100%;
+
+            .palette-button {
+                width: 100px;
+                height: 100px;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
new file mode 100644
index 000000000..390b7e2c2
--- /dev/null
+++ b/src/client/views/Palette.tsx
@@ -0,0 +1,25 @@
+import * as React from "react";
+import "./Palette.scss";
+import { PointData } from "../../new_fields/InkField";
+
+export interface PaletteProps {
+    x: number;
+    y: number;
+    thumb: number[];
+}
+
+export default class Palette extends React.Component<PaletteProps> {
+    render() {
+        return (
+            <div className="palette-container" style={{ transform: `translate(${this.props.x}px, ${this.props.y}px)` }}>
+                <div className="palette-thumb" style={{ transform: `translate(${this.props.thumb[0] - this.props.x}px, ${this.props.thumb[1] - this.props.y}px)` }}>
+                    <div className="palette-thumbContent">
+                        <div className="palette-button" style={{ background: "green" }} onPointerDown={() => console.log("hi")}>1</div>
+                        <div className="palette-button" style={{ background: "red" }}>2</div>
+                        <div className="palette-button" style={{ background: "blue" }}>3</div>
+                    </div>
+                </div>
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index 251cd41e5..3eb66ff72 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -1,6 +1,7 @@
 import * as React from 'react';
 import { action } from 'mobx';
 import { InteractionUtils } from '../util/InteractionUtils';
+import { SelectionManager } from '../util/SelectionManager';
 
 const HOLD_DURATION = 1000;
 
@@ -34,11 +35,19 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
                 case 1:
                     this.handle1PointerDown(e);
                     e.persist();
+                    if (this.holdTimer) {
+                        clearTimeout(this.holdTimer)
+                        this.holdTimer = undefined;
+                    }
                     this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(e), HOLD_DURATION);
+                    console.log(this.holdTimer);
                     break;
                 case 2:
                     this.handle2PointersDown(e);
                     break;
+                case 5:
+                    this.handleHandDown(e);
+                    break;
             }
         }
     }
@@ -54,7 +63,9 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
         this._touchDrag = true;
         if (this.holdTimer) {
+            console.log("clear");
             clearTimeout(this.holdTimer);
+            this.holdTimer = undefined;
         }
         switch (myTouches.length) {
             case 1:
@@ -88,7 +99,9 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
             }
         }
         if (this.holdTimer) {
+            console.log("clear");
             clearTimeout(this.holdTimer);
+            this.holdTimer = undefined;
         }
         this._touchDrag = false;
         e.stopPropagation();
@@ -137,4 +150,9 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         e.stopPropagation();
         e.preventDefault();
     }
+
+    handleHandDown = (e: React.TouchEvent) => {
+        e.stopPropagation();
+        e.preventDefault();
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index cb95dcbbc..8b3d332af 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -15,6 +15,11 @@
     display: flex;
     justify-content: space-between;
     flex-wrap: nowrap;
+    touch-action: none;
+
+    div {
+        touch-action: none;
+    }
 
 
     .collectionSchemaView-tableContainer {
@@ -49,7 +54,7 @@
     .rt-table {
         height: 100%;
         display: -webkit-inline-box;
-        direction: ltr; 
+        direction: ltr;
         overflow: visible;
     }
 
@@ -202,7 +207,7 @@ button.add-column {
         border-bottom: 1px solid lightgray;
 
         &:first-child {
-            padding-top : 0;
+            padding-top: 0;
         }
 
         &:last-child {
@@ -231,7 +236,7 @@ button.add-column {
         transition: background-color 0.2s;
 
         &:hover {
-            background-color: $light-color-secondary; 
+            background-color: $light-color-secondary;
         }
 
         &.active {
@@ -267,7 +272,7 @@ button.add-column {
             overflow-y: scroll;
             position: absolute;
             top: 28px;
-            box-shadow: 0 10px 16px rgba(0,0,0,0.1);
+            box-shadow: 0 10px 16px rgba(0, 0, 0, 0.1);
 
             .key-option {
                 background-color: $light-color;
@@ -380,6 +385,7 @@ button.add-column {
 
     &.editing {
         padding: 0;
+
         input {
             outline: 0;
             border: none;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9656debf3..d7cccc036 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -44,6 +44,7 @@ import { TraceMobx } from "../../../../new_fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
 import { LinkManager } from "../../../util/LinkManager";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import Palette from "../../Palette";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -444,6 +445,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }
             return;
         }
+        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+            return;
+        }
         if (!e.cancelBubble) {
             const selectedTool = InkingControl.Instance.selectedTool;
             if (selectedTool === InkTool.None) {
@@ -844,6 +848,53 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], inkData);
     }
 
+    private thumbIdentifier?: number;
+    private hand?: React.Touch[];
+
+    @action
+    handleHandDown = (e: React.TouchEvent) => {
+        const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+        this.hand = fingers;
+        const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
+        this.thumbIdentifier = thumb?.identifier;
+        const others = fingers.filter(f => f !== thumb);
+        const minX = Math.min(...others.map(f => f.clientX));
+        const minY = Math.min(...others.map(f => f.clientY));
+        const t = this.getTransform().transformPoint(minX, minY);
+        const th = this.getTransform().transformPoint(thumb.clientX, thumb.clientY);
+        this._palette = <Palette x={t[0]} y={t[1]} thumb={th} />;
+
+        document.removeEventListener("touchmove", this.onTouch);
+        document.removeEventListener("touchmove", this.handleHandMove);
+        document.addEventListener("touchmove", this.handleHandMove);
+        document.removeEventListener("touchend", this.handleHandUp);
+        document.addEventListener("touchend", this.handleHandUp);
+    }
+
+    @action
+    handleHandMove = (e: TouchEvent) => {
+        for (let i = 0; i < e.changedTouches.length; i++) {
+            const pt = e.changedTouches.item(i);
+            if (pt?.identifier === this.thumbIdentifier) {
+            }
+        }
+    }
+
+    @action
+    handleHandUp = (e: TouchEvent) => {
+        console.log(e.changedTouches.length);
+        this.onTouchEnd(e);
+        if (this.prevPoints.size < 3) {
+            if (this.hand) {
+                for (const h of this.hand) {
+                    this.prevPoints.has(h.identifier) && this.prevPoints.delete(h.identifier);
+                }
+            }
+            this._palette = undefined;
+            document.removeEventListener("touchend", this.handleHandUp);
+        }
+    }
+
     onContextMenu = (e: React.MouseEvent) => {
         const layoutItems: ContextMenuProps[] = [];
 
@@ -907,9 +958,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ];
     }
 
+    @observable private _palette?: JSX.Element;
+
     children = () => {
         const eles: JSX.Element[] = [];
         this.extensionDoc && (eles.push(...this.childViews()));
+        this._palette && (eles.push(this._palette));
         // this.currentStroke && (eles.push(this.currentStroke));
         eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
         return eles;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7d6b8bf01..415d0e2ef 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -256,7 +256,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             document.removeEventListener("touchend", this.onTouchEnd);
             document.addEventListener("touchend", this.onTouchEnd);
             if ((e.nativeEvent as any).formattedHandled) e.stopPropagation();
-            console.log("down")
         }
     }
 
@@ -327,6 +326,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 const fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight);
                 if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) {
                     layoutDoc.ignoreAspect = false;
+
                     layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
                     layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
                 }
@@ -357,8 +357,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false);
                 }
             }
-            // let newWidth = Math.max(Math.abs(oldPoint1!.clientX - oldPoint2!.clientX), Math.abs(pt1.clientX - pt2.clientX))
-            // this.props.Document.width = newWidth;
             e.stopPropagation();
             e.preventDefault();
         }
@@ -374,19 +372,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             return;
         }
-        // if (!InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE)) {
-        //     e.stopPropagation();
-        //     return;
-        // }
         if (!e.nativeEvent.cancelBubble || this.Document.onClick || this.Document.onDragStart) {
-            // if ((e.nativeEvent.cancelBubble && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)))
-            //     // return if we're inking, and not selecting a button document
-            //     || (InkingControl.Instance.selectedTool !== InkTool.None && !this.Document.onClick)
-            //     // return if using pen or eraser
-            //     || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InteractionUtils.IsType(e, InteractionUtils.ERASERTYPE)) {
-            //     return;
-            // }
-
             this._downX = e.clientX;
             this._downY = e.clientY;
             this._hitTemplateDrag = false;
@@ -408,6 +394,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     onPointerMove = (e: PointerEvent): void => {
         if ((e as any).formattedHandled) { e.stopPropagation(); return; }
+        if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) return;
         if (e.cancelBubble && this.active) {
             document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
         }
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index ec698b151..0b0280519 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -11,6 +11,8 @@ import { List } from '../new_fields/List';
 import { observer } from 'mobx-react';
 import { observable } from 'mobx';
 import { Utils } from '../Utils';
+import MobileInterface from './MobileInterface';
+import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
 
 
 
@@ -104,10 +106,25 @@ class Uploader extends React.Component {
 }
 
 
-DocServer.init(window.location.protocol, window.location.hostname, 4321, "image upload");
-
-ReactDOM.render((
-    <Uploader />
-),
-    document.getElementById('root')
-);
\ No newline at end of file
+// DocServer.init(window.location.protocol, window.location.hostname, 4321, "image upload");
+(async () => {
+    const info = await CurrentUserUtils.loadCurrentUser();
+    DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + "mobile");
+    await Docs.Prototypes.initialize();
+    if (info.id !== "__guest__") {
+        // a guest will not have an id registered
+        await CurrentUserUtils.loadUserDocument(info);
+    }
+    document.getElementById('root')!.addEventListener('wheel', event => {
+        if (event.ctrlKey) {
+            event.preventDefault();
+        }
+    }, true);
+    ReactDOM.render((
+        // <Uploader />
+        <MobileInterface />
+    ),
+        document.getElementById('root')
+    );
+}
+)();
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
new file mode 100644
index 000000000..ea2fc917f
--- /dev/null
+++ b/src/mobile/MobileInterface.tsx
@@ -0,0 +1,69 @@
+import React = require('react');
+import { observer } from 'mobx-react';
+import { computed, action } from 'mobx';
+import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
+import { FieldValue, Cast } from '../new_fields/Types';
+import { Doc } from '../new_fields/Doc';
+import { Docs } from '../client/documents/Documents';
+import { CollectionView } from '../client/views/collections/CollectionView';
+import { DocumentView } from '../client/views/nodes/DocumentView';
+import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils';
+import { Transform } from '../client/util/Transform';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faPenNib, faHighlighter, faEraser, faMousePointer } from '@fortawesome/free-solid-svg-icons';
+
+@observer
+export default class MobileInterface extends React.Component {
+    @computed private get userDoc() { return CurrentUserUtils.UserDocument; }
+    @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
+
+    @action
+    componentDidMount = () => {
+        library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]);
+
+        if (this.userDoc && !this.mainContainer) {
+            const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+            this.userDoc.activeMobile = doc;
+        }
+    }
+
+    @computed
+    get mainContent() {
+        if (this.mainContainer) {
+            return <DocumentView
+                Document={this.mainContainer}
+                DataDoc={undefined}
+                LibraryPath={emptyPath}
+                addDocument={undefined}
+                addDocTab={returnFalse}
+                pinToPres={emptyFunction}
+                removeDocument={undefined}
+                ruleProvider={undefined}
+                onClick={undefined}
+                ScreenToLocalTransform={Transform.Identity}
+                ContentScaling={returnOne}
+                PanelWidth={() => window.screen.width}
+                PanelHeight={() => window.screen.height}
+                renderDepth={0}
+                focus={emptyFunction}
+                backgroundColor={returnEmptyString}
+                parentActive={returnTrue}
+                whenActiveChanged={emptyFunction}
+                bringToFront={emptyFunction}
+                ContainingCollectionView={undefined}
+                ContainingCollectionDoc={undefined}
+                zoomToScale={emptyFunction}
+                getScale={returnOne}>
+            </DocumentView>;
+        }
+        return "hello";
+    }
+
+    render() {
+        return (
+            <div className="mobile-container">
+                {this.mainContent}
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 83d631958..20dbf4b17 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -9,7 +9,8 @@ export enum InkTool {
     Pen,
     Highlighter,
     Eraser,
-    Scrubber
+    Scrubber,
+    Stamp
 }
 
 export interface PointData {
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 4edcfa623..062604458 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -37,7 +37,8 @@ export namespace GestureUtils {
     export enum Gestures {
         Box = "box",
         Line = "line",
-        Stroke = "stroke"
+        Stroke = "stroke",
+        Scribble = "scribble"
     }
 
     export const GestureRecognizer = new NDollarRecognizer(false);
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 220c37e2b..71dd34e68 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -28,6 +28,7 @@ export class CurrentUserUtils {
 
     @observable public static GuestTarget: Doc | undefined;
     @observable public static GuestWorkspace: Doc | undefined;
+    @observable public static GuestMobile: Doc | undefined;
 
     // a default set of note types .. not being used yet...
     static setupNoteTypes(doc: Doc) {
@@ -55,8 +56,10 @@ export class CurrentUserUtils {
             { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' },
             { title: "presentation", icon: "tv", ignoreClick: true, drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })' },
             { title: "import folder", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' },
+            { title: "mobile view", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' },
             { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen,  this)`, activePen: doc },
             { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
+            { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
             { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc },
             { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc },
             { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc },
@@ -90,6 +93,29 @@ export class CurrentUserUtils {
         }
     }
 
+    static setupMobileButtons(doc: Doc, buttons?: string[]) {
+        const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
+            { title: "record", icon: "microphone", ignoreClick: true, click: "FILL" },
+            { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen,  this)`, activePen: doc },
+            { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
+            { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc },
+            { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc },
+            { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc },
+        ];
+        return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({
+            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
+            onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
+            ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen,
+            backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory,
+        }));
+    }
+
+    static setupMobileDoc(userDoc: Doc) {
+        return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), {
+            columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5
+        });
+    }
+
     // setup the Creator button which will display the creator panel.  This panel will include the drag creators and the color picker.  when clicked, this panel will be displayed in the target container (ie, sidebarContainer)  
     static setupToolsPanel(sidebarContainer: Doc, doc: Doc) {
         // setup a masonry view of all he creators
-- 
cgit v1.2.3-70-g09d2


From acf72eb45bdcdda5a87a7017f698793d2feea1c4 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Thu, 16 Jan 2020 11:46:12 -0500
Subject: extra fixes after pull from master

---
 src/client/views/MainView.tsx         | 3 ---
 src/server/ApiManagers/UserManager.ts | 2 +-
 2 files changed, 1 insertion(+), 4 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 05bfee95b..b300b0471 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -39,12 +39,9 @@ import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsM
 import InkSelectDecorations from './InkSelectDecorations';
 import { Scripting } from '../util/Scripting';
 import { AudioBox } from './nodes/AudioBox';
-<<<<<<< HEAD
 import SettingsManager from '../util/SettingsManager';
-=======
 import { TraceMobx } from '../../new_fields/util';
 import RichTextMenu from '../util/RichTextMenu';
->>>>>>> e410cde0e430553002d4e1a2f64364b57b65fdbc
 
 @observer
 export class MainView extends React.Component {
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index 36d48e366..b0d868918 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -41,7 +41,7 @@ export default class UserManager extends ApiManager {
         register({
             method: Method.POST,
             subscription: '/internalResetPassword',
-            onValidation: async ({ user, req, res }) => {
+            secureHandler: async ({ user, req, res }) => {
                 const result: any = {};
                 const { curr_pass, new_pass, new_confirm } = req.body;
                 // perhaps should assert whether curr password is entered correctly
-- 
cgit v1.2.3-70-g09d2


From 4b0a056f1afaf20dea4be64c7b238748d99ad12e Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Fri, 17 Jan 2020 15:36:22 -0500
Subject: palette now doesn't interfere with touch

---
 src/client/documents/Documents.ts                  |  1 +
 src/client/util/InteractionUtils.ts                |  2 +-
 src/client/views/GestureOverlay.tsx                | 82 +++++++++++++++++-----
 src/client/views/MainView.tsx                      |  5 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../authentication/models/current_user_utils.ts    |  1 +
 6 files changed, 70 insertions(+), 23 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 64abd4f57..c49642de0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -119,6 +119,7 @@ export interface DocumentOptions {
     limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents
     // [key: string]: Opt<Field>;
     pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown
+    isExpanded?: boolean; // is linear view expanded
 }
 
 class EmptyBox {
diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts
index da42bdc93..3e6d27242 100644
--- a/src/client/util/InteractionUtils.ts
+++ b/src/client/util/InteractionUtils.ts
@@ -49,7 +49,7 @@ export namespace InteractionUtils {
     export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent<React.TouchEvent | TouchEvent>, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] {
         const myTouches = new Array<React.Touch>();
         for (const pt of mte.touches) {
-            if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) {
+            if (!ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) {
                 for (const tPt of mte.targetTouches) {
                     if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
                         if (pt && prevPoints.has(pt.identifier)) {
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 96c1513ff..854dff0e2 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -16,7 +16,8 @@ import { Scripting } from "../util/Scripting";
 import { FieldValue, Cast, NumCast } from "../../new_fields/Types";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 import Palette from "./Palette";
-import { Utils } from "../../Utils";
+import { Utils, emptyPath, emptyFunction } from "../../Utils";
+import { DocumentView } from "./nodes/DocumentView";
 
 @observer
 export default class GestureOverlay extends Touchable {
@@ -24,15 +25,15 @@ export default class GestureOverlay extends Touchable {
 
     @observable private _points: { X: number, Y: number }[] = [];
     @observable private _palette?: JSX.Element;
+    @observable private _elements: JSX.Element[];
     @observable public Color: string = "rgb(244, 67, 54)";
     @observable public Width: number = 5;
 
     private _d1: Doc | undefined;
     private _thumbDoc: Doc | undefined;
     private _thumbX?: number;
-    private _thumbY?: number;
     private thumbIdentifier?: number;
-    private _hands: (React.Touch[])[] = [];
+    private _hands: Map<number, React.Touch[]> = new Map<number, React.Touch[]>();
 
     protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
@@ -49,22 +50,22 @@ export default class GestureOverlay extends Touchable {
         this._hands.forEach((hand) => {
             for (let i = 0; i < e.targetTouches.length; i++) {
                 const pt = e.targetTouches.item(i);
-                if (pt && hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) {
-                    ntt.splice(ntt.indexOf(pt));
+                if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
+                    ntt.splice(ntt.indexOf(pt), 1);
                 }
             }
 
             for (let i = 0; i < e.changedTouches.length; i++) {
                 const pt = e.changedTouches.item(i);
-                if (pt && hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) {
-                    nct.splice(nct.indexOf(pt));
+                if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
+                    nct.splice(nct.indexOf(pt), 1);
                 }
             }
 
             for (let i = 0; i < e.touches.length; i++) {
                 const pt = e.touches.item(i);
-                if (pt && hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) {
-                    nt.splice(ntt.indexOf(pt));
+                if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
+                    nt.splice(nt.indexOf(pt), 1);
                 }
             }
         });
@@ -94,9 +95,10 @@ export default class GestureOverlay extends Touchable {
         });
 
         ptsToDelete.forEach(pt => this.prevPoints.delete(pt));
+        const nts = this.getNewTouches(te);
+        console.log(nts.nt.length);
 
-        if (this.prevPoints.size && this.prevPoints.size < 5) {
-            const nts = this.getNewTouches(te);
+        if (nts.nt.length < 5) {
             const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY);
             target?.dispatchEvent(
                 new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>("dashOnTouchStart",
@@ -190,12 +192,12 @@ export default class GestureOverlay extends Touchable {
         const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
         if (thumb.identifier === this.thumbIdentifier) {
             this._thumbX = thumb.clientX;
-            this._thumbY = thumb.clientY;
+            this._hands.set(thumb.identifier, fingers);
             return;
         }
         this.thumbIdentifier = thumb?.identifier;
-        fingers.forEach((f) => this.prevPoints.delete(f.identifier));
-        this._hands.push(fingers);
+        // fingers.forEach((f) => this.prevPoints.delete(f.identifier));
+        this._hands.set(thumb.identifier, fingers);
         const others = fingers.filter(f => f !== thumb);
         const minX = Math.min(...others.map(f => f.clientX));
         const minY = Math.min(...others.map(f => f.clientY));
@@ -207,7 +209,6 @@ export default class GestureOverlay extends Touchable {
             runInAction(() => {
                 this._thumbDoc = thumbDoc;
                 this._thumbX = thumb.clientX;
-                this._thumbY = thumb.clientY;
                 this._palette = <Palette x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />;
             });
         }
@@ -221,6 +222,29 @@ export default class GestureOverlay extends Touchable {
 
     @action
     handleHandMove = (e: TouchEvent) => {
+        const fingers = new Array<React.Touch>();
+        for (let i = 0; i < e.touches.length; i++) {
+            const pt: any = e.touches.item(i);
+            if (pt.radiusX > 1 && pt.radiusY > 1) {
+                for (let j = 0; j < e.targetTouches.length; j++) {
+                    const tPt = e.targetTouches.item(j);
+                    if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
+                        if (pt && this.prevPoints.has(pt.identifier)) {
+                            this._hands.forEach(hand => hand.some(f => {
+                                if (f.identifier === pt.identifier) {
+                                    fingers.push(pt);
+                                }
+                            }));
+                        }
+                    }
+                }
+            }
+        }
+        const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
+        if (thumb?.identifier === this.thumbIdentifier) {
+            this._hands.set(thumb.identifier, fingers);
+        }
+
         for (let i = 0; i < e.changedTouches.length; i++) {
             const pt = e.changedTouches.item(i);
             if (pt && pt.identifier === this.thumbIdentifier && this._thumbX && this._thumbDoc) {
@@ -236,6 +260,7 @@ export default class GestureOverlay extends Touchable {
     handleHandUp = (e: TouchEvent) => {
         if (e.touches.length < 3) {
             // this.onTouchEnd(e);
+            if (this.thumbIdentifier) this._hands.delete(this.thumbIdentifier);
             this._palette = undefined;
             this.thumbIdentifier = undefined;
             this._thumbDoc = undefined;
@@ -380,16 +405,35 @@ export default class GestureOverlay extends Touchable {
         );
     }
 
+    @computed get elements() {
+        return [
+            this.props.children,
+            this._elements,
+            this._palette,
+            this.currentStroke
+        ]
+    }
+
+    @action
+    openFloatingDoc = (doc: Doc) => {
+        // this._elements.push(
+        //     <DocumentView 
+        //     Document={doc}
+        //     LibraryPath={emptyPath}
+        //     addDocument={undefined}
+        //     addDocTab={emptyFunction}
+        //     />
+        // )
+    }
+
     render() {
         return (
             <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
-                {this.props.children}
-                {this._palette}
-                {this.currentStroke}
+                {this.elements}
             </div>);
     }
 }
 
 Scripting.addGlobal("GestureOverlay", GestureOverlay);
 Scripting.addGlobal(function setPen(width: any, color: any) { runInAction(() => { GestureOverlay.Instance.Color = color; GestureOverlay.Instance.Width = width; }); });
-Scripting.addGlobal(function resetPen() { runInAction(() => { GestureOverlay.Instance.Color = "rgb(244, 67, 54)"; GestureOverlay.Instance.Width = 5; }); });
\ No newline at end of file
+Scripting.addGlobal(function resetPen() { runInAction(() => { runInAction(() => { GestureOverlay.Instance.Color = "rgb(244, 67, 54)"; GestureOverlay.Instance.Width = 5; })); });
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 93fb0a07c..b8b956270 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,7 +1,7 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import {
     faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
-    faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt, faPhone, faStamp
+    faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt, faPhone, faStamp, faClipboard
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -137,6 +137,7 @@ export class MainView extends React.Component {
         library.add(faEllipsisV);
         library.add(faMusic);
         library.add(faPhone);
+        library.add(faClipboard);
         library.add(faStamp);
         this.initEventListeners();
         this.initAuthenticationRouters();
@@ -518,7 +519,7 @@ export class MainView extends React.Component {
             </GestureOverlay>
             <PreviewCursor />
             <ContextMenu />
-            <RadialMenu/>
+            <RadialMenu />
             <PDFMenu />
             <MarqueeOptionsMenu />
             <OverlayView />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index aaa585b55..b302bb1dc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -327,7 +327,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
         if (!e.nativeEvent.cancelBubble) {
-            // const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
+            // const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
             const pt = me.changedTouches[0];
             if (pt) {
                 this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false;
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 8b8b1e029..eb48b28f2 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -114,6 +114,7 @@ export class CurrentUserUtils {
         const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
             { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen,  this)`, activePen: doc },
             { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
+            // { title: "notepad", icon: "clipboard", pointerUp: "closeFloatingDoc(this.clipboard)", pointerDown: 'openFloatingDoc(this.clipboard)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
         ];
         return docProtoData.map(data => Docs.Create.FontIconDocument({
             nativeWidth: 10, nativeHeight: 10, width: 10, height: 10, dropAction: data.pointerDown ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
-- 
cgit v1.2.3-70-g09d2


From dabb4a9c66083b88eba7bfb07a2614634e124b10 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Mon, 20 Jan 2020 16:33:49 -0500
Subject: resizable palette

---
 src/client/documents/Documents.ts                  |  4 +-
 src/client/views/GestureOverlay.scss               |  6 ++
 src/client/views/GestureOverlay.tsx                | 68 +++++++++++++++-------
 src/client/views/MainView.tsx                      |  2 +-
 .../views/collections/CollectionLinearView.tsx     |  2 +-
 src/client/views/nodes/ButtonBox.tsx               |  5 +-
 src/new_fields/documentSchemas.ts                  |  2 +
 .../authentication/models/current_user_utils.ts    | 16 +++--
 8 files changed, 75 insertions(+), 30 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 7e7b10ae8..821185518 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -80,7 +80,7 @@ export interface DocumentOptions {
     isTemplateDoc?: boolean;
     templates?: List<string>;
     viewType?: number;
-    backgroundColor?: string;
+    backgroundColor?: string | ScriptField;
     ignoreClick?: boolean;
     lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged
     lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed
@@ -127,6 +127,8 @@ export interface DocumentOptions {
     // [key: string]: Opt<Field>;
     pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown
     isExpanded?: boolean; // is linear view expanded
+    textTransform?: string; // is linear view expanded
+    letterSpacing?: string; // is linear view expanded
 }
 
 class EmptyBox {
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index f9a52d976..d980b0a91 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -11,4 +11,10 @@
     position: absolute;
     width: 300px;
     height: 300px;
+}
+
+.filter-cont {
+    position: absolute;
+    background-color: transparent;
+    border: 1px solid black;
 }
\ No newline at end of file
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 7fb8e7797..e44db4463 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -24,19 +24,25 @@ import { DocumentContentsView } from "./nodes/DocumentContentsView";
 export default class GestureOverlay extends Touchable {
     static Instance: GestureOverlay;
 
-    @observable private _points: { X: number, Y: number }[] = [];
-    @observable private _palette?: JSX.Element;
-    @observable private _clipboardDoc?: JSX.Element;
     @observable public Color: string = "rgb(244, 67, 54)";
     @observable public Width: number = 5;
     @observable public SavedColor?: string;
     @observable public SavedWidth?: number;
 
-    private _d1: Doc | undefined;
-    private _thumbDoc: Doc | undefined;
     @observable private _thumbX?: number;
     @observable private _thumbY?: number;
+    @observable private _pointerY?: number;
+    @observable private _points: { X: number, Y: number }[] = [];
+    @observable private _palette?: JSX.Element;
+    @observable private _clipboardDoc?: JSX.Element;
+    @observable private _showBounds: boolean = false;
+
+    @computed private get height(): number { return Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); }
+
+    private _d1: Doc | undefined;
+    private _thumbDoc: Doc | undefined;
     private thumbIdentifier?: number;
+    private pointerIdentifier?: number;
     private _hands: Map<number, React.Touch[]> = new Map<number, React.Touch[]>();
 
     protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
@@ -178,7 +184,7 @@ export default class GestureOverlay extends Touchable {
         e.stopPropagation();
     }
 
-    handleHandDown = (e: React.TouchEvent) => {
+    handleHandDown = async (e: React.TouchEvent) => {
         const fingers = new Array<React.Touch>();
         for (let i = 0; i < e.touches.length; i++) {
             const pt: any = e.touches.item(i);
@@ -194,6 +200,22 @@ export default class GestureOverlay extends Touchable {
             }
         }
         const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
+        const rightMost = Math.max(...fingers.map(f => f.clientX));
+        const leftMost = Math.min(...fingers.map(f => f.clientX));
+        let pointer: React.Touch | undefined;
+        // left hand
+        if (thumb.clientX === rightMost) {
+            pointer = fingers.reduce((a, v) => a.clientX > v.clientX || v.identifier === thumb.identifier ? a : v);
+        }
+        // right hand
+        else if (thumb.clientX === leftMost) {
+            pointer = fingers.reduce((a, v) => a.clientX < v.clientX || v.identifier === thumb.identifier ? a : v);
+        }
+        else {
+            console.log("not hand");
+        }
+        this.pointerIdentifier = pointer?.identifier;
+        runInAction(() => this._pointerY = pointer?.clientY);
         if (thumb.identifier === this.thumbIdentifier) {
             this._thumbX = thumb.clientX;
             this._thumbY = thumb.clientY;
@@ -201,15 +223,12 @@ export default class GestureOverlay extends Touchable {
             return;
         }
         this.thumbIdentifier = thumb?.identifier;
-        // fingers.forEach((f) => this.prevPoints.delete(f.identifier));
         this._hands.set(thumb.identifier, fingers);
         const others = fingers.filter(f => f !== thumb);
         const minX = Math.min(...others.map(f => f.clientX));
         const minY = Math.min(...others.map(f => f.clientY));
-        // const t = this.getTransform().transformPoint(minX, minY);
-        // const th = this.getTransform().transformPoint(thumb.clientX, thumb.clientY);
 
-        const thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc));
+        const thumbDoc = await Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc);
         if (thumbDoc) {
             runInAction(() => {
                 this._thumbDoc = thumbDoc;
@@ -247,7 +266,7 @@ export default class GestureOverlay extends Touchable {
             }
         }
         const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
-        if (thumb?.identifier === this.thumbIdentifier) {
+        if (thumb?.identifier && thumb?.identifier === this.thumbIdentifier) {
             this._hands.set(thumb.identifier, fingers);
         }
 
@@ -259,6 +278,9 @@ export default class GestureOverlay extends Touchable {
                     this._thumbX = pt.clientX;
                 }
             }
+            if (pt && pt.identifier === this.pointerIdentifier) {
+                this._pointerY = pt.clientY;
+            }
         }
     }
 
@@ -329,8 +351,6 @@ export default class GestureOverlay extends Touchable {
         return actionPerformed;
     }
 
-
-
     @action
     onPointerUp = (e: PointerEvent) => {
         if (this._points.length > 1) {
@@ -414,7 +434,6 @@ export default class GestureOverlay extends Touchable {
     @computed get elements() {
         return [
             this.props.children,
-            // this._clipboardDoc,
             this._palette,
             this.currentStroke
         ];
@@ -422,8 +441,6 @@ export default class GestureOverlay extends Touchable {
 
     @action
     public openFloatingDoc = (doc: Doc) => {
-        // const t = new Transform(-(this._clipboardDoc ? (this._thumbX ?? 0) : -350), -(this._thumbY ?? 0) + 350, 1);
-        // let t =
         this._clipboardDoc =
             <DocumentView
                 Document={doc}
@@ -435,7 +452,7 @@ export default class GestureOverlay extends Touchable {
                 onClick={undefined}
                 ruleProvider={undefined}
                 removeDocument={undefined}
-                ScreenToLocalTransform={() => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + 350, 1)}
+                ScreenToLocalTransform={() => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1)}
                 ContentScaling={returnOne}
                 PanelWidth={() => 300}
                 PanelHeight={() => 300}
@@ -462,13 +479,24 @@ export default class GestureOverlay extends Touchable {
             <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
                 {this.elements}
                 <div className="clipboardDoc-cont" style={{
-                    transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - 350}px)`,
+                    transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
+                    height: this.height,
+                    width: this.height,
                     pointerEvents: this._clipboardDoc ? "unset" : "none",
-                    touchAction: this._clipboardDoc ? "unset" : "none"
+                    touchAction: this._clipboardDoc ? "unset" : "none",
                 }}>
                     {this._clipboardDoc}
                 </div>
-            </div>);
+                <div className="filter-cont" style={{
+                    transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
+                    height: this.height,
+                    width: this.height,
+                    pointerEvents: this._showBounds ? "unset" : "none",
+                    touchAction: this._showBounds ? "unset" : "none",
+                    display: this._showBounds ? "unset" : "none",
+                }}>
+                </div>
+            </div >);
     }
 }
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 168d2ea18..14ee0dc53 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -46,7 +46,7 @@ import RichTextMenu from '../util/RichTextMenu';
 @observer
 export class MainView extends React.Component {
     public static Instance: MainView;
-    private _buttonBarHeight = 75;
+    private _buttonBarHeight = 35;
     private _flyoutSizeOnDown = 0;
     private _urlState: HistoryUtil.DocUrl;
     private _docBtnRef = React.createRef<HTMLDivElement>();
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 8b0638aa1..77af2dc0e 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -48,7 +48,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
             (i) => runInAction(() => {
                 this._selectedIndex = i;
                 let selected: any = undefined;
-                this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => {
+                this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map(async (pair, ind) => {
                     const isSelected = this._selectedIndex === ind;
                     if (isSelected) {
                         selected = pair;
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index d1272c266..d29fe1711 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -80,7 +80,10 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
         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: this.Document.backgroundColor || "", color: this.Document.color || "black", fontSize: this.Document.fontSize }} >
+                <div className="buttonBox-mainButton" style={{
+                    background: this.Document.backgroundColor, color: this.Document.color || "black",
+                    fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "", textTransform: this.Document.textTransform || ""
+                }} >
                     <div className="buttonBox-mainButtonCenter">
                         {(this.Document.text || this.Document.title)}
                     </div>
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 24f6224eb..d5c34e128 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -57,6 +57,8 @@ export const documentSchema = createSchema({
     yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     LODarea: "number",          // area (width*height) where CollectionFreeFormViews switch from a label to rendering contents
     LODdisable: "boolean",      // whether to disbale LOD switching for CollectionFreeFormViews
+    letterSpacing: "string",
+    textTransform: "string"
 });
 
 export const positionSchema = createSchema({
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index adeee452c..3e6399d82 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -129,7 +129,7 @@ export class CurrentUserUtils {
     static setupThumbDoc(userDoc: Doc) {
         if (!userDoc.thumbDoc) {
             userDoc.thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), {
-                width: 100, height: 50, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5, isExpanded: true
+                width: 100, height: 50, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5, isExpanded: true, backgroundColor: "white"
             });
         }
         return userDoc.thumbDoc;
@@ -154,7 +154,8 @@ export class CurrentUserUtils {
         });
 
         return Docs.Create.ButtonDocument({
-            width: 35, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Tools", fontSize: 10, targetContainer: sidebarContainer,
+            width: 35, height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Tools", fontSize: 10, targetContainer: sidebarContainer,
+            letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
             sourcePanel: Docs.Create.StackingDocument([dragCreators, color], {
                 width: 500, height: 800, lockedPosition: true, chromeStatus: "disabled", title: "tools stack"
             }),
@@ -179,9 +180,10 @@ export class CurrentUserUtils {
         });
 
         return Docs.Create.ButtonDocument({
-            width: 50, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Library", fontSize: 10,
+            width: 50, height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Library", fontSize: 10,
+            letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
             sourcePanel: 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
+                title: "Library", xMargin: 5, yMargin: 5, gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true, boxShadow: "0 0",
             }),
             targetContainer: sidebarContainer,
             onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
@@ -191,7 +193,8 @@ export class CurrentUserUtils {
     // setup the Search button which will display the search panel.  
     static setupSearchPanel(sidebarContainer: Doc) {
         return Docs.Create.ButtonDocument({
-            width: 50, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Search", fontSize: 10,
+            width: 50, height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Search", fontSize: 10,
+            letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
             sourcePanel: Docs.Create.QueryDocument({
                 title: "search stack", ignoreClick: true
             }),
@@ -214,7 +217,8 @@ export class CurrentUserUtils {
         // Finally, setup the list of buttons to display in the sidebar
         doc.sidebarButtons = Docs.Create.StackingDocument([doc.SearchBtn as Doc, doc.LibraryBtn as Doc, doc.ToolsBtn as Doc], {
             width: 500, height: 80, boxShadow: "0 0", sectionFilter: "title", hideHeadings: true, ignoreClick: true,
-            backgroundColor: "lightgrey", chromeStatus: "disabled", title: "library stack"
+            backgroundColor: "rgb(100, 100, 100)", chromeStatus: "disabled", title: "library stack",
+            yMargin: 10,
         });
     }
 
-- 
cgit v1.2.3-70-g09d2


From 613c2c8de636927fb38a7a32ec6b0427b238db2b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 21 Jan 2020 21:33:42 -0500
Subject: added filter resizer for pivot viewer.  fixed click/drag confusion
 with mainview divider.

---
 src/client/views/MainView.tsx                      |  5 +-
 .../views/collections/CollectionPivotView.scss     | 13 +++-
 .../views/collections/CollectionPivotView.tsx      | 90 +++++++++++++---------
 3 files changed, 70 insertions(+), 38 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d7656aba4..0958c434a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -307,8 +307,10 @@ export class MainView extends React.Component {
         </Measure>;
     }
 
+    _canClick = false;
     onPointerDown = (e: React.PointerEvent) => {
         if (this._flyoutTranslate) {
+            this._canClick = true;
             this._flyoutSizeOnDown = e.clientX;
             document.removeEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
@@ -339,11 +341,12 @@ export class MainView extends React.Component {
     @action
     onPointerMove = (e: PointerEvent) => {
         this.flyoutWidth = Math.max(e.clientX, 0);
+        Math.abs(this.flyoutWidth - this._flyoutSizeOnDown) > 6 && (this._canClick = false);
         this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30;
     }
     @action
     onPointerUp = (e: PointerEvent) => {
-        if (Math.abs(e.clientX - this._flyoutSizeOnDown) < 4) {
+        if (Math.abs(e.clientX - this._flyoutSizeOnDown) < 4 && this._canClick) {
             this.flyoutWidth = this.flyoutWidth < 15 ? 250 : 0;
             this.flyoutWidth && (this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30);
         }
diff --git a/src/client/views/collections/CollectionPivotView.scss b/src/client/views/collections/CollectionPivotView.scss
index bd3d6c77b..2ddcc97ea 100644
--- a/src/client/views/collections/CollectionPivotView.scss
+++ b/src/client/views/collections/CollectionPivotView.scss
@@ -45,7 +45,7 @@
         }
         .collectionPivotView-tree {
             display:inline-block;
-            width: 200px;
+            width: 100%;
             height: calc(100% - 30px);
         }
     }
@@ -54,4 +54,15 @@
         width: calc(100% - 200px);
         height: 100%;
     }
+    .collectionPivotView-dragger {
+        background-color: lightgray;
+        height: 40px;
+        width: 20px;
+        position: absolute;
+        border-radius: 10px;
+        top: 55%;
+        border: 1px black solid;
+        z-index: 2;
+        left:-10px;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
index f31f1aba6..8e712e370 100644
--- a/src/client/views/collections/CollectionPivotView.tsx
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -65,7 +65,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
         return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
     }
 
-    bodyPanelWidth = () => this.props.PanelWidth() - 200;
+    bodyPanelWidth = () => this.props.PanelWidth() - this._facetWidth;
     getTransform = () => this.props.ScreenToLocalTransform().translate(-200, 0);
 
     @computed get _allFacets() {
@@ -78,39 +78,53 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
      * Responds to clicking the check box in the flyout menu
      */
     facetClick = (facetHeader: string) => {
-        const { Document, fieldKey } = this.props;
-        const facetCollection = Document.facetCollection;
+        const facetCollection = this.props.Document.facetCollection;
         if (facetCollection instanceof Doc) {
             const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader);
             if (found !== -1) {
-                //Doc.RemoveDocFromList(facetCollection, "data", DocListCast(facetCollection.data)[found]);
                 (facetCollection.data as List<Doc>).splice(found, 1);
             } else {
                 const newFacet = Docs.Create.FreeformDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
+                const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
+                const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
+                newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
                 Doc.AddDocToList(facetCollection, "data", newFacet);
-                const { dataDoc } = this;
-                const capturedVariables = {
-                    layoutDoc: Document,
-                    dataDoc,
-                    dataKey: fieldKey,
-                    facetHeader
-                };
-                const params = {
-                    layoutDoc: Doc.name,
-                    dataDoc: Doc.name,
-                    dataKey: "string",
-                    facetHeader: "string"
-                };
-                newFacet.container = dataDoc;
-                newFacet.data = ComputedField.MakeFunction("readFacetData(layoutDoc, dataDoc, dataKey, facetHeader)", params, capturedVariables);
             }
         }
     }
+    _canClick = false;
+    _facetWidthOnDown = 0;
+    @observable _facetWidth = 200;
+    onPointerDown = (e: React.PointerEvent) => {
+        this._canClick = true;
+        this._facetWidthOnDown = e.screenX;
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        document.addEventListener("pointermove", this.onPointerMove);
+        document.addEventListener("pointerup", this.onPointerUp);
+        e.stopPropagation();
+        e.preventDefault();
+    }
+
+
+    @action
+    onPointerMove = (e: PointerEvent) => {
+        this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
+        Math.abs(e.movementX) > 6 && (this._canClick = false);
+    }
+    @action
+    onPointerUp = (e: PointerEvent) => {
+        if (Math.abs(e.screenX - this._facetWidthOnDown) < 6 && this._canClick) {
+            this._facetWidth = this._facetWidth < 15 ? 200 : 0;
+        }
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+    }
 
     render() {
         const facetCollection = Cast(this.props.Document?.facetCollection, Doc, null);
         const flyout = (
-            <div className="collectionPivotView-flyout" title=" ">
+            <div className="collectionPivotView-flyout" style={{ width: `${this._facetWidth}` }}>
                 {this._allFacets.map(facet => <label className="collectionPivotView-flyout-item" onClick={e => this.facetClick(facet)}>
                     <input type="checkbox" checked={this.props.Document.facetCollection instanceof Doc && DocListCast(this.props.Document.facetCollection.data).some(d => {
                         return d.title === facet;
@@ -120,23 +134,27 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
                 </label>)}
             </div>
         );
-        return !facetCollection ? (null) : <div className="collectionPivotView">
-            <div className="collectionPivotView-treeView">
-                <div className="collectionPivotView-addFacet" onPointerDown={e => e.stopPropagation()}>
-                    <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
-                        <div className="collectionPivotView-button">
-                            <span className="collectionPivotView-span">Facet Filters</span>
-                            <FontAwesomeIcon icon={faEdit} size={"lg"} />
-                        </div>
-                    </Flyout>
+        return !facetCollection ? (null) :
+            <div className="collectionPivotView">
+                <div className="collectionPivotView-dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
+                    <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
                 </div>
-                <div className="collectionPivotView-tree">
-                    <CollectionTreeView {...this.props} Document={facetCollection} />
+                <div className="collectionPivotView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
+                    <div className="collectionPivotView-addFacet" style={{ width: `${this._facetWidth}px` }} onPointerDown={e => e.stopPropagation()}>
+                        <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
+                            <div className="collectionPivotView-button">
+                                <span className="collectionPivotView-span">Facet Filters</span>
+                                <FontAwesomeIcon icon={faEdit} size={"lg"} />
+                            </div>
+                        </Flyout>
+                    </div>
+                    <div className="collectionPivotView-tree">
+                        <CollectionTreeView {...this.props} Document={facetCollection} />
+                    </div>
                 </div>
-            </div>
-            <div className="collectionPivotView-pivot">
-                <CollectionFreeFormView  {...this.props} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
-            </div>
-        </div>;
+                <div className="collectionPivotView-pivot" style={{ width: this.bodyPanelWidth() }}>
+                    <CollectionFreeFormView  {...this.props} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
+                </div>
+            </div>;
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From bfebed91e12abca324d7a638adeb1da9755a9057 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 22 Jan 2020 15:41:04 -0500
Subject: basic cleanup for warnings

---
 src/client/apis/youtube/YoutubeBox.tsx             |  2 +-
 src/client/util/ProseMirrorEditorView.tsx          | 74 ----------------------
 src/client/util/RichTextMenu.tsx                   | 68 ++++++++++----------
 src/client/util/RichTextSchema.tsx                 |  2 +-
 src/client/views/EditableView.tsx                  |  2 +-
 src/client/views/MainView.tsx                      |  2 +-
 .../views/collections/CollectionPivotView.tsx      | 11 ++--
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 .../views/collections/CollectionViewChromes.tsx    |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 .../collectionFreeForm/MarqueeOptionsMenu.tsx      |  3 +
 src/client/views/linking/LinkMenu.tsx              |  2 +-
 src/client/views/nodes/WebBox.tsx                  |  2 +-
 src/client/views/search/SearchItem.tsx             |  1 -
 src/new_fields/Doc.ts                              | 10 +--
 src/new_fields/documentSchemas.ts                  |  1 +
 src/server/Websocket/Websocket.ts                  |  2 -
 17 files changed, 59 insertions(+), 132 deletions(-)
 delete mode 100644 src/client/util/ProseMirrorEditorView.tsx

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

diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index fd3d9e2f1..d654e2530 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -46,7 +46,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
      * When component mounts, last search's results are laoded in based on the back up stored
      * in the document of the props.
      */
-    async componentWillMount() {
+    async componentDidMount() {
         //DocServer.getYoutubeChannels();
         const castedSearchBackUp = Cast(this.props.Document.cachedSearchResults, Doc);
         const awaitedBackUp = await castedSearchBackUp;
diff --git a/src/client/util/ProseMirrorEditorView.tsx b/src/client/util/ProseMirrorEditorView.tsx
deleted file mode 100644
index b42adfbb4..000000000
--- a/src/client/util/ProseMirrorEditorView.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from "react";
-import { EditorView } from "prosemirror-view";
-import { EditorState } from "prosemirror-state";
-
-export interface ProseMirrorEditorViewProps {
-    /* EditorState instance to use. */
-    editorState: EditorState;
-    /* Called when EditorView produces new EditorState. */
-    onEditorState: (editorState: EditorState) => any;
-}
-
-/**
- * This wraps ProseMirror's EditorView into React component.
- * This code was found on https://discuss.prosemirror.net/t/using-with-react/904
- */
-export class ProseMirrorEditorView extends React.Component<ProseMirrorEditorViewProps>  {
-
-    private _editorView?: EditorView;
-
-    _createEditorView = (element: HTMLDivElement | null) => {
-        if (element !== null) {
-            this._editorView = new EditorView(element, {
-                state: this.props.editorState,
-                dispatchTransaction: this.dispatchTransaction,
-            });
-        }
-    }
-
-    dispatchTransaction = (tx: any) => {
-        // In case EditorView makes any modification to a state we funnel those
-        // modifications up to the parent and apply to the EditorView itself.
-        const editorState = this.props.editorState.apply(tx);
-        if (this._editorView) {
-            this._editorView.updateState(editorState);
-        }
-        this.props.onEditorState(editorState);
-    }
-
-    focus() {
-        if (this._editorView) {
-            this._editorView.focus();
-        }
-    }
-
-    componentWillReceiveProps(nextProps: { editorState: EditorState<any>; }) {
-        // In case we receive new EditorState through props — we apply it to the
-        // EditorView instance.
-        if (this._editorView) {
-            if (nextProps.editorState !== this.props.editorState) {
-                this._editorView.updateState(nextProps.editorState);
-            }
-        }
-    }
-
-    componentWillUnmount() {
-        if (this._editorView) {
-            this._editorView.destroy();
-        }
-    }
-
-    shouldComponentUpdate() {
-        // Note that EditorView manages its DOM itself so we'd ratrher don't mess
-        // with it.
-        return false;
-    }
-
-    render() {
-        // Render just an empty div which is then used as a container for an
-        // EditorView instance.
-        return (
-            <div ref={this._createEditorView} />
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index f070589ae..eb5c90654 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -73,7 +73,7 @@ export default class RichTextMenu extends AntimodeMenu {
         this.fontSizeOptions = [
             { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize },
@@ -312,22 +312,22 @@ export default class RichTextMenu extends AntimodeMenu {
         }
 
         return (
-            <button className={"antimodeMenu-button" + (isActive ? " active" : "")} title={title} onPointerDown={onClick}>
+            <button className={"antimodeMenu-button" + (isActive ? " active" : "")} key={title} title={title} onPointerDown={onClick}>
                 <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
             </button>
         );
     }
 
-    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[]): JSX.Element {
+    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
         const items = options.map(({ title, label, hidden, style }) => {
             if (hidden) {
                 return label === activeOption ?
-                    <option value={label} title={title} style={style ? style : {}} selected hidden>{label}</option> :
-                    <option value={label} title={title} style={style ? style : {}} hidden>{label}</option>;
+                    <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
+                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
             }
             return label === activeOption ?
-                <option value={label} title={title} style={style ? style : {}} selected>{label}</option> :
-                <option value={label} title={title} style={style ? style : {}}>{label}</option>;
+                <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
+                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
         });
 
         const self = this;
@@ -340,19 +340,19 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             });
         }
-        return <select onChange={onChange}>{items}</select>;
+        return <select onChange={onChange} key={key}>{items}</select>;
     }
 
-    createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[]): JSX.Element {
+    createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
         const items = options.map(({ title, label, hidden, style }) => {
             if (hidden) {
                 return label === activeOption ?
-                    <option value={label} title={title} style={style ? style : {}} selected hidden>{label}</option> :
-                    <option value={label} title={title} style={style ? style : {}} hidden>{label}</option>;
+                    <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
+                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
             }
             return label === activeOption ?
-                <option value={label} title={title} style={style ? style : {}} selected>{label}</option> :
-                <option value={label} title={title} style={style ? style : {}}>{label}</option>;
+                <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
+                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
         });
 
         const self = this;
@@ -363,7 +363,7 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             });
         }
-        return <select onChange={e => onChange(e.target.value)}>{items}</select>;
+        return <select onChange={e => onChange(e.target.value)} key={key}>{items}</select>;
     }
 
     changeFontSize = (mark: Mark, view: EditorView) => {
@@ -472,7 +472,7 @@ export default class RichTextMenu extends AntimodeMenu {
             </div>;
 
         return (
-            <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} />
+            <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} dropdownContent={dropdownContent} />
         );
     }
 
@@ -535,21 +535,21 @@ export default class RichTextMenu extends AntimodeMenu {
             </button>;
 
         const dropdownContent =
-            <div className="dropdown">
+            <div className="dropdown" >
                 <p>Change font color:</p>
                 <div className="color-wrapper">
                     {this.fontColors.map(color => {
                         if (color) {
                             return this.activeFontColor === color ?
-                                <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
-                                <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
+                                <button className="color-button active" key={"active" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
+                                <button className="color-button" key={"other" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
                         }
                     })}
                 </div>
             </div>;
 
         return (
-            <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} />
+            <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} />
         );
     }
 
@@ -582,7 +582,7 @@ export default class RichTextMenu extends AntimodeMenu {
         }
 
         const button =
-            <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onHighlightClick}>
+            <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={onHighlightClick}>
                 <FontAwesomeIcon icon="highlighter" size="lg" />
                 <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
             </button>;
@@ -594,15 +594,15 @@ export default class RichTextMenu extends AntimodeMenu {
                     {this.highlightColors.map(color => {
                         if (color) {
                             return this.activeHighlightColor === color ?
-                                <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
-                                <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
+                                <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
+                                <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
                         }
                     })}
                 </div>
             </div>;
 
         return (
-            <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} />
+            <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} />
         );
     }
 
@@ -635,7 +635,7 @@ export default class RichTextMenu extends AntimodeMenu {
             </div>;
 
         return (
-            <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
+            <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
         );
     }
 
@@ -773,7 +773,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
     render() {
 
-        const row1 = <div className="antimodeMenu-row">{[
+        const row1 = <div className="antimodeMenu-row" key="row1">{[
             this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
             this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
             this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
@@ -787,14 +787,14 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
         ]}</div>;
 
-        const row2 = <div className="antimodeMenu-row row-2">
-            <div>
-                {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions),
-                this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions),
-                this.createNodesDropdown(this.activeListType, this.listTypeOptions)]}
+        const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
+            <div key="row">
+                {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
+                this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
+                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]}
             </div>
-            <div>
-                <button className="antimodeMenu-button" title="Pin menu" onClick={this.toggleMenuPin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
+            <div key="button">
+                <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
                     <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} />
                 </button>
                 {this.getDragger()}
@@ -864,12 +864,12 @@ class ButtonDropdown extends React.Component<ButtonDropdownProps> {
                     </button> :
                     <>
                         {this.props.button}
-                        <button className="dropdown-button antimodeMenu-button" onPointerDown={this.onDropdownClick}>
+                        <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
                             <FontAwesomeIcon icon="caret-down" size="sm" />
                         </button>
                     </>}
 
-                {this.showDropdown ? this.props.dropdownContent : <></>}
+                {this.showDropdown ? this.props.dropdownContent : (null)}
             </div>
         );
     }
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 1f70cafc4..5751c8c7e 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -306,7 +306,7 @@ export const marks: { [index: string]: MarkSpec } = {
             }
         }],
         toDOM(node: any) {
-            return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }];
+            return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0];
         }
     },
 
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 0d677b8ce..394caba72 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -66,7 +66,7 @@ export class EditableView extends React.Component<EditableProps> {
     }
 
     @action
-    componentWillReceiveProps(nextProps: EditableProps) {
+    componentDidUpdate(nextProps: EditableProps) {
         // this is done because when autosuggest is turned on, the suggestions are passed in as a prop,
         // so when the suggestions are passed in, and no editing prop is passed in, it used to set it
         // to false. this will no longer do so -syip
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0958c434a..79e0cfea5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -64,7 +64,7 @@ export class MainView extends React.Component {
 
     public isPointerDown = false;
 
-    componentWillMount() {
+    componentDidMount() {
         const tag = document.createElement('script');
 
         tag.src = "https://www.youtube.com/iframe_api";
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
index f23540529..aa68cc18f 100644
--- a/src/client/views/collections/CollectionPivotView.tsx
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -28,6 +28,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
         if (!this.props.Document.facetCollection) {
             const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", yMargin: 0, treeViewHideTitle: true });
             facetCollection.target = this.props.Document;
+            facetCollection.dontCopyOnAlias = true;
 
             const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
             const script = CompileScript(scriptText, {
@@ -122,8 +123,8 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
         const facetCollection = Cast(this.props.Document?.facetCollection, Doc, null);
         const flyout = (
             <div className="collectionPivotView-flyout" style={{ width: `${this._facetWidth}` }}>
-                {this._allFacets.map(facet => <label className="collectionPivotView-flyout-item" onClick={e => this.facetClick(facet)}>
-                    <input type="checkbox" checked={this.props.Document.facetCollection instanceof Doc && DocListCast(this.props.Document.facetCollection.data).some(d => {
+                {this._allFacets.map(facet => <label className="collectionPivotView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
+                    <input type="checkbox" onChange={e => { }} checked={this.props.Document.facetCollection instanceof Doc && DocListCast(this.props.Document.facetCollection.data).some(d => {
                         return d.title === facet;
                     })} />
                     <span className="checkmark" />
@@ -133,7 +134,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
         );
         return !facetCollection ? (null) :
             <div className="collectionPivotView">
-                <div className="collectionPivotView-dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
+                <div className="collectionPivotView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
                     <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
                 </div>
                 <div className="collectionPivotView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
@@ -145,11 +146,11 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
                             </div>
                         </Flyout>
                     </div>
-                    <div className="collectionPivotView-tree">
+                    <div className="collectionPivotView-tree" key="tree">
                         <CollectionTreeView {...this.props} Document={facetCollection} />
                     </div>
                 </div>
-                <div className="collectionPivotView-pivot" style={{ width: this.bodyPanelWidth() }}>
+                <div className="collectionPivotView-pivot" key="pivot" style={{ width: this.bodyPanelWidth() }}>
                     <CollectionFreeFormView  {...this.props} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
                 </div>
             </div>;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index bef587786..9776085c9 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -720,7 +720,7 @@ Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey
     const facetValues = new Set<string>();
     DocListCast(dataDoc[dataKey]).forEach(child => {
         Object.keys(Doc.GetProto(child)).forEach(key => child[key] instanceof Doc && facetValues.add((child[key] as Doc)[facetHeader]?.toString() || "(null)"));
-        facetValues.add(child[facetHeader]?.toString() || "(null)");
+        facetValues.add(Field.toString(child[facetHeader] as Field));
     });
     const text = "determineCheckedState(layoutDoc, facetHeader, facetValue)";
     const params = {
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 075c48134..42c563fe2 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -416,8 +416,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                                 placeholder="FILTER"
                                 value={this.filterValue ? this.filterValue.script.originalScript === "return true" ? "" : this.filterValue.script.originalScript : ""}
                                 onChange={(e) => { }}
-                                onPointerDown={this.openViewSpecs}
-                                id="viewSpecsInput" />
+                                onPointerDown={this.openViewSpecs} />
                             {this.getPivotInput()}
                             <div className="collectionViewBaseChrome-viewSpecsMenu"
                                 onPointerDown={this.openViewSpecs}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index db5259bdc..a7e4fce4e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -3,7 +3,7 @@ import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
 import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
@@ -737,7 +737,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const fontSize = Cast(viewDef.fontSize, "number");
             return [text, x, y, width, height].some(val => val === undefined) ? undefined :
                 {
-                    ele: <div className="collectionFreeform-customText" style={{ width, height, fontSize, transform: `translate(${x}px, ${y}px)` }}>
+                    ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z} style={{ width, height, fontSize, transform: `translate(${x}px, ${y}px)` }}>
                         {text}
                     </div>,
                     bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 32e39d25e..71f265484 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -25,18 +25,21 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
             <button
                 className="antimodeMenu-button"
                 title="Create a Collection"
+                key="group"
                 onPointerDown={this.createCollection}>
                 <FontAwesomeIcon icon="object-group" size="lg" />
             </button>,
             <button
                 className="antimodeMenu-button"
                 title="Summarize Documents"
+                key="summarize"
                 onPointerDown={this.summarize}>
                 <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
             </button>,
             <button
                 className="antimodeMenu-button"
                 title="Delete Documents"
+                key="delete"
                 onPointerDown={this.delete}>
                 <FontAwesomeIcon icon="trash-alt" size="lg" />
             </button>,
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 52628ba4c..1a40f0c55 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -25,7 +25,7 @@ export class LinkMenu extends React.Component<Props> {
     @observable private _editingLink?: Doc;
 
     @action
-    componentWillReceiveProps() {
+    componentDidMount() {
         this._editingLink = undefined;
     }
 
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index b35ea0bb0..e9118e3ab 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -34,7 +34,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
     @observable private collapsed: boolean = true;
     @observable private url: string = "";
 
-    componentWillMount() {
+    componentDidMount() {
 
         const field = Cast(this.props.Document[this.props.fieldKey], WebField);
         if (field && field.url.href.indexOf("youtube") !== -1) {
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 88a4d4c50..30fcc7908 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -260,7 +260,6 @@ export class SearchItem extends React.Component<SearchItemProps> {
     onPointerMoved = (e: PointerEvent) => {
         if (Math.abs(e.clientX - this._downX) > Utils.DRAG_THRESHOLD ||
             Math.abs(e.clientY - this._downY) > Utils.DRAG_THRESHOLD) {
-            console.log("DRAGGIGNG");
             document.removeEventListener("pointermove", this.onPointerMoved);
             document.removeEventListener("pointerup", this.onPointerUp);
             const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 9620f753f..a7ab33008 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -38,7 +38,7 @@ export namespace Field {
     }
     export function toString(field: Field): string {
         if (typeof field === "string") {
-            return `"${field}"`;
+            return field;
         } else if (typeof field === "number" || typeof field === "boolean") {
             return String(field);
         } else if (field instanceof ObjectField) {
@@ -544,6 +544,7 @@ export namespace Doc {
                     copy[key] = field;
                 } else if (cfield instanceof ComputedField) {
                     copy[key] = ComputedField.MakeFunction(cfield.script.originalScript);
+                } else if (doc[key] instanceof Doc && (doc[key] as Doc).dontCopyOnAlias) {
                 } else if (field instanceof ObjectField) {
                     copy[key] = ObjectField.MakeCopy(field);
                 } else if (field instanceof Promise) {
@@ -800,13 +801,12 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo
 });
 Scripting.addGlobal(function matchFieldValue(doc: Doc, key: string, value: any) {
     const fieldVal = doc[key] ? doc[key] : doc[key + "_ext"];
-    if (StrCast(fieldVal, null) !== undefined) return StrCast(fieldVal) === value;
-    if (NumCast(fieldVal, null) !== undefined) return NumCast(fieldVal) === value;
     if (Cast(fieldVal, listSpec("string"), []).length) {
         const vals = Cast(fieldVal, listSpec("string"), []);
         return vals.some(v => v === value);
     }
-    return false;
+    const fieldStr = Field.toString(fieldVal as Field);
+    return fieldStr == value;
 });
 Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: string) {
     const docFilters = Cast(container.docFilter, listSpec("string"), []);
@@ -818,7 +818,7 @@ Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: an
     }
     if (modifiers !== undefined) {
         docFilters.push(key);
-        docFilters.push(value);
+        docFilters.push(value)
         docFilters.push(modifiers);
         container.docFilter = new List<string>(docFilters);
     }
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 3683e5820..3b641fc8e 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -18,6 +18,7 @@ export const documentSchema = createSchema({
     creationDate: DateField,     // when the document was created
     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")
+    dontCopyOnAlias: "boolean", // whether the document should not be copied when it is a field of a document that is being aliased
     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)
     onPointerDown: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 9e6ad1c72..a2fdc7c89 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -124,8 +124,6 @@ export namespace WebSocket {
             socket.broadcast.emit(MessageStore.SetField.Message, newValue));
         if (newValue.type === Types.Text) {
             Search.updateDocument({ id: newValue.id, data: (newValue as any).data });
-            console.log("set field");
-            console.log("checking in");
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 0142f4e14cc8e291ee5acbae3cc4b81c95c4634a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 23 Jan 2020 00:40:55 -0500
Subject: got rid of rule provider stuff.  fixed panning in freeform view with
 pivot layouts

---
 src/client/util/RichTextMenu.tsx                   | 20 ++---------
 src/client/util/RichTextRules.ts                   | 24 -------------
 src/client/util/RichTextSchema.tsx                 |  1 -
 src/client/views/DocumentDecorations.tsx           |  9 +----
 src/client/views/GestureOverlay.tsx                |  1 -
 src/client/views/InkingControl.tsx                 | 29 +--------------
 src/client/views/MainView.tsx                      |  4 ---
 src/client/views/OverlayView.tsx                   |  1 -
 src/client/views/Palette.tsx                       |  1 -
 .../views/collections/CollectionDockingView.tsx    |  1 -
 .../views/collections/CollectionLinearView.tsx     |  1 -
 .../views/collections/CollectionSchemaCells.tsx    |  1 -
 .../views/collections/CollectionSchemaView.tsx     |  1 -
 .../views/collections/CollectionStackingView.tsx   |  3 +-
 src/client/views/collections/CollectionSubView.tsx |  1 -
 .../views/collections/CollectionTreeView.tsx       |  3 --
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 42 +++++-----------------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  4 +--
 .../views/nodes/ContentFittingDocumentView.tsx     |  2 --
 src/client/views/nodes/DocumentBox.tsx             |  1 -
 src/client/views/nodes/DocumentView.tsx            |  8 ++---
 src/client/views/nodes/FieldView.tsx               |  1 -
 src/client/views/nodes/FormattedTextBox.tsx        | 33 -----------------
 src/client/views/nodes/FormattedTextBoxComment.tsx |  1 -
 src/client/views/nodes/ImageBox.tsx                |  1 -
 src/client/views/nodes/KeyValuePair.tsx            |  1 -
 src/client/views/nodes/VideoBox.tsx                |  1 -
 src/client/views/nodes/WebBox.tsx                  |  1 -
 src/client/views/pdf/PDFViewer.tsx                 |  1 -
 .../views/presentationview/PresElementBox.tsx      |  1 -
 src/client/views/search/SearchItem.tsx             |  1 -
 src/mobile/MobileInterface.tsx                     |  1 -
 32 files changed, 16 insertions(+), 185 deletions(-)

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

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index eb5c90654..e07efe056 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -367,27 +367,11 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     changeFontSize = (mark: Mark, view: EditorView) => {
-        const size = mark.attrs.fontSize;
-        if (this.editorProps) {
-            const ruleProvider = this.editorProps.ruleProvider;
-            const heading = NumCast(this.editorProps.Document.heading);
-            if (ruleProvider && heading) {
-                ruleProvider["ruleSize_" + heading] = size;
-            }
-        }
-        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch);
+        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch);
     }
 
     changeFontFamily = (mark: Mark, view: EditorView) => {
-        const fontName = mark.attrs.family;
-        if (this.editorProps) {
-            const ruleProvider = this.editorProps.ruleProvider;
-            const heading = NumCast(this.editorProps.Document.heading);
-            if (ruleProvider && heading) {
-                ruleProvider["ruleFont_" + heading] = fontName;
-            }
-        }
-        this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch);
+        this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch);
     }
 
     // TODO: remove doesn't work
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index e20abb04c..0ee9481f0 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -67,12 +67,6 @@ export const inpRules = {
             new RegExp(/^%([0-9]+)\s$/),
             (state, match, start, end) => {
                 const size = Number(match[1]);
-                const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
-                const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
-                if (ruleProvider && heading) {
-                    (Cast(FormattedTextBox.FocusedBox!.props.Document, Doc) as Doc).heading = size;
-                    return state.tr.deleteRange(start, end);
-                }
                 return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
             }),
 
@@ -175,12 +169,6 @@ export const inpRules = {
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
-                const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
-                const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
-                if (ruleProvider && heading) {
-                    ruleProvider["ruleAlign_" + heading] = "center";
-                    return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
-                }
                 const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
                     state.tr;
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
@@ -191,12 +179,6 @@ export const inpRules = {
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
-                const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
-                const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
-                if (ruleProvider && heading) {
-                    ruleProvider["ruleAlign_" + heading] = "left";
-                    return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
-                }
                 const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
                     state.tr;
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
@@ -207,12 +189,6 @@ export const inpRules = {
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
-                const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
-                const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
-                if (ruleProvider && heading) {
-                    ruleProvider["ruleAlign_" + heading] = "right";
-                    return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
-                }
                 const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
                     state.tr;
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 5751c8c7e..fe7ab31a8 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -761,7 +761,6 @@ export class DashDocView {
                     fitToBox={BoolCast(dashDoc.fitToBox)}
                     addDocument={returnFalse}
                     removeDocument={removeDoc}
-                    ruleProvider={undefined}
                     ScreenToLocalTransform={this.getDocTransform}
                     addDocTab={self._textBox.props.addDocTab}
                     pinToPres={returnFalse}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 322536b7e..e6a119d1f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -362,14 +362,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     onRadiusMove = (e: PointerEvent): void => {
         let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1]));
         dist = dist < 3 ? 0 : dist;
-        let usingRule = false;
-        SelectionManager.SelectedDocuments().map(dv => {
-            const ruleProvider = dv.props.ruleProvider;
-            const heading = NumCast(dv.props.Document.heading);
-            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.isTemplateForField ? dv.props.Document : Doc.GetProto(dv.props.Document)).
+        SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplateForField ? dv.props.Document : Doc.GetProto(dv.props.Document)).
             map(d => d.borderRounding = `${Math.min(100, dist)}%`);
         e.stopPropagation();
         e.preventDefault();
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 84d089b47..580c53a37 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -462,7 +462,6 @@ export default class GestureOverlay extends Touchable {
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
                 onClick={undefined}
-                ruleProvider={undefined}
                 removeDocument={undefined}
                 ScreenToLocalTransform={() => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1)}
                 ContentScaling={returnOne}
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index ab53b76be..10d013865 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -55,34 +55,7 @@ export class InkingControl {
                 }
                 const oldColor = StrCast(targetDoc.backgroundColor);
                 let matchedColor = this._selectedColor;
-                const cvd = view.props.ContainingCollectionDoc;
-                let ruleProvider = view.props.ruleProvider;
-                // if (cvd) {
-                //     if (!cvd.colorPalette) {
-                //         const defaultPalette = ["rg(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)",];
-                //         const colorPalette = Cast(cvd.colorPalette, listSpec("string"));
-                //         if (!colorPalette) cvd.colorPalette = new List<string>(defaultPalette);
-                //     }
-                //     const cp = Cast(cvd.colorPalette, listSpec("string")) as string[];
-                //     let closest = 0;
-                //     let dist = 10000000;
-                //     const ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor));
-                //     for (let i = 0; i < cp.length; i++) {
-                //         const cpcol = Utils.fromRGBAstr(cp[i]);
-                //         const d = Math.sqrt((ccol.r - cpcol.r) * (ccol.r - cpcol.r) + (ccol.b - cpcol.b) * (ccol.b - cpcol.b) + (ccol.g - cpcol.g) * (ccol.g - cpcol.g));
-                //         if (d < dist) {
-                //             dist = d;
-                //             closest = i;
-                //         }
-                //     }
-                //     cp[closest] = "rgba(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + "," + color.rgb.a + ")";
-                //     cvd.colorPalette = new List(cp);
-                //     matchedColor = cp[closest];
-                //     ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined;
-                //     ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)));
-                // }
-                (!ruleProvider && targetDoc) && (Doc.Layout(view.props.Document).backgroundColor = matchedColor);
+                targetDoc && (Doc.Layout(view.props.Document).backgroundColor = matchedColor);
 
                 return {
                     target: targetDoc,
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 79e0cfea5..cd4e9a1a6 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -276,7 +276,6 @@ export class MainView extends React.Component {
             addDocTab={this.addDocTabFunc}
             pinToPres={emptyFunction}
             onClick={undefined}
-            ruleProvider={undefined}
             removeDocument={undefined}
             ScreenToLocalTransform={Transform.Identity}
             ContentScaling={returnOne}
@@ -377,7 +376,6 @@ export class MainView extends React.Component {
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={undefined}
-                    ruleProvider={undefined}
                     onClick={undefined}
                     ScreenToLocalTransform={Transform.Identity}
                     ContentScaling={returnOne}
@@ -404,7 +402,6 @@ export class MainView extends React.Component {
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={returnFalse}
-                    ruleProvider={undefined}
                     onClick={undefined}
                     ScreenToLocalTransform={this.mainContainerXf}
                     ContentScaling={returnOne}
@@ -500,7 +497,6 @@ export class MainView extends React.Component {
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={this.remButtonDoc}
-                    ruleProvider={undefined}
                     onClick={undefined}
                     ScreenToLocalTransform={this.buttonBarXf}
                     ContentScaling={returnOne}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 350a75d29..295cd7c6e 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -178,7 +178,6 @@ export class OverlayView extends React.Component {
                     // select={emptyFunction}
                     // layoutKey={"layout"}
                     bringToFront={emptyFunction}
-                    ruleProvider={undefined}
                     addDocument={undefined}
                     removeDocument={undefined}
                     ContentScaling={returnOne}
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 811c24f53..10aac96a0 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -56,7 +56,6 @@ export default class Palette extends React.Component<PaletteProps> {
                             addDocTab={returnFalse}
                             pinToPres={emptyFunction}
                             removeDocument={undefined}
-                            ruleProvider={undefined}
                             onClick={undefined}
                             ScreenToLocalTransform={Transform.Identity}
                             ContentScaling={returnOne}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 5f9b4e0c7..125cd828f 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -723,7 +723,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             bringToFront={emptyFunction}
             addDocument={undefined}
             removeDocument={undefined}
-            ruleProvider={undefined}
             ContentScaling={this.contentScaling}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 77af2dc0e..8bf6b4202 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -108,7 +108,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 addDocTab={this.props.addDocTab}
                                 pinToPres={emptyFunction}
                                 removeDocument={this.props.removeDocument}
-                                ruleProvider={undefined}
                                 onClick={undefined}
                                 ScreenToLocalTransform={this.getTransform(dref)}
                                 ContentScaling={returnOne}
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 79a34bc00..7e990ab4d 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -145,7 +145,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             DataDoc: this.props.rowProps.original,
             LibraryPath: [],
             fieldKey: this.props.rowProps.column.id as string,
-            ruleProvider: undefined,
             ContainingCollectionView: this.props.CollectionView,
             ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
             isSelected: returnFalse,
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b466d9511..994c7b351 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -144,7 +144,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                 LibraryPath={this.props.LibraryPath}
                 childDocs={this.childDocs}
                 renderDepth={this.props.renderDepth}
-                ruleProvider={this.props.Document.isRuleProvider && layoutDoc && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
                 PanelWidth={this.previewWidth}
                 PanelHeight={this.previewHeight}
                 getTransform={this.getPreviewTransform}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index b2e4b9d90..ec22bb813 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -39,7 +39,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @observable _scroll = 0; // used to force the document decoration to update when scrolling
     @computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
     @computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
-    @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized).map(d => (Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, d).layout as Doc) || d); }
+    @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => !pair.layout.isMinimized).map(pair => pair.layout); }
     @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
     @computed get yMargin() { return Math.max(this.props.Document.showTitle && !this.props.Document.showTitleHover ? 30 : 0, NumCast(this.props.Document.yMargin, 2 * this.gridGap)); }
     @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
@@ -167,7 +167,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             LibraryPath={this.props.LibraryPath}
             showOverlays={this.overlays}
             renderDepth={this.props.renderDepth + 1}
-            ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
             fitToBox={this.props.fitToBox}
             onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
             PanelWidth={width}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 6c4f940b6..063b5c5df 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -40,7 +40,6 @@ export interface CollectionViewProps extends FieldViewProps {
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
-    ruleProvider: Doc | undefined;
     children?: never | (() => JSX.Element[]) | React.ReactNode;
     isAnnotationOverlay?: boolean;
     annotationsKey: string;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 2c8c53f97..ec348e74b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -43,7 +43,6 @@ export interface TreeViewProps {
     prevSibling?: Doc;
     renderDepth: number;
     deleteDoc: (doc: Doc) => boolean;
-    ruleProvider: Doc | undefined;
     moveDocument: DragManager.MoveFunction;
     dropAction: "alias" | "copy" | undefined;
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string, libraryPath?: Doc[]) => boolean;
@@ -352,7 +351,6 @@ class TreeView extends React.Component<TreeViewProps> {
                     LibraryPath={emptyPath}
                     renderDepth={this.props.renderDepth + 1}
                     showOverlays={this.noOverlays}
-                    ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
                     PanelWidth={this.docWidth}
                     PanelHeight={this.docHeight}
@@ -559,7 +557,6 @@ class TreeView extends React.Component<TreeViewProps> {
                 containingCollection={containingCollection}
                 prevSibling={docs[i]}
                 treeViewId={treeViewId}
-                ruleProvider={containingCollection.isRuleProvider && pair.layout.type !== DocumentType.TEXT ? containingCollection : containingCollection.ruleProvider as Doc}
                 key={child[Id]}
                 indentDocument={indent}
                 outdentDocument={outdent}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index aa1559416..65953dbdc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -54,7 +54,6 @@ export const panZoomSchema = createSchema({
     arrangeScript: ScriptField,
     arrangeInit: ScriptField,
     useClusters: "boolean",
-    isRuleProvider: "boolean",
     fitToBox: "boolean",
     xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
@@ -103,14 +102,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
     private addLiveTextBox = (newBox: Doc) => {
         FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
-        const maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
-        let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0;
-        if (heading === 0) {
-            const sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 :
-                DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0);
-            heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
-        }
-        !this.Document.isRuleProvider && (newBox.heading = heading);
         this.addDocument(newBox);
     }
     private addDocument = (newBox: Doc) => {
@@ -412,16 +403,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         if (!this.isAnnotationOverlay) {
             PDFMenu.Instance.fadeOut(true);
-            const minx = docs.length ? NumCast(docs[0].x) : 0;
-            const maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
-            const miny = docs.length ? NumCast(docs[0].y) : 0;
-            const maxy = docs.length ? NumCast(docs[0].height) + miny : miny;
+            const minx = this.childDataProvider(docs[0]).x;//docs.length ? NumCast(docs[0].x) : 0;
+            const miny = this.childDataProvider(docs[0]).y;//docs.length ? NumCast(docs[0].y) : 0;
+            const maxx = this.childDataProvider(docs[0]).width + minx;//docs.length ? NumCast(docs[0].width) + minx : minx;
+            const maxy = this.childDataProvider(docs[0]).height + miny;//docs.length ? NumCast(docs[0].height) + miny : miny;
             const ranges = docs.filter(doc => doc).reduce((range, doc) => {
                 const layoutDoc = Doc.Layout(doc);
-                const x = NumCast(doc.x);
-                const xe = x + NumCast(layoutDoc.width);
-                const y = NumCast(doc.y);
-                const ye = y + NumCast(layoutDoc.height);
+                const x = this.childDataProvider(doc).x;//NumCast(doc.x);
+                const y = this.childDataProvider(doc).y;//NumCast(doc.y);
+                const xe = this.childDataProvider(doc).width + x;//x + NumCast(layoutDoc.width);
+                const ye = this.childDataProvider(doc).height + y; //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]]);
@@ -694,7 +685,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             Document: childLayout,
             LibraryPath: this.libraryPath,
             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
             onClick: this.onChildClickHandler,
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
@@ -779,7 +769,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             computedElementData.elements.push({
                 ele: <CollectionFreeFormDocumentView key={pair.layout[Id]}  {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
-                    ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
                     fitToBox={this.props.fitToBox || this.Document.freeformLayoutEngine === "pivot"} />,
                 bounds: this.childDataProvider(pair.layout)
@@ -827,20 +816,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }, "arrange contents");
     }
 
-    autoFormat = () => {
-        this.Document.isRuleProvider = !this.Document.isRuleProvider;
-        // 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(Doc.Layout(pair.layout).data).map(heading => {
-                const headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, heading);
-                const 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) {
-                    Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(headingLayout.heading)] = headingLayout.backgroundColor;
-                }
-            })
-        );
-    }
 
     analyzeStrokes = async () => {
         const children = await DocListCastAsync(this.dataDoc.data);
@@ -908,7 +883,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         layoutItems.push({ description: `${this.Document.LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document.LODdisable = !this.Document.LODdisable, icon: "table" });
         layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
-        layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" });
         layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
         layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 614a68e7a..cd78f5d49 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -73,10 +73,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         .scale(1 / this.contentScaling())
 
     borderRounding = () => {
-        const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
         const ld = this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] instanceof Doc ? this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] as Doc : undefined;
-        const br = StrCast((ld || this.props.Document).borderRounding);
-        return !br && ruleRounding ? ruleRounding : br;
+        return StrCast((ld || this.props.Document).borderRounding);
     }
 
     @computed
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index e97445f27..96102e0e8 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -24,7 +24,6 @@ interface ContentFittingDocumentViewProps {
     fitToBox?: boolean;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    ruleProvider: Doc | undefined;
     focus?: (doc: Doc) => void;
     showOverlays?: (doc: Doc) => { title?: string, caption?: string };
     CollectionView?: CollectionView;
@@ -97,7 +96,6 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                         LibraryPath={this.props.LibraryPath}
                         fitToBox={this.props.fitToBox}
                         onClick={this.props.onClick}
-                        ruleProvider={this.props.ruleProvider}
                         showOverlays={this.props.showOverlays}
                         addDocument={this.props.addDocument}
                         removeDocument={this.props.removeDocument}
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 863ea748b..6b7b652c6 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -96,7 +96,6 @@ export class DocumentBox extends DocComponent<FieldViewProps, DocBoxSchema>(DocB
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
                 removeDocument={this.props.removeDocument}
-                ruleProvider={this.props.ruleProvider}
                 addDocTab={this.props.addDocTab}
                 pinToPres={this.props.pinToPres}
                 getTransform={this.getTransform}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b491acb8d..2570d232a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -72,7 +72,6 @@ export interface DocumentViewProps {
     renderDepth: number;
     showOverlays?: (doc: Doc) => { title?: string, titleHover?: string, caption?: string };
     ContentScaling: () => number;
-    ruleProvider: Doc | undefined;
     PanelWidth: () => number;
     PanelHeight: () => number;
     focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void;
@@ -875,7 +874,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             renderDepth={this.props.renderDepth}
             showOverlays={this.props.showOverlays}
             ContentScaling={this.childScaling}
-            ruleProvider={this.props.ruleProvider}
             PanelWidth={this.props.PanelWidth}
             PanelHeight={this.props.PanelHeight}
             focus={this.props.focus}
@@ -967,16 +965,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     render() {
         if (!(this.props.Document instanceof Doc)) return (null);
-        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 = (clusterCol && !colorSet) ?
             this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) :
-            ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
+            StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
 
         const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
-        const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding;
+        const borderRounding = this.getLayoutPropStr("borderRounding");
         const localScale = fullDegree;
 
         const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 6e6ee1712..538e433a9 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -27,7 +27,6 @@ export interface FieldViewProps {
     fitToBox?: boolean;
     ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
-    ruleProvider: Doc | undefined;
     Document: Doc;
     DataDoc?: Doc;
     LibraryPath: Doc[];
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 3814f02da..0a1a79bc4 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -89,7 +89,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     private _scrollToRegionReactionDisposer: Opt<IReactionDisposer>;
     private _reactionDisposer: Opt<IReactionDisposer>;
     private _heightReactionDisposer: Opt<IReactionDisposer>;
-    private _rulesReactionDisposer: Opt<IReactionDisposer>;
     private _proxyReactionDisposer: Opt<IReactionDisposer>;
     private _pullReactionDisposer: Opt<IReactionDisposer>;
     private _pushReactionDisposer: Opt<IReactionDisposer>;
@@ -549,36 +548,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
             { fireImmediately: true });
 
-        this._rulesReactionDisposer = reaction(() => {
-            const ruleProvider = this.props.ruleProvider;
-            const heading = NumCast(this.layoutDoc.heading);
-            if (ruleProvider instanceof Doc) {
-                return {
-                    align: StrCast(ruleProvider["ruleAlign_" + heading], ""),
-                    font: StrCast(ruleProvider["ruleFont_" + heading], "Arial"),
-                    size: NumCast(ruleProvider["ruleSize_" + heading], 13)
-                };
-            }
-            return undefined;
-        },
-            action((rules: any) => {
-                this._ruleFontFamily = rules ? rules.font : "Arial";
-                this._ruleFontSize = rules ? rules.size : 0;
-                rules && setTimeout(() => {
-                    const view = this._editorView!;
-                    if (this.ProseRef) {
-                        const n = new NodeSelection(view.state.doc.resolve(0));
-                        if (this._editorView!.state.doc.textContent === "") {
-                            view.dispatch(view.state.tr.setSelection(new TextSelection(view.state.doc.resolve(0), view.state.doc.resolve(2))).
-                                replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true));
-                        } else if (n.node && n.node.type === view.state.schema.nodes.paragraph) {
-                            view.dispatch(view.state.tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align }));
-                        }
-                        this.tryUpdateHeight();
-                    }
-                }, 0);
-            }), { fireImmediately: true }
-        );
         this._scrollToRegionReactionDisposer = reaction(
             () => StrCast(this.layoutDoc.scrollToLinkID),
             async (scrollToLinkID) => {
@@ -849,7 +818,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     componentWillUnmount() {
         this._scrollToRegionReactionDisposer && this._scrollToRegionReactionDisposer();
-        this._rulesReactionDisposer && this._rulesReactionDisposer();
         this._reactionDisposer && this._reactionDisposer();
         this._proxyReactionDisposer && this._proxyReactionDisposer();
         this._pushReactionDisposer && this._pushReactionDisposer();
@@ -1171,7 +1139,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                             addDocument={(doc: Doc) => { doc.hideSidebar = true; return this.addDocument(doc); }}
                             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}>
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index f7a530790..8dedfda9b 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -185,7 +185,6 @@ export class FormattedTextBoxComment {
                                     active={returnFalse}
                                     addDocument={returnFalse}
                                     removeDocument={returnFalse}
-                                    ruleProvider={undefined}
                                     addDocTab={returnFalse}
                                     pinToPres={returnFalse}
                                     dontRegisterView={true}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index db427710e..b7e904283 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -364,7 +364,6 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                 addDocument={this.addDocument}
                 CollectionView={undefined}
                 ScreenToLocalTransform={this.props.ScreenToLocalTransform}
-                ruleProvider={undefined}
                 renderDepth={this.props.renderDepth + 1}
                 ContainingCollectionDoc={this.props.ContainingCollectionDoc}
                 chromeCollapsed={true}>
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 91f8bb3b0..46466aa0f 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -58,7 +58,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             LibraryPath: [],
             ContainingCollectionView: undefined,
             ContainingCollectionDoc: undefined,
-            ruleProvider: undefined,
             fieldKey: this.props.keyName,
             isSelected: returnFalse,
             select: emptyFunction,
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 376d27380..52b4f391a 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -353,7 +353,6 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
                     addDocument={this.addDocumentWithTimestamp}
                     CollectionView={undefined}
                     ScreenToLocalTransform={this.props.ScreenToLocalTransform}
-                    ruleProvider={undefined}
                     renderDepth={this.props.renderDepth + 1}
                     ContainingCollectionDoc={this.props.ContainingCollectionDoc}
                     chromeCollapsed={true}>
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index e9118e3ab..f21f9ff46 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -211,7 +211,6 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
                 addDocument={this.addDocument}
                 CollectionView={undefined}
                 ScreenToLocalTransform={this.props.ScreenToLocalTransform}
-                ruleProvider={undefined}
                 renderDepth={this.props.renderDepth + 1}
                 ContainingCollectionDoc={this.props.ContainingCollectionDoc}
                 chromeCollapsed={true}>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 62467ce4d..d6829937e 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -650,7 +650,6 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
                 addDocument={this.addDocument}
                 CollectionView={undefined}
                 ScreenToLocalTransform={this.overlayTransform}
-                ruleProvider={undefined}
                 renderDepth={this.props.renderDepth + 1}
                 ContainingCollectionDoc={this.props.ContainingCollectionView?.props.Document}
                 chromeCollapsed={true}>
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index c02042380..92dc0f3d2 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -175,7 +175,6 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
                     fitToBox={StrCast(this.targetDoc.type).indexOf(DocumentType.COL) !== -1}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
-                    ruleProvider={undefined}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
                     PanelWidth={() => this.props.PanelWidth() - 20}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 30fcc7908..21a4560cf 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -161,7 +161,6 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
-                    ruleProvider={undefined}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
                     getTransform={Transform.Identity}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index ea2fc917f..b1eaeaa0a 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -38,7 +38,6 @@ export default class MobileInterface extends React.Component {
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
                 removeDocument={undefined}
-                ruleProvider={undefined}
                 onClick={undefined}
                 ScreenToLocalTransform={Transform.Identity}
                 ContentScaling={returnOne}
-- 
cgit v1.2.3-70-g09d2


From c23d9ba83c87511a626bfe8d1da5dd981e311262 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 23 Jan 2020 18:24:38 -0500
Subject: got rid of extension docs.  changed layout-specific keys to start
 with "_" which flags them to be written to the current layout document

---
 .../apis/google_docs/GooglePhotosClientUtils.ts    |   2 +-
 src/client/apis/youtube/YoutubeBox.tsx             |   2 +-
 src/client/documents/Documents.ts                  | 109 +++++++++++----------
 src/client/util/DictationManager.ts                |   6 +-
 src/client/util/DragManager.ts                     |   2 +-
 src/client/util/DropConverter.ts                   |   3 +-
 .../util/Import & Export/DirectoryImportBox.tsx    |   4 +-
 src/client/util/RichTextRules.ts                   |   4 +-
 src/client/util/RichTextSchema.tsx                 |   4 +-
 src/client/views/DocComponent.tsx                  |   9 +-
 src/client/views/DocumentDecorations.tsx           |  40 ++++----
 src/client/views/MainView.tsx                      |  10 +-
 src/client/views/MetadataEntryMenu.tsx             |   4 +-
 src/client/views/PreviewCursor.tsx                 |   4 +-
 src/client/views/TemplateMenu.tsx                  |   4 +-
 .../views/collections/CollectionDockingView.tsx    |  20 ++--
 .../views/collections/CollectionLinearView.tsx     |  12 +--
 .../collections/CollectionMasonryViewFieldRow.tsx  |   4 +-
 .../views/collections/CollectionPivotView.tsx      |  23 ++---
 .../views/collections/CollectionSchemaView.tsx     |   2 +-
 .../views/collections/CollectionStackingView.tsx   |  34 +++----
 .../CollectionStackingViewFieldColumn.tsx          |   8 +-
 .../views/collections/CollectionStaffView.tsx      |   4 -
 src/client/views/collections/CollectionSubView.tsx |  23 ++---
 .../views/collections/CollectionTreeView.tsx       |  36 +++----
 src/client/views/collections/CollectionView.tsx    |  50 ++++------
 .../views/collections/CollectionViewChromes.tsx    |  22 ++---
 .../views/collections/ParentDocumentSelector.tsx   |  18 ++--
 .../CollectionFreeFormLayoutEngines.tsx            |   8 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 100 ++++++++++---------
 .../collections/collectionFreeForm/MarqueeView.tsx |  54 +++++-----
 src/client/views/linking/LinkEditor.tsx            |   2 +-
 src/client/views/linking/LinkFollowBox.tsx         |  48 ++++-----
 src/client/views/linking/LinkMenuGroup.tsx         |   2 +-
 src/client/views/nodes/AudioBox.tsx                | 101 +++++++++----------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  12 +--
 .../views/nodes/ContentFittingDocumentView.tsx     |  10 +-
 src/client/views/nodes/DocumentView.tsx            | 106 ++++++++++----------
 src/client/views/nodes/FormattedTextBox.tsx        |  26 +++--
 src/client/views/nodes/FormattedTextBoxComment.tsx |   2 +-
 src/client/views/nodes/IconBox.tsx                 |   2 +-
 src/client/views/nodes/ImageBox.tsx                |  89 ++++++++---------
 src/client/views/nodes/KeyValueBox.tsx             |   8 +-
 src/client/views/nodes/KeyValuePair.tsx            |   2 +-
 src/client/views/nodes/PDFBox.tsx                  |  22 ++---
 src/client/views/nodes/PresBox.tsx                 |   4 +-
 src/client/views/nodes/VideoBox.tsx                |  32 +++---
 src/client/views/nodes/WebBox.tsx                  |  14 +--
 src/client/views/pdf/Annotation.tsx                |  10 +-
 src/client/views/pdf/PDFViewer.tsx                 |  67 +++++++------
 .../views/presentationview/PresElementBox.tsx      |  10 +-
 src/client/views/search/SearchBox.tsx              |  16 +--
 src/client/views/search/SearchItem.tsx             |  10 +-
 src/mobile/ImageUpload.tsx                         |   2 +-
 src/new_fields/Doc.ts                              |  51 +++-------
 src/new_fields/RichTextUtils.ts                    |   2 +-
 src/new_fields/documentSchemas.ts                  |  13 +--
 src/new_fields/util.ts                             |  34 ++++++-
 src/scraping/buxton/scraper.py                     |  14 +--
 .../authentication/models/current_user_utils.ts    |  70 ++++++-------
 60 files changed, 696 insertions(+), 710 deletions(-)

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

diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 966d8053a..21cd20e14 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -136,7 +136,7 @@ export namespace GooglePhotos {
                 document.contentSize = upload.contentSize;
                 return document;
             });
-            const options = { width: 500, height: 500 };
+            const options = { _width: 500, _height: 500 };
             return constructor(children, options);
         };
 
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index d654e2530..c940bba43 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -337,7 +337,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
         const newVideoX = NumCast(this.props.Document.x);
         const newVideoY = NumCast(this.props.Document.y) + NumCast(this.props.Document.height);
 
-        addFunction(Docs.Create.VideoDocument(embeddedUrl, { title: filteredTitle, width: 400, height: 315, x: newVideoX, y: newVideoY }));
+        addFunction(Docs.Create.VideoDocument(embeddedUrl, { title: filteredTitle, _width: 400, _height: 315, x: newVideoX, y: newVideoY }));
         this.videoClicked = true;
     }
 
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6132f787b..c665a7fd3 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -56,21 +56,28 @@ const requestImageSize = require('../util/request-image-size');
 const path = require('path');
 
 export interface DocumentOptions {
+    _panX?: number;
+    _panY?: number;
+    _width?: number;
+    _height?: number;
+    _nativeWidth?: number;
+    _nativeHeight?: number;
+    _fitWidth?: boolean;
+    _fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents
+    _LODdisable?: boolean;
+    _dropAction?: dropActionType;
+    _chromeStatus?: string;
+    _viewType?: number;
+    _gridGap?: number; // gap between items in masonry view
+    _xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts
+    _yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts
     x?: number;
     y?: number;
     z?: number;
     type?: string;
-    width?: number;
-    height?: number;
-    nativeWidth?: number;
-    nativeHeight?: number;
     title?: string;
-    panX?: number;
-    panY?: number;
     page?: number;
     scale?: number;
-    fitWidth?: boolean;
-    fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents
     isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
     forceActive?: boolean;
     preventTreeViewOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expande/collapse state to be independent of other views of the same document in the tree view
@@ -79,16 +86,12 @@ export interface DocumentOptions {
     isTemplateForField?: string; // the field key for which the containing document is a rendering template
     isTemplateDoc?: boolean;
     templates?: List<string>;
-    viewType?: number;
     backgroundColor?: string | ScriptField;
     ignoreClick?: boolean;
     lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged
     lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed
     opacity?: number;
     defaultBackgroundColor?: string;
-    dropAction?: dropActionType;
-    chromeStatus?: string;
-    LODdisable?: boolean;
     columnWidth?: number;
     fontSize?: number;
     curPage?: number;
@@ -114,9 +117,6 @@ export interface DocumentOptions {
     onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
     clipboard?: Doc; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
     icon?: string;
-    gridGap?: number; // gap between items in masonry view
-    xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts
-    yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts
     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.
@@ -158,19 +158,19 @@ export namespace Docs {
         const TemplateMap: TemplateMap = new Map([
             [DocumentType.TEXT, {
                 layout: { view: FormattedTextBox, dataField: data },
-                options: { height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" }
+                options: { _height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" }
             }],
             [DocumentType.HIST, {
                 layout: { view: HistogramBox, dataField: data },
-                options: { height: 300, backgroundColor: "black" }
+                options: { _height: 300, backgroundColor: "black" }
             }],
             [DocumentType.QUERY, {
                 layout: { view: QueryBox, dataField: data },
-                options: { width: 400 }
+                options: { _width: 400 }
             }],
             [DocumentType.COLOR, {
                 layout: { view: ColorBox, dataField: data },
-                options: { nativeWidth: 220, nativeHeight: 300 }
+                options: { _nativeWidth: 220, _nativeHeight: 300 }
             }],
             [DocumentType.IMG, {
                 layout: { view: ImageBox, dataField: data },
@@ -178,19 +178,19 @@ export namespace Docs {
             }],
             [DocumentType.WEB, {
                 layout: { view: WebBox, dataField: data },
-                options: { height: 300 }
+                options: { _height: 300 }
             }],
             [DocumentType.COL, {
                 layout: { view: CollectionView, dataField: data },
-                options: { panX: 0, panY: 0, scale: 1, width: 500, height: 500 }
+                options: { _panX: 0, _panY: 0, scale: 1, _width: 500, _height: 500 }
             }],
             [DocumentType.KVP, {
                 layout: { view: KeyValueBox, dataField: data },
-                options: { height: 150 }
+                options: { _height: 150 }
             }],
             [DocumentType.DOCUMENT, {
                 layout: { view: DocumentBox, dataField: data },
-                options: { height: 250 }
+                options: { _height: 250 }
             }],
             [DocumentType.VID, {
                 layout: { view: VideoBox, dataField: data },
@@ -198,7 +198,7 @@ export namespace Docs {
             }],
             [DocumentType.AUDIO, {
                 layout: { view: AudioBox, dataField: data },
-                options: { height: 35, backgroundColor: "lightGray" }
+                options: { _height: 35, backgroundColor: "lightGray" }
             }],
             [DocumentType.PDF, {
                 layout: { view: PDFBox, dataField: data },
@@ -206,11 +206,11 @@ export namespace Docs {
             }],
             [DocumentType.ICON, {
                 layout: { view: IconBox, dataField: data },
-                options: { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) },
+                options: { _width: Number(MINIMIZED_ICON_SIZE), _height: Number(MINIMIZED_ICON_SIZE) },
             }],
             [DocumentType.IMPORT, {
                 layout: { view: DirectoryImportBox, dataField: data },
-                options: { height: 150 }
+                options: { _height: 150 }
             }],
             [DocumentType.LINKDOC, {
                 data: new List<Doc>(),
@@ -228,7 +228,7 @@ export namespace Docs {
             }],
             [DocumentType.FONTICON, {
                 layout: { view: FontIconBox, dataField: data },
-                options: { width: 40, height: 40, borderRounding: "100%" },
+                options: { _width: 40, _height: 40, borderRounding: "100%" },
             }],
             [DocumentType.LINKFOLLOW, {
                 layout: { view: LinkFollowBox, dataField: data }
@@ -243,7 +243,7 @@ export namespace Docs {
         ]);
 
         // All document prototypes are initialized with at least these values
-        const defaultOptions: DocumentOptions = { x: 0, y: 0, width: 300 };
+        const defaultOptions: DocumentOptions = { x: 0, y: 0, _width: 300 };
         const suffix = "Proto";
 
         /**
@@ -321,7 +321,8 @@ export namespace Docs {
             // whatever options pertain to this specific prototype
             const options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) };
             options.layout = layout.view.LayoutString(layout.dataField);
-            return Doc.assign(new Doc(prototypeId, true), { ...options });
+            let doc = Doc.assign(new Doc(prototypeId, true), { layoutKey: "layout", ...options });
+            return doc;
         }
 
     }
@@ -332,7 +333,7 @@ export namespace Docs {
      */
     export namespace Create {
 
-        const delegateKeys = ["x", "y", "width", "height", "panX", "panY", "nativeWidth", "nativeHeight", "dropAction", "annotationOn", "forceActive", "fitWidth", "diableLOD"];
+        const delegateKeys = ["x", "y", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "_dropAction", "_annotationOn", "_chromeStatus", "_forceActive", "_fitWidth", "_LODdisable"];
 
         /**
          * This function receives the relevant document prototype and uses
@@ -402,11 +403,11 @@ export namespace Docs {
             requestImageSize(target)
                 .then((size: any) => {
                     const aspect = size.height / size.width;
-                    if (!inst.nativeWidth) {
-                        inst.nativeWidth = size.width;
+                    if (!inst._nativeWidth) {
+                        inst._nativeWidth = size.width;
                     }
-                    inst.nativeHeight = NumCast(inst.nativeWidth) * aspect;
-                    inst.height = NumCast(inst.width) * aspect;
+                    inst._nativeHeight = NumCast(inst._nativeWidth) * aspect;
+                    inst._height = NumCast(inst._width) * aspect;
                 })
                 .catch((err: any) => console.log(err));
             // }
@@ -465,7 +466,7 @@ export namespace Docs {
             const ctlog = await Gateway.Instance.GetSchema(url, schemaName);
             if (ctlog && ctlog.schemas) {
                 const schema = ctlog.schemas[0];
-                const schemaDoc = Docs.Create.TreeDocument([], { ...options, nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: schema.displayName! });
+                const schemaDoc = Docs.Create.TreeDocument([], { ...options, _nativeWidth: undefined, _nativeHeight: undefined, _width: 150, _height: 100, title: schema.displayName! });
                 const schemaDocuments = Cast(schemaDoc.data, listSpec(Doc), []);
                 if (!schemaDocuments) {
                     return;
@@ -482,13 +483,13 @@ export namespace Docs {
                                 new AttributeTransformationModel(atmod, AggregateFunction.None),
                                 new AttributeTransformationModel(atmod, AggregateFunction.Count),
                                 new AttributeTransformationModel(atmod, AggregateFunction.Count));
-                            docs.push(Docs.Create.HistogramDocument(histoOp, { ...columnOptions, width: 200, height: 200, title: attr.displayName! }));
+                            docs.push(Docs.Create.HistogramDocument(histoOp, { ...columnOptions, _width: 200, _height: 200, title: attr.displayName! }));
                         }
                     }));
                 });
                 return schemaDoc;
             }
-            return Docs.Create.TreeDocument([], { width: 50, height: 100, title: schemaName });
+            return Docs.Create.TreeDocument([], { _width: 50, _height: 100, title: schemaName });
         }
 
         export function WebDocument(url: string, options: DocumentOptions = {}) {
@@ -508,31 +509,31 @@ export namespace Docs {
         }
 
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Freeform }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Freeform }, id);
         }
 
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Linear }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Linear }, id);
         }
 
         export function SchemaDocument(schemaColumns: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List(schemaColumns), ...options, _viewType: CollectionViewType.Schema });
         }
 
         export function TreeDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Tree });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Tree });
         }
 
         export function StackingDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Stacking });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Stacking });
         }
 
         export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Multicolumn });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Multicolumn });
         }
 
         export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Masonry });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Masonry });
         }
 
         export function ButtonDocument(options?: DocumentOptions) {
@@ -553,7 +554,7 @@ export namespace Docs {
         }
 
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
-            const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
+            const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
             Doc.GetProto(inst).data = new List<Doc>(documents);
             return inst;
         }
@@ -714,24 +715,24 @@ export namespace Docs {
             let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
             if (type.indexOf("image") !== -1) {
                 ctor = Docs.Create.ImageDocument;
-                if (!options.width) options.width = 300;
+                if (!options._width) options._width = 300;
             }
             if (type.indexOf("video") !== -1) {
                 ctor = Docs.Create.VideoDocument;
-                if (!options.width) options.width = 600;
-                if (!options.height) options.height = options.width * 2 / 3;
+                if (!options._width) options._width = 600;
+                if (!options._height) options._height = options._width * 2 / 3;
             }
             if (type.indexOf("audio") !== -1) {
                 ctor = Docs.Create.AudioDocument;
             }
             if (type.indexOf("pdf") !== -1) {
                 ctor = Docs.Create.PdfDocument;
-                if (!options.width) options.width = 400;
-                if (!options.height) options.height = options.width * 1200 / 927;
+                if (!options._width) options._width = 400;
+                if (!options._height) options._height = options._width * 1200 / 927;
             }
             if (type.indexOf("excel") !== -1) {
                 ctor = Docs.Create.DBDocument;
-                options.dropAction = "copy";
+                options._dropAction = "copy";
             }
             if (type.indexOf("html") !== -1) {
                 if (path.includes(window.location.hostname)) {
@@ -742,15 +743,15 @@ export namespace Docs {
                             const alias = Doc.MakeAlias(field);
                             alias.x = options.x || 0;
                             alias.y = options.y || 0;
-                            alias.width = options.width || 300;
-                            alias.height = options.height || options.width || 300;
+                            alias._width = options._width || 300;
+                            alias._height = options._height || options._width || 300;
                             return alias;
                         }
                         return undefined;
                     });
                 }
                 ctor = Docs.Create.WebDocument;
-                options = { height: options.width, ...options, title: path, nativeWidth: undefined };
+                options = { _height: options._width, ...options, title: path, _nativeWidth: undefined };
             }
             return ctor ? ctor(path, options) : undefined;
         }
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 3d8f2d234..a99e3bc4f 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -325,14 +325,14 @@ export namespace DictationManager {
 
             ["open fields", {
                 action: (target: DocumentView) => {
-                    const kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 });
+                    const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 });
                     target.props.addDocTab(kvp, target.props.DataDoc, "onRight");
                 }
             }],
 
             ["new outline", {
                 action: (target: DocumentView) => {
-                    const newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" });
+                    const newBox = Docs.Create.TextDocument({ _width: 400, _height: 200, title: "My Outline" });
                     newBox.autoHeight = true;
                     const proto = newBox.proto!;
                     const prompt = "Press alt + r to start dictating here...";
@@ -379,7 +379,7 @@ export namespace DictationManager {
                 expression: /view as (freeform|stacking|masonry|schema|tree)/g,
                 action: (target: DocumentView, matches: RegExpExecArray) => {
                     const mode = CollectionViewType.valueOf(matches[1]);
-                    mode && (target.props.Document.viewType = mode);
+                    mode && (target.props.Document._viewType = mode);
                 },
                 restrictTo: [DocumentType.COL]
             }
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index df2f5fe3c..601a7e856 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -205,7 +205,7 @@ export namespace DragManager {
     // drag a button template and drop a new button 
     export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
         const finishDrag = (e: DragCompleteEvent) => {
-            const bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title });
+            const bd = Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: title });
             bd.onClick = ScriptField.MakeScript(script);
             params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc)));
             initialize && initialize(bd);
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 66caf52cb..9c068d2d7 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,5 +1,4 @@
 import { DragManager } from "./DragManager";
-import { CollectionViewType } from "../views/collections/CollectionView";
 import { Doc, DocListCast } from "../../new_fields/Doc";
 import { DocumentType } from "../documents/DocumentTypes";
 import { ObjectField } from "../../new_fields/ObjectField";
@@ -34,7 +33,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
             } else {
                 layoutDoc.isTemplateDoc = (layoutDoc.type === DocumentType.TEXT || layoutDoc.layout instanceof Doc) && !data.userDropAction;
             }
-            dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" });
+            dbox = Docs.Create.FontIconDocument({ _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" });
             dbox.dragFactory = layoutDoc;
             dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
             dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 5b5bffd8c..c7c94abed 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -145,8 +145,8 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
         const offset: number = this.persistent ? (height === 0 ? 0 : height + 30) : 0;
         const options: DocumentOptions = {
             title: `Import of ${directory}`,
-            width: 1105,
-            height: 500,
+            _width: 1105,
+            _height: 500,
             x: NumCast(doc.x),
             y: NumCast(doc.y) + offset
         };
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 0ee9481f0..fb22e0d18 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -79,7 +79,7 @@ export const inpRules = {
                 const value = state.doc.textBetween(start, end);
                 if (value) {
                     DocServer.GetRefField(value).then(docx => {
-                        const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500, }, value);
+                        const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500, }, value);
                         DocUtils.Publish(target, value, returnFalse, returnFalse);
                         DocUtils.MakeLink({ doc: (schema as any).Document }, { doc: target }, "portal link", "");
                     });
@@ -196,7 +196,7 @@ export const inpRules = {
         new InputRule(
             new RegExp(/%#$/),
             (state, match, start, end) => {
-                const target = Docs.Create.TextDocument({ width: 75, height: 35, backgroundColor: "yellow", annotationOn: FormattedTextBox.FocusedBox!.dataDoc, autoHeight: true, fontSize: 9, title: "inline comment" });
+                const target = Docs.Create.TextDocument({ _width: 75, _height: 35, backgroundColor: "yellow", annotationOn: FormattedTextBox.FocusedBox!.dataDoc, autoHeight: true, fontSize: 9, title: "inline comment" });
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const newNode = schema.nodes.dashComment.create({ docid: target[Id] });
                 const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: target[Id], float: "right" });
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index fe7ab31a8..d64098e5f 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -703,7 +703,7 @@ export class DashDocView {
         const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
         return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
     }
-    contentScaling = () => NumCast(this._dashDoc!.nativeWidth) > 0 && !this._dashDoc!.ignoreAspect ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!.nativeWidth) : 1;
+    contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 && !this._dashDoc!.ignoreAspect ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1;
     outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document);  // ideally, this would scroll to show the focus target
     constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
         this._textBox = tbox;
@@ -758,7 +758,7 @@ export class DashDocView {
                 ReactDOM.render(<DocumentView
                     Document={dashDoc}
                     LibraryPath={tbox.props.LibraryPath}
-                    fitToBox={BoolCast(dashDoc.fitToBox)}
+                    fitToBox={BoolCast(dashDoc._fitToBox)}
                     addDocument={returnFalse}
                     removeDocument={removeDoc}
                     ScreenToLocalTransform={this.getDocTransform}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 1ffbd80ca..6d185ff87 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -35,7 +35,6 @@ export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCt
         @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.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; }
-        @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
         active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
     }
     return Component;
@@ -59,14 +58,12 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
         @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.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; }
-        @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
-        @computed get extensionDocSync() { return Doc.fieldExtensionDocSync(this.dataDoc, this.props.fieldKey); }
-        @computed get annotationsKey() { return "annotations"; }
+        @computed get annotationsKey() { return this.props.fieldKey+"-annotations"; }
 
         @action.bound
         removeDocument(doc: Doc): boolean {
             Doc.GetProto(doc).annotationOn = undefined;
-            const value = this.extensionDoc && Cast(this.extensionDoc[this.annotationsKey], listSpec(Doc), []);
+            const value = Cast(this.dataDoc[this.props.fieldKey+"-"+this.annotationsKey], listSpec(Doc), []);
             const index = value ? Doc.IndexOf(doc, value.map(d => d as Doc), true) : -1;
             return index !== -1 && value && value.splice(index, 1) ? true : false;
         }
@@ -79,7 +76,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.annotationsKey, doc) ? true : false;
+            return Doc.AddDocToList(this.dataDoc, this.annotationsKey, doc) ? true : false;
         }
 
         whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e6a119d1f..095b9b17c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -317,8 +317,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
         IconBox.AutomaticTitle(iconDoc);
         //iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined;
-        iconDoc.width = Number(MINIMIZED_ICON_SIZE);
-        iconDoc.height = Number(MINIMIZED_ICON_SIZE);
+        iconDoc._width = Number(MINIMIZED_ICON_SIZE);
+        iconDoc._height = Number(MINIMIZED_ICON_SIZE);
         iconDoc.x = NumCast(doc.x);
         iconDoc.y = NumCast(doc.y) - 24;
         iconDoc.maximizedDocs = new List<Doc>(selected.map(s => s.props.Document));
@@ -454,10 +454,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
                 const doc = PositionDocument(element.props.Document);
                 const layoutDoc = PositionDocument(Doc.Layout(element.props.Document));
-                let nwidth = layoutDoc.nativeWidth || 0;
-                let nheight = layoutDoc.nativeHeight || 0;
-                const width = (layoutDoc.width || 0);
-                const height = (layoutDoc.height || (nheight / nwidth * width));
+                let nwidth = layoutDoc._nativeWidth || 0;
+                let nheight = layoutDoc._nativeHeight || 0;
+                const width = (layoutDoc._width || 0);
+                const height = (layoutDoc._height || (nheight / nwidth * width));
                 const scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling();
                 const actualdW = Math.max(width + (dW * scale), 20);
                 const actualdH = Math.max(height + (dH * scale), 20);
@@ -466,33 +466,33 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 const fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight);
                 if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) {
                     layoutDoc.ignoreAspect = false;
-                    layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
-                    layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
+                    layoutDoc._nativeWidth = nwidth = layoutDoc._width || 0;
+                    layoutDoc._nativeHeight = nheight = layoutDoc._height || 0;
                 }
                 if (fixedAspect && (!nwidth || !nheight)) {
-                    layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
-                    layoutDoc.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) {
-                            layoutDoc.nativeWidth = actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0);
+                            layoutDoc._nativeWidth = actualdW / (layoutDoc._width || 1) * (layoutDoc._nativeWidth || 0);
                         }
-                        layoutDoc.width = actualdW;
-                        if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width;
-                        else layoutDoc.height = actualdH;
+                        layoutDoc._width = actualdW;
+                        if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width;
+                        else layoutDoc._height = actualdH;
                     }
                     else {
                         if (!fixedAspect) {
-                            layoutDoc.nativeHeight = actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0);
+                            layoutDoc._nativeHeight = actualdH / (layoutDoc._height || 1) * (doc._nativeHeight || 0);
                         }
-                        layoutDoc.height = actualdH;
-                        if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height;
-                        else layoutDoc.width = actualdW;
+                        layoutDoc._height = actualdH;
+                        if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height;
+                        else layoutDoc._width = actualdW;
                     }
                 } else {
-                    dW && (layoutDoc.width = actualdW);
-                    dH && (layoutDoc.height = actualdH);
+                    dW && (layoutDoc._width = actualdW);
+                    dH && (layoutDoc._height = actualdH);
                     dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false);
                 }
             }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cd4e9a1a6..2297c2bb3 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -176,9 +176,9 @@ export class MainView extends React.Component {
         } else {
             if (received && this._urlState.sharing) {
                 reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized,
-                    initialized => initialized && received && DocServer.GetRefField(received).then(field => {
-                        if (field instanceof Doc && field.viewType !== CollectionViewType.Docking) {
-                            CollectionDockingView.AddRightSplit(field, undefined);
+                    initialized => initialized && received && DocServer.GetRefField(received).then(docField => {
+                        if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) {
+                            CollectionDockingView.AddRightSplit(docField, undefined);
                         }
                     }),
                 );
@@ -199,8 +199,8 @@ export class MainView extends React.Component {
         const freeformOptions: DocumentOptions = {
             x: 0,
             y: 400,
-            width: this._panelWidth * .7,
-            height: this._panelHeight,
+            _width: this._panelWidth * .7,
+            _height: this._panelHeight,
             title: "Collection " + workspaceCount,
             backgroundColor: "white"
         };
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index 243cdb8f6..a48e8f508 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -178,11 +178,11 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
         }
         docSource = docSource as Doc[] | Doc;
         if (docSource instanceof Doc) {
-            if (docSource.viewType === undefined) {
+            if (docSource._viewType === undefined) {
                 return (null);
             }
         } else if (Array.isArray(docSource)) {
-            if (!docSource.every(doc => doc.viewType !== undefined)) {
+            if (!docSource.every(doc => doc._viewType !== undefined)) {
                 return null;
             }
         }
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 9706d0f99..831d9ca0b 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -31,8 +31,8 @@ export class PreviewCursor extends React.Component<{}> {
                 if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) {
                     const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/");
                     return PreviewCursor._addDocument(Docs.Create.VideoDocument(url, {
-                        title: url, width: 400, height: 315,
-                        nativeWidth: 600, nativeHeight: 472.5,
+                        title: url, _width: 400, _height: 315,
+                        _nativeWidth: 600, _nativeHeight: 472.5,
                         x: newPoint[0], y: newPoint[1]
                     }));
                 }
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index d953d3bab..d6cc394d8 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -104,7 +104,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     toggleChrome = (): void => {
         this.props.docs.map(dv => {
             const layout = Doc.Layout(dv.Document);
-            layout.chromeStatus = (layout.chromeStatus !== "disabled" ? "disabled" : "enabled");
+            layout._chromeStatus = (layout._chromeStatus !== "disabled" ? "disabled" : "enabled");
         });
     }
     onAliasButtonUp = (e: PointerEvent): void => {
@@ -150,7 +150,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         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={StrCast(this.props.docs[0].Document.layoutKey, "layout") !== "layout"} toggle={this.toggleCustom} />);
         templateMenu.push(<OtherToggle key={"narrative"} name={"Narrative"} checked={StrCast(this.props.docs[0].Document.layoutKey, "layout") === "layout_narrative"} toggle={this.toggleNarrative} />);
-        templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout.chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
+        templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
         return (
             <Flyout anchorPoint={anchorPoints.LEFT_TOP}
                 content={<ul className="template-list" ref={this._dragRef} style={{ display: "block" }}>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 125cd828f..75e43a875 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -235,7 +235,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             if (found) {
                 Doc.GetProto(found.props.Document).data = new List<Doc>([document]);
             } else {
-                const stackView = Docs.Create.FreeformDocument([document], { fitToBox: true, isDisplayPanel: true, title: "document viewer" });
+                const stackView = Docs.Create.FreeformDocument([document], { _fitToBox: true, isDisplayPanel: true, title: "document viewer" });
                 CollectionDockingView.AddRightSplit(stackView, undefined, []);
             }
         }
@@ -505,7 +505,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         stack.header.element[0].style.backgroundColor = DocServer.Control.isReadOnly() ? "#228540" : undefined;
         stack.header.element.on('mousedown', (e: any) => {
             if (e.target === stack.header.element[0] && e.button === 1) {
-                this.AddTab(stack, Docs.Create.FreeformDocument([], { width: this.props.PanelWidth(), height: this.props.PanelHeight(), title: "Untitled Collection" }), undefined);
+                this.AddTab(stack, Docs.Create.FreeformDocument([], { _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: "Untitled Collection" }), undefined);
             }
         });
 
@@ -665,19 +665,19 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
 
     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;
+    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.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;
+    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.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);
+            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.layoutDoc!.nativeHeight);
+                return this._panelHeight / NumCast(this.layoutDoc!._nativeHeight);
             }
         }
         const nativeH = this.nativeHeight();
@@ -745,7 +745,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             (<div className="collectionDockingView-content" ref={ref => this._mainCont = ref}
                 style={{
                     transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`,
-                    height: this.layoutDoc && this.layoutDoc.fitWidth ? undefined : "100%",
+                    height: this.layoutDoc && this.layoutDoc._fitWidth ? undefined : "100%",
                     width: this.widthpercent
                 }}>
                 {this.docView}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 8bf6b4202..67062ae41 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -38,8 +38,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
 
     componentDidMount() {
         // is there any reason this needs to exist? -syip.  yes, it handles autoHeight for stacking views (masonry isn't yet supported).
-        this._widthDisposer = reaction(() => NumCast(this.props.Document.height, 0) + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
-            () => this.props.Document.width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
+        this._widthDisposer = reaction(() => this.props.Document[HeightSym]() + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
+            () => this.props.Document._width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
             { fireImmediately: true }
         );
 
@@ -73,7 +73,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
 
     public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
 
-    dimension = () => NumCast(this.props.Document.height); // 2 * the padding
+    dimension = () => NumCast(this.props.Document._height); // 2 * the padding
     getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
         if (!ele.current) return Transform.Identity();
         const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
@@ -88,11 +88,11 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                     onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} />
                 <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
 
-                <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document.width, 25) }}>
+                <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document._width, 25) }}>
                     {this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => {
-                        const nested = pair.layout.viewType === CollectionViewType.Linear;
+                        const nested = pair.layout._viewType === CollectionViewType.Linear;
                         const dref = React.createRef<HTMLDivElement>();
-                        const nativeWidth = NumCast(pair.layout.nativeWidth, this.dimension());
+                        const nativeWidth = NumCast(pair.layout._nativeWidth, this.dimension());
                         const deltaSize = nativeWidth * .15 / 2;
                         return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
                             style={{
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 80752303c..57b091ce4 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -136,7 +136,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     addDocument = (value: string, shiftDown?: boolean) => {
         this._createAliasSelected = false;
         const key = StrCast(this.props.parent.props.Document.sectionFilter);
-        const newDoc = Docs.Create.TextDocument({ height: 18, width: 200, title: value });
+        const newDoc = Docs.Create.TextDocument({ _height: 18, _width: 200, title: value });
         newDoc[key] = this.getValue(this.props.heading);
         return this.props.parent.props.addDocument(newDoc);
     }
@@ -258,7 +258,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     @computed get contentLayout() {
         const rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap))));
         const style = this.props.parent; const collapsed = this._collapsed;
-        const chromeStatus = this.props.parent.props.Document.chromeStatus;
+        const chromeStatus = this.props.parent.props.Document._chromeStatus;
         const newEditableViewProps = {
             GetValue: () => "",
             SetValue: this.addDocument,
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
index 43a302af7..a44f990e1 100644
--- a/src/client/views/collections/CollectionPivotView.tsx
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -24,11 +24,11 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
         this._narrativeDisposer?.();
     }
     componentDidMount() {
-        this.props.Document.freeformLayoutEngine = "pivot";
-        if (!this.props.Document.facetCollection) {
-            const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", yMargin: 0, treeViewHideTitle: true });
+        this.props.Document._freeformLayoutEngine = "pivot";
+        if (!this.props.Document._facetCollection) {
+            const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true });
             facetCollection.target = this.props.Document;
-            this.props.Document.excludeFields = new List<string>(["facetCollection", "docFilter", "viewSpecScript"]);
+            this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilter", "viewSpecScript"]);
 
             const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
             const script = CompileScript(scriptText, {
@@ -54,15 +54,10 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
                         }
                     }
                 }), { fireImmediately: true });
-            this.props.Document.facetCollection = facetCollection;
-            this.props.Document.fitToBox = true;
+            this.props.Document._facetCollection = facetCollection;
+            this.props.Document._fitToBox = true;
         }
     }
-
-    @computed get fieldExtensionDoc() {
-        return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
-    }
-
     bodyPanelWidth = () => this.props.PanelWidth() - this._facetWidth;
     getTransform = () => this.props.ScreenToLocalTransform().translate(-200, 0);
 
@@ -76,7 +71,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
      * Responds to clicking the check box in the flyout menu
      */
     facetClick = (facetHeader: string) => {
-        const facetCollection = this.props.Document.facetCollection;
+        const facetCollection = this.props.Document._facetCollection;
         if (facetCollection instanceof Doc) {
             const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader);
             if (found !== -1) {
@@ -120,11 +115,11 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
     }
 
     render() {
-        const facetCollection = Cast(this.props.Document?.facetCollection, Doc, null);
+        const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
         const flyout = (
             <div className="collectionPivotView-flyout" style={{ width: `${this._facetWidth}` }}>
                 {this._allFacets.map(facet => <label className="collectionPivotView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
-                    <input type="checkbox" onChange={e => { }} checked={this.props.Document.facetCollection instanceof Doc && DocListCast(this.props.Document.facetCollection.data).some(d => {
+                    <input type="checkbox" onChange={e => { }} checked={this.props.Document._facetCollection instanceof Doc && DocListCast(this.props.Document._facetCollection.data).some(d => {
                         return d.title === facet;
                     })} />
                     <span className="checkmark" />
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 994c7b351..9ecb8dc43 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -477,7 +477,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
 
     @undoBatch
     createRow = () => {
-        const newDoc = Docs.Create.TextDocument({ title: "", width: 100, height: 30 });
+        const newDoc = Docs.Create.TextDocument({ title: "", _width: 100, _height: 30 });
         this.props.addDocument(newDoc);
     }
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ec22bb813..372db5fee 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -39,13 +39,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @observable _scroll = 0; // used to force the document decoration to update when scrolling
     @computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
     @computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
-    @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => !pair.layout.isMinimized).map(pair => pair.layout); }
-    @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
-    @computed get yMargin() { return Math.max(this.props.Document.showTitle && !this.props.Document.showTitleHover ? 30 : 0, NumCast(this.props.Document.yMargin, 2 * this.gridGap)); }
-    @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
+    @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout); }
+    @computed get xMargin() { return NumCast(this.props.Document._xMargin, 2 * this.gridGap); }
+    @computed get yMargin() { return Math.max(this.props.Document.showTitle && !this.props.Document.showTitleHover ? 30 : 0, NumCast(this.props.Document._yMargin, 2 * this.gridGap)); }
+    @computed get gridGap() { return NumCast(this.props.Document._gridGap, 10); }
     @computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); }
     @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
-    @computed get showAddAGroup() { return (this.sectionFilter && (this.props.Document.chromeStatus !== 'view-mode' && this.props.Document.chromeStatus !== 'disabled')); }
+    @computed get showAddAGroup() { return (this.sectionFilter && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); }
     @computed get columnWidth() {
         return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin,
             this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250));
@@ -55,7 +55,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     children(docs: Doc[]) {
         this._docXfs.length = 0;
         return docs.map((d, i) => {
-            const width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
+            const width = () => Math.min(d._nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
             const height = () => this.getDocHeight(d);
             const dref = React.createRef<HTMLDivElement>();
             const dxf = () => this.getDocTransform(d, dref.current!);
@@ -125,7 +125,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         },
             (hgt: number) => {
                 const doc = hgt === -1 ? undefined : this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
-                doc && hgt > 0 && (Doc.Layout(doc).height = hgt);
+                doc && hgt > 0 && (Doc.Layout(doc)._height = hgt);
             },
             { fireImmediately: true }
         );
@@ -187,16 +187,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     getDocHeight(d?: Doc) {
         if (!d) return 0;
         const layoutDoc = Doc.Layout(d);
-        const nw = NumCast(layoutDoc.nativeWidth);
-        const nh = NumCast(layoutDoc.nativeHeight);
+        const nw = NumCast(layoutDoc._nativeWidth);
+        const nh = NumCast(layoutDoc._nativeHeight);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
-        if (!layoutDoc.ignoreAspect && !layoutDoc.fitWidth && nw && nh) {
+        if (!layoutDoc.ignoreAspect && !layoutDoc._fitWidth && nw && nh) {
             const aspect = nw && nh ? nh / nw : 1;
-            if (!(d.nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
+            if (!(d._nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
             return wid * aspect;
         }
-        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]();
+        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) => {
@@ -241,7 +241,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                 const pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height());
                 if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
                     targInd = i;
-                    const axis = this.Document.viewType === CollectionViewType.Masonry ? 0 : 1;
+                    const axis = this.Document._viewType === CollectionViewType.Masonry ? 0 : 1;
                     plusOne = where[axis] > (pos[axis] + pos1[axis]) / 2 ? 1 : 0;
                 }
             });
@@ -359,7 +359,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     onToggle = (checked: Boolean) => {
-        this.props.Document.chromeStatus = checked ? "collapsed" : "view-mode";
+        this.props.Document._chromeStatus = checked ? "collapsed" : "view-mode";
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
@@ -408,10 +408,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                             style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
                             <EditableView {...editableViewProps} />
                         </div>}
-                    {this.props.Document.chromeStatus !== 'disabled' ? <Switch
+                    {this.props.Document._chromeStatus !== 'disabled' ? <Switch
                         onChange={this.onToggle}
                         onClick={this.onToggle}
-                        defaultChecked={this.props.Document.chromeStatus !== 'view-mode'}
+                        defaultChecked={this.props.Document._chromeStatus !== 'view-mode'}
                         checkedChildren="edit"
                         unCheckedChildren="view"
                     /> : null}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index f0dcadb5b..29dfb60c5 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -136,7 +136,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     @action
     addDocument = (value: string, shiftDown?: boolean) => {
         if (value === ":freeForm") {
-            return this.props.parent.props.addDocument(Docs.Create.FreeformDocument([], { width: 200, height: 200 }));
+            return this.props.parent.props.addDocument(Docs.Create.FreeformDocument([], { _width: 200, _height: 200 }));
         } else if (value.startsWith(":")) {
             const { Document, addDocument } = this.props.parent.props;
             const fieldKey = value.substring(1);
@@ -152,7 +152,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         }
         this._createAliasSelected = false;
         const key = StrCast(this.props.parent.props.Document.sectionFilter);
-        const newDoc = Docs.Create.TextDocument({ height: 18, width: 200, documentText: "@@@" + value, title: value, autoHeight: true });
+        const newDoc = Docs.Create.TextDocument({ _height: 18, _width: 200, documentText: "@@@" + value, title: value, autoHeight: true });
         newDoc[key] = this.getValue(this.props.heading);
         const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
         const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
@@ -310,7 +310,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 style={{
                     width: (style.columnWidth) /
                         ((uniqueHeadings.length +
-                            ((this.props.parent.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.Document.chromeStatus !== 'disabled') ? 1 : 0)) || 1)
+                            ((this.props.parent.props.Document._chromeStatus !== 'view-mode' && this.props.parent.props.Document._chromeStatus !== 'disabled') ? 1 : 0)) || 1)
                 }}>
                 <div className={"collectionStackingView-collapseBar" + (this.props.headingObject.collapsed === true ? " active" : "")} onClick={this.collapseSection}></div>
                 {/* the default bucket (no key value) has a tooltip that describes what it is.
@@ -350,7 +350,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 </div>
             </div> : (null);
         for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `;
-        const chromeStatus = this.props.parent.props.Document.chromeStatus;
+        const chromeStatus = this.props.parent.props.Document._chromeStatus;
         return (
             <div className="collectionStackingViewFieldColumn" key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, background: this._background }}
                 ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx
index 105061f46..8c7e113b2 100644
--- a/src/client/views/collections/CollectionStaffView.tsx
+++ b/src/client/views/collections/CollectionStaffView.tsx
@@ -23,10 +23,6 @@ export class CollectionStaffView extends CollectionSubView(doc => doc) {
         this.props.Document.staves = 5;
     }
 
-    @computed get fieldExtensionDoc() {
-        return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
-    }
-
     @computed get addStaffButton() {
         return <div onPointerDown={this.addStaff}>+</div>;
     }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 063b5c5df..db393918d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -82,7 +82,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         }
 
         @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateForField ? 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
@@ -90,14 +89,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         // 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() {
             const { annotationsKey, fieldKey } = this.props;
-            const { extensionDoc, dataDoc } = this;
             if (annotationsKey) {
-                if (extensionDoc) {
-                    return extensionDoc[annotationsKey];
-                }
-                return undefined;
+                return this.dataDoc[fieldKey + "-" + annotationsKey];
             }
-            return dataDoc[fieldKey];
+            return this.dataDoc[fieldKey];
         }
 
         get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
@@ -216,7 +211,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                         this.props.addDocument && this.props.addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
                     }
                 } else if (text) {
-                    this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument({ ...options, width: 100, height: 25, documentText: "@@@" + text }));
+                    this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument({ ...options, _width: 100, _height: 25, documentText: "@@@" + text }));
                 }
                 return;
             }
@@ -226,7 +221,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
                 if (img) {
                     const split = img.split("src=\"")[1].split("\"")[0];
-                    const doc = Docs.Create.ImageDocument(split, { ...options, width: 300 });
+                    const doc = Docs.Create.ImageDocument(split, { ...options, _width: 300 });
                     ImageUtils.ExtractExif(doc);
                     this.props.addDocument(doc);
                     return;
@@ -241,7 +236,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                             }
                         });
                     } else {
-                        const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", width: 300, height: 300, documentText: text });
+                        const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300, documentText: text });
                         this.props.addDocument(htmlDoc);
                     }
                     return;
@@ -249,12 +244,12 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             }
             if (text && text.indexOf("www.youtube.com/watch") !== -1) {
                 const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
-                this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, width: 400, height: 315, nativeWidth: 600, nativeHeight: 472.5 }));
+                this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, _width: 400, _height: 315, _nativeWidth: 600, _nativeHeight: 472.5 }));
                 return;
             }
             let matches: RegExpExecArray | null;
             if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
-                const newBox = Docs.Create.TextDocument({ ...options, width: 400, height: 200, title: "Awaiting title from Google Docs..." });
+                const newBox = Docs.Create.TextDocument({ ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." });
                 const proto = newBox.proto!;
                 const documentId = matches[2];
                 proto[GoogleRef] = documentId;
@@ -303,7 +298,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     const dropFileName = file ? file.name : "-empty-";
                     promises.push(Networking.PostFormDataToServer("/upload", formData).then(results => {
                         results.map(action(({ clientAccessPath }: any) => {
-                            const full = { ...options, width: 300, title: dropFileName };
+                            const full = { ...options, _width: 300, title: dropFileName };
                             const pathname = Utils.prepend(clientAccessPath);
                             Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
                                 doc && (Doc.GetProto(doc).fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""));
@@ -318,7 +313,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 Promise.all(promises).finally(() => { completed && completed(); batch.end(); });
             } else {
                 if (text && !text.includes("https://")) {
-                    this.props.addDocument(Docs.Create.TextDocument({ ...options, documentText: "@@@" + text, width: 400, height: 315 }));
+                    this.props.addDocument(Docs.Create.TextDocument({ ...options, documentText: "@@@" + text, _width: 400, _height: 315 }));
                 }
                 batch.end();
             }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ec348e74b..21f2bc326 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -179,7 +179,7 @@ class TreeView extends React.Component<TreeViewProps> {
         OnFillDown={undoBatch((value: string) => {
             Doc.SetInPlace(this.props.document, key, value, false);
             const layoutDoc = this.props.document.layoutCustom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.layoutCustom)) : undefined;
-            const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+            const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
             TreeView.loadId = doc[Id];
             return this.props.addDocument(doc);
         })}
@@ -210,7 +210,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" });
                 ContextMenu.Instance.addItem({ description: "Create New Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" });
             }
-            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { const kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
+            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { const kvp = Docs.Create.KVPDocument(this.props.document, { _width: 300, _height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
             ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" });
             ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15);
             e.stopPropagation();
@@ -256,21 +256,21 @@ class TreeView extends React.Component<TreeViewProps> {
     }
     docWidth = () => {
         const layoutDoc = Doc.Layout(this.props.document);
-        const aspect = NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth);
+        const aspect = NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth);
         if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20));
-        return NumCast(layoutDoc.nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20;
+        return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20;
     }
     docHeight = () => {
         const layoutDoc = Doc.Layout(this.props.document);
         const bounds = this.boundsOfCollectionDocument;
         return Math.min(this.MAX_EMBED_HEIGHT, (() => {
-            const aspect = NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth, 1);
+            const aspect = NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth, 1);
             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(layoutDoc.nativeHeight)) / NumCast(layoutDoc.nativeWidth,
-                    NumCast(this.props.containingCollection.height)))) :
-                NumCast(layoutDoc.height) ? NumCast(layoutDoc.height) : 50;
+            return layoutDoc._fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection._height) :
+                Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth,
+                    NumCast(this.props.containingCollection._height)))) :
+                NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50;
         })());
     }
 
@@ -547,7 +547,7 @@ class TreeView extends React.Component<TreeViewProps> {
             };
             const childLayout = Doc.Layout(pair.layout);
             const rowHeight = () => {
-                const aspect = NumCast(childLayout.nativeWidth, 0) / NumCast(childLayout.nativeHeight, 0);
+                const aspect = NumCast(childLayout._nativeWidth, 0) / NumCast(childLayout._nativeHeight, 0);
                 return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]();
             };
             return !(child instanceof Doc) ? (null) : <TreeView
@@ -637,10 +637,10 @@ export class CollectionTreeView extends CollectionSubView(Document) {
 
                 const detailedLayout = Docs.Create.StackingDocument([
                     ImageDocument(fallbackImg, { title: "activeHero" }),
-                    TreeDocument([], { title: "data", height: 100, onChildClick: ScriptField.MakeFunction(`containingCollection.resolvedDataDoc.activeHero = copyField(this.data)`, { containingCollection: Doc.name }) }),
+                    MulticolumnDocument([], { title: "data", _height: 100, onChildClick: ScriptField.MakeFunction(`containingCollection.resolvedDataDoc.activeHero = copyField(this.data)`, { containingCollection: Doc.name }) }),
                     TextDocument({ title: "short_description", autoHeight: true }),
-                    ...["year", "company", "degrees_of_freedom"].map(key => TextDocument({ title: key, height: 30 }))
-                ], { autoHeight: true, chromeStatus: "disabled", title: "detailed layout stack" });
+                    ...["year", "company", "degrees_of_freedom"].map(key => TextDocument({ title: key, _height: 30 }))
+                ], { autoHeight: true, _chromeStatus: "disabled", title: "detailed layout stack" });
                 detailedLayout.isTemplateDoc = makeTemplate(detailedLayout);
 
                 const cardLayout = ImageDocument(fallbackImg, { isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work?
@@ -650,7 +650,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
 
                 Document.childLayout = cardLayout;
                 Document.childDetailed = detailedLayout;
-                Document.viewType = CollectionViewType.Pivot;
+                Document._viewType = CollectionViewType.Pivot;
                 Document.pivotField = "company";
             }
         });
@@ -675,12 +675,12 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     }
 
     render() {
-        const dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
+        const dropAction = StrCast(this.props.Document._dropAction) as dropActionType;
         const addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, false);
         const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
         return !this.childDocs ? (null) : (
             <div className="collectionTreeView-dropTarget" id="body"
-                style={{ background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document.yMargin, 20)}px` }}
+                style={{ background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document._yMargin, 20)}px` }}
                 onContextMenu={this.onContextMenu}
                 onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
                 onDrop={this.onTreeDrop}
@@ -695,7 +695,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                     OnFillDown={undoBatch((value: string) => {
                         Doc.SetInPlace(this.dataDoc, "title", value, false);
                         const layoutDoc = this.props.Document.layoutCustom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.layoutCustom)) : undefined;
-                        const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+                        const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
                         TreeView.loadId = doc[Id];
                         Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, false, false, false);
                     })} />)}
@@ -734,7 +734,7 @@ Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey
 });
 
 Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) {
-    const docFilters = Cast(layoutDoc.docFilter, listSpec("string"), []);
+    const docFilters = Cast(layoutDoc._docFilter, listSpec("string"), []);
     for (let i = 0; i < docFilters.length; i += 3) {
         const [header, value, state] = docFilters.slice(i, i + 3);
         if (header === facetHeader && value === facetValue) {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 430aecef4..715094bf5 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -91,17 +91,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
     @observable private static _safeMode = false;
     public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
 
-    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.props.Document, this.props.fieldKey); }
-
     get collectionViewType(): CollectionViewType | undefined {
-        if (!this.extensionDoc) return CollectionViewType.Invalid;
-        NumCast(this.props.Document.viewType) && setTimeout(() => {
-            if (this.props.Document.viewType) {
-                this.extensionDoc!.viewType = NumCast(this.props.Document.viewType);
-            }
-            Doc.GetProto(this.props.Document).viewType = this.props.Document.viewType = undefined;
-        });
-        const viewField = NumCast(this.extensionDoc.viewType, Cast(this.props.Document.viewType, "number"));
+        const viewField = NumCast(this.props.Document._viewType);
         if (CollectionView._safeMode) {
             if (viewField === CollectionViewType.Freeform) {
                 return CollectionViewType.Tree;
@@ -110,15 +101,15 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 return CollectionViewType.Freeform;
             }
         }
-        return viewField === undefined ? CollectionViewType.Invalid : viewField;
+        return viewField;
     }
 
     componentDidMount = () => {
-        this._reactionDisposer = reaction(() => StrCast(this.props.Document.chromeStatus),
+        this._reactionDisposer = reaction(() => StrCast(this.props.Document._chromeStatus),
             () => {
                 // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
                 // chrome status may also be view-mode, in reference to stacking view's toggle mode. it is essentially disabled mode, but prevents the toggle button from showing up on the left sidebar.
-                const chromeStatus = this.props.Document.chromeStatus;
+                const chromeStatus = this.props.Document._chromeStatus;
                 if (chromeStatus && (chromeStatus === "disabled" || chromeStatus === "collapsed")) {
                     runInAction(() => this._collapsed = true);
                 }
@@ -128,7 +119,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     componentWillUnmount = () => this._reactionDisposer && this._reactionDisposer();
 
     // bcz: Argh?  What's the height of the collection chromes??  
-    chromeHeight = () => (this.props.Document.chromeStatus === "enabled" ? -60 : 0);
+    chromeHeight = () => (this.props.Document._chromeStatus === "enabled" ? -60 : 0);
 
     active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0;
 
@@ -138,8 +129,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     addDocument(doc: Doc): boolean {
         const targetDataDoc = Doc.GetProto(this.props.Document);
         Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
-        const extension = Doc.fieldExtensionDoc(targetDataDoc, this.props.fieldKey);  // set metadata about the field being rendered (ie, the set of documents) on an extension field for that field
-        extension && (extension.lastModified = new DateField(new Date(Date.now())));
+        targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
         Doc.GetProto(doc).lastOpened = new DateField;
         return true;
     }
@@ -192,19 +182,19 @@ export class CollectionView extends Touchable<FieldViewProps> {
             case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
             case CollectionViewType.Pivot: { return (<CollectionPivotView key="collview" {...props} />); }
             case CollectionViewType.Freeform:
-            default: { this.props.Document.freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); }
+            default: { this.props.Document._freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); }
         }
     }
 
     @action
     private collapse = (value: boolean) => {
         this._collapsed = value;
-        this.props.Document.chromeStatus = value ? "collapsed" : "enabled";
+        this.props.Document._chromeStatus = value ? "collapsed" : "enabled";
     }
 
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         // 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
-        const chrome = this.props.Document.chromeStatus === "disabled" || type === CollectionViewType.Docking ? (null) :
+        const chrome = this.props.Document._chromeStatus === "disabled" || type === CollectionViewType.Docking ? (null) :
             <CollectionViewBaseChrome CollectionView={this} key="chrome" type={type} collapse={this.collapse} />;
         return [chrome, this.SubViewHelper(type, renderProps)];
     }
@@ -214,24 +204,24 @@ export class CollectionView extends Touchable<FieldViewProps> {
         if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             const existingVm = ContextMenu.Instance.findByDescription("View Modes...");
             const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
-            subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; }, icon: "signature" });
+            subItems.push({ description: "Freeform", event: () => { this.props.Document._viewType = CollectionViewType.Freeform; }, icon: "signature" });
             if (CollectionView._safeMode) {
-                ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" });
+                ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document._viewType = CollectionViewType.Invalid, icon: "project-diagram" });
             }
-            subItems.push({ description: "Schema", event: () => this.props.Document.viewType = CollectionViewType.Schema, icon: "th-list" });
-            subItems.push({ description: "Treeview", event: () => this.props.Document.viewType = CollectionViewType.Tree, icon: "tree" });
-            subItems.push({ description: "Stacking", event: () => this.props.Document.viewType = CollectionViewType.Stacking, icon: "ellipsis-v" });
+            subItems.push({ description: "Schema", event: () => this.props.Document._viewType = CollectionViewType.Schema, icon: "th-list" });
+            subItems.push({ description: "Treeview", event: () => this.props.Document._viewType = CollectionViewType.Tree, icon: "tree" });
+            subItems.push({ description: "Stacking", event: () => this.props.Document._viewType = CollectionViewType.Stacking, icon: "ellipsis-v" });
             subItems.push({
                 description: "Stacking (AutoHeight)", event: () => {
-                    this.props.Document.viewType = CollectionViewType.Stacking;
+                    this.props.Document._viewType = CollectionViewType.Stacking;
                     this.props.Document.autoHeight = true;
                 }, icon: "ellipsis-v"
             });
-            subItems.push({ description: "Staff", event: () => this.props.Document.viewType = CollectionViewType.Staff, icon: "music" });
-            subItems.push({ description: "Multicolumn", event: () => this.props.Document.viewType = CollectionViewType.Multicolumn, icon: "columns" });
-            subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" });
-            subItems.push({ description: "Pivot", event: () => this.props.Document.viewType = CollectionViewType.Pivot, icon: "columns" });
-            switch (this.props.Document.viewType) {
+            subItems.push({ description: "Staff", event: () => this.props.Document._viewType = CollectionViewType.Staff, icon: "music" });
+            subItems.push({ description: "Multicolumn", event: () => this.props.Document._viewType = CollectionViewType.Multicolumn, icon: "columns" });
+            subItems.push({ description: "Masonry", event: () => this.props.Document._viewType = CollectionViewType.Masonry, icon: "columns" });
+            subItems.push({ description: "Pivot", event: () => this.props.Document._viewType = CollectionViewType.Pivot, icon: "columns" });
+            switch (this.props.Document._viewType) {
                 case CollectionViewType.Freeform: {
                     subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
                     break;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 42c563fe2..59d7a2671 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -55,9 +55,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         immediate: (draggedDocs: Doc[]) => Doc.GetProto(this.props.CollectionView.props.Document).data = new List<Doc>(draggedDocs.map((d: any) => Doc.MakeAlias(d)))
     };
     _viewCommand = {
-        title: "restore view", script: "this.target.panX = this.restoredPanX; this.target.panY = this.restoredPanY; this.target.scale = this.restoredScale;", params: ["target"],
-        immediate: (draggedDocs: Doc[]) => { this.props.CollectionView.props.Document.panX = 0; this.props.CollectionView.props.Document.panY = 0; this.props.CollectionView.props.Document.scale = 1; },
-        initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document.panX; button.restoredPanY = this.props.CollectionView.props.Document.panY; button.restoredScale = this.props.CollectionView.props.Document.scale; }
+        title: "restore view", script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;", params: ["target"],
+        immediate: (draggedDocs: Doc[]) => { this.props.CollectionView.props.Document._panX = 0; this.props.CollectionView.props.Document._panY = 0; this.props.CollectionView.props.Document.scale = 1; },
+        initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document._panX; button.restoredPanY = this.props.CollectionView.props.Document._panY; button.restoredScale = this.props.CollectionView.props.Document.scale; }
     };
     _freeform_commands = [this._contentCommand, this._templateCommand, this._narrativeCommand, this._viewCommand];
     _stacking_commands = [this._contentCommand, this._templateCommand];
@@ -132,7 +132,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         runInAction(() => {
             this.addKeyRestrictions(fields);
             // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
-            const chromeStatus = this.props.CollectionView.props.Document.chromeStatus;
+            const chromeStatus = this.props.CollectionView.props.Document._chromeStatus;
             if (chromeStatus) {
                 if (chromeStatus === "disabled") {
                     throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!");
@@ -149,7 +149,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     @undoBatch
     viewChanged = (e: React.ChangeEvent) => {
         //@ts-ignore
-        this.props.CollectionView.props.Document.viewType = parseInt(e.target.selectedOptions[0].value);
+        this.props.CollectionView.props.Document._viewType = parseInt(e.target.selectedOptions[0].value);
     }
 
     @action
@@ -223,7 +223,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
             `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
             "true";
 
-        const docFilter = Cast(this.props.CollectionView.props.Document.docFilter, listSpec("string"), []);
+        const docFilter = Cast(this.props.CollectionView.props.Document._docFilter, listSpec("string"), []);
         const docFilterText = Doc.MakeDocFilter(docFilter);
         const finalScript = docFilterText && !fullScript.startsWith("(())") ? `${fullScript} ${docFilterText ? "&&" : ""} (${docFilterText})` :
             docFilterText ? docFilterText : fullScript;
@@ -242,9 +242,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     @action
     toggleCollapse = () => {
-        this.props.CollectionView.props.Document.chromeStatus = this.props.CollectionView.props.Document.chromeStatus === "enabled" ? "collapsed" : "enabled";
+        this.props.CollectionView.props.Document._chromeStatus = this.props.CollectionView.props.Document._chromeStatus === "enabled" ? "collapsed" : "enabled";
         if (this.props.collapse) {
-            this.props.collapse(this.props.CollectionView.props.Document.chromeStatus !== "enabled");
+            this.props.collapse(this.props.CollectionView.props.Document._chromeStatus !== "enabled");
         }
     }
 
@@ -272,7 +272,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     @observable private pivotKeyDisplay = this.pivotKey;
     getPivotInput = () => {
-        if (StrCast(this.document.freeformLayoutEngine) !== "pivot") {
+        if (StrCast(this.document._freeformLayoutEngine) !== "pivot") {
             return (null);
         }
         return (<input className="collectionViewBaseChrome-viewSpecsInput"
@@ -383,7 +383,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     }
 
     render() {
-        const collapsed = this.props.CollectionView.props.Document.chromeStatus !== "enabled";
+        const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
         return (
             <div className="collectionViewChrome-cont" style={{ top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined }}>
                 <div className="collectionViewChrome">
@@ -402,7 +402,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                             className="collectionViewBaseChrome-viewPicker"
                             onPointerDown={stopPropagation}
                             onChange={this.viewChanged}
-                            value={NumCast(this.props.CollectionView.props.Document.viewType)}>
+                            value={NumCast(this.props.CollectionView.props.Document._viewType)}>
                             <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform View</option>
                             <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema View</option>
                             <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 1cc05f92c..18f1baf44 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -21,7 +21,13 @@ export const Flyout = higflyout.default;
 
 library.add(faEdit);
 
-type SelectorProps = { Document: Doc, Views: DocumentView[], Stack?: any, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void };
+type SelectorProps = {
+    Document: Doc,
+    Views: DocumentView[],
+    Stack?: any,
+    addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void
+};
+
 @observer
 export class SelectorContextMenu extends React.Component<SelectorProps> {
     @observable private _docs: { col: Doc, target: Doc }[] = [];
@@ -49,11 +55,11 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
     getOnClick({ col, target }: { col: Doc, target: Doc }) {
         return () => {
             col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
-            if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-                const newPanX = NumCast(target.x) + NumCast(target.width) / 2;
-                const newPanY = NumCast(target.y) + NumCast(target.height) / 2;
-                col.panX = newPanX;
-                col.panY = newPanY;
+            if (NumCast(col._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+                const newPanX = NumCast(target.x) + NumCast(target._width) / 2;
+                const newPanY = NumCast(target.y) + NumCast(target._height) / 2;
+                col._panX = newPanX;
+                col._panY = newPanY;
             }
             this.props.addDocTab(col, undefined, "inTab"); // bcz: dataDoc?
         };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 32097816d..8bf56338b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -79,10 +79,10 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
         for (const doc of val) {
             const layoutDoc = Doc.Layout(doc);
             let wid = pivotAxisWidth;
-            let hgt = layoutDoc.nativeWidth ? (NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
+            let hgt = layoutDoc._nativeWidth ? (NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
             if (hgt > pivotAxisWidth) {
                 hgt = pivotAxisWidth;
-                wid = layoutDoc.nativeHeight ? (NumCast(layoutDoc.nativeWidth) / NumCast(layoutDoc.nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
+                wid = layoutDoc.nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
             }
             docMap.set(doc, {
                 x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.length < numCols ? (numCols - val.length) * pivotAxisWidth / 2 : 0),
@@ -104,8 +104,8 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
             x: NumCast(pair.layout.x),
             y: NumCast(pair.layout.y),
             z: NumCast(pair.layout.z),
-            width: NumCast(pair.layout.width),
-            height: NumCast(pair.layout.height)
+            width: NumCast(pair.layout._width),
+            height: NumCast(pair.layout._height)
         };
         const pos = docMap.get(pair.layout) || defaultPosition;
         const data = poolData.get(pair.layout[Id]);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 65953dbdc..c89fb92a5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -41,15 +41,13 @@ import React = require("react");
 import { computedFn } from "mobx-utils";
 import { TraceMobx } from "../../../../new_fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { LinkManager } from "../../../util/LinkManager";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
-import CollectionPaletteVIew from "../../Palette";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
 export const panZoomSchema = createSchema({
-    panX: "number",
-    panY: "number",
+    _panX: "number",
+    _panY: "number",
     scale: "number",
     arrangeScript: ScriptField,
     arrangeInit: ScriptField,
@@ -81,16 +79,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
     @observable _clusterSets: (Doc[])[] = [];
 
-    @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
+    @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._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); }
-    @computed get nativeWidth() { return this.Document.fitToContent ? 0 : this.Document.nativeWidth || 0; }
-    @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; }
+    @computed get nativeWidth() { return this.Document._fitToContent ? 0 : this.Document._nativeWidth || 0; }
+    @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document._nativeHeight || 0; }
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
     private easing = () => this.props.Document.panTransformType === "Ease";
-    private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document.panX || 0;
-    private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document.panY || 0;
+    private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
+    private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document._panY || 0;
     private zoomScaling = () => (1 / this.parentScaling) * (this.fitToContent ?
         Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
         this.Document.scale || 1)
@@ -146,13 +144,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         const 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(layoutDoc._width)) {
+                            layoutDoc._width = 300;
                         }
-                        if (!NumCast(layoutDoc.height)) {
-                            const nw = NumCast(layoutDoc.nativeWidth);
-                            const nh = NumCast(layoutDoc.nativeHeight);
-                            layoutDoc.height = nw && nh ? nh / nw * NumCast(layoutDoc.width) : 300;
+                        if (!NumCast(layoutDoc._height)) {
+                            const nw = NumCast(layoutDoc._nativeWidth);
+                            const nh = NumCast(layoutDoc._nativeHeight);
+                            layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
                         }
                         this.bringToFront(d);
                     }));
@@ -182,8 +180,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const layoutDoc = Doc.Layout(cd);
             const cx = NumCast(cd.x) - this._clusterDistance;
             const cy = NumCast(cd.y) - this._clusterDistance;
-            const cw = NumCast(layoutDoc.width) + 2 * this._clusterDistance;
-            const ch = NumCast(layoutDoc.height) + 2 * this._clusterDistance;
+            const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance;
+            const 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);
@@ -353,7 +351,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
                 const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
-                const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, width: B.width, height: B.height });
+                const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, _width: B.width, _height: B.height });
                 this.addDocument(inkDoc);
                 e.stopPropagation();
                 break;
@@ -375,7 +373,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     }
                     return pass;
                 });
-                this.addDocument(Docs.Create.FreeformDocument(sel, { x: bounds.x, y: bounds.y, width: bWidth, height: bHeight, panX: 0, panY: 0 }));
+                this.addDocument(Docs.Create.FreeformDocument(sel, { x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
                 sel.forEach(d => this.props.removeDocument(d));
                 break;
 
@@ -397,11 +395,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // I think it makes sense for the marquee menu to go away when panned. -syip2
         MarqueeOptionsMenu.Instance.fadeOut(true);
 
-        let x = this.Document.panX || 0;
-        let y = this.Document.panY || 0;
+        let x = this.Document._panX || 0;
+        let y = this.Document._panY || 0;
         const docs = this.childLayoutPairs.map(pair => pair.layout);
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        if (!this.isAnnotationOverlay) {
+        if (!this.isAnnotationOverlay && docs.length) {
             PDFMenu.Instance.fadeOut(true);
             const minx = this.childDataProvider(docs[0]).x;//docs.length ? NumCast(docs[0].x) : 0;
             const miny = this.childDataProvider(docs[0]).y;//docs.length ? NumCast(docs[0].y) : 0;
@@ -591,8 +589,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const scale = this.getLocalTransform().inverse().Scale;
             const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
             const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY));
-            this.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
-            this.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
+            this.Document._panX = this.isAnnotationOverlay ? newPanX : panX;
+            this.Document._panY = this.isAnnotationOverlay ? newPanY : panY;
         }
     }
 
@@ -616,14 +614,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
         // TODO This technically isn't correct if type !== "doc", as 
         // currently nothing is done, but we should probably push a new state
-        if (state.type === "doc" && this.Document.panX !== undefined && this.Document.panY !== undefined) {
+        if (state.type === "doc" && this.Document._panX !== undefined && this.Document._panY !== undefined) {
             const init = state.initializers![this.Document[Id]];
             if (!init) {
-                state.initializers![this.Document[Id]] = { panX: this.Document.panX, panY: this.Document.panY };
+                state.initializers![this.Document[Id]] = { panX: this.Document._panX, panY: this.Document._panY };
                 HistoryUtil.pushState(state);
             } else if (init.panX !== this.Document.panX || init.panY !== this.Document.panY) {
-                init.panX = this.Document.panX;
-                init.panY = this.Document.panY;
+                init.panX = this.Document._panX;
+                init.panY = this.Document._panY;
                 HistoryUtil.pushState(state);
             }
         }
@@ -639,13 +637,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }
         } else {
             const layoutdoc = Doc.Layout(doc);
-            const newPanX = NumCast(doc.x) + NumCast(layoutdoc.width) / 2;
-            const newPanY = NumCast(doc.y) + NumCast(layoutdoc.height) / 2;
+            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);
 
-            const savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType };
+            const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
 
             if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
             Doc.BrushDoc(this.props.Document);
@@ -655,8 +653,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
             afterFocus && setTimeout(() => {
                 if (afterFocus && afterFocus()) {
-                    this.Document.panX = savedState.px;
-                    this.Document.panY = savedState.py;
+                    this.Document._panX = savedState.px;
+                    this.Document._panY = savedState.py;
                     this.Document.scale = savedState.s;
                     this.Document.panTransformType = savedState.pt;
                 }
@@ -666,7 +664,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     setScaleToZoom = (doc: Doc, scale: number = 0.5) => {
-        this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc.width), this.props.PanelHeight() / NumCast(doc.height));
+        this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
     }
 
     zoomToScale = (scale: number) => {
@@ -709,7 +707,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             return { ...result, transition: "transform 1s" };
         }
         const layoutDoc = Doc.Layout(params.doc);
-        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") };
+        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[]) => {
@@ -735,7 +733,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
     }
 
-    childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) { return this._layoutPoolData.get(doc[Id]); }.bind(this));
+    childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) {
+        if (!doc) {
+            console.log(doc);
+        }
+        return this._layoutPoolData.get(doc[Id]);
+    }.bind(this));
 
     doPivotLayout(poolData: ObservableMap<string, any>) {
         return computePivotLayout(poolData, this.props.Document, this.childDocs,
@@ -761,7 +764,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     get doLayoutComputation() {
         let computedElementData: { elements: ViewDefResult[] };
-        switch (this.Document.freeformLayoutEngine) {
+        switch (this.Document._freeformLayoutEngine) {
             case "pivot": computedElementData = this.doPivotLayout(this._layoutPoolData); break;
             default: computedElementData = this.doFreeformLayout(this._layoutPoolData); break;
         }
@@ -770,7 +773,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 ele: <CollectionFreeFormDocumentView key={pair.layout[Id]}  {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
-                    fitToBox={this.props.fitToBox || this.Document.freeformLayoutEngine === "pivot"} />,
+                    fitToBox={this.props.fitToBox || this.Document._freeformLayoutEngine === "pivot"} />,
                 bounds: this.childDataProvider(pair.layout)
             }));
 
@@ -797,12 +800,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     layoutDocsInGrid = () => {
         UndoManager.RunInBatch(() => {
             const docs = DocListCast(this.Document[this.props.fieldKey]);
-            const startX = this.Document.panX || 0;
+            const startX = this.Document._panX || 0;
             let x = startX;
-            let y = this.Document.panY || 0;
+            let y = this.Document._panY || 0;
             let i = 0;
-            const width = Math.max(...docs.map(doc => NumCast(doc.width)));
-            const height = Math.max(...docs.map(doc => NumCast(doc.height)));
+            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) {
                 doc.x = x;
                 doc.y = y;
@@ -880,8 +883,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const layoutItems: ContextMenuProps[] = [];
 
         layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
-        layoutItems.push({ description: `${this.Document.LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document.LODdisable = !this.Document.LODdisable, icon: "table" });
-        layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+        layoutItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+        layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
         layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
@@ -918,7 +921,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             description: "Add Note ...",
             subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({
                 description: (i + 1) + ": " + StrCast(note.title),
-                event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument({ width: 200, height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], autoHeight: true, layout: note, title: StrCast(note.title) })),
+                event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument({ _width: 200, _height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], autoHeight: true, layout: note, title: StrCast(note.title) })),
                 icon: "eye"
             })) as ContextMenuProps[],
             icon: "eye"
@@ -939,7 +942,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     children = () => {
         const eles: JSX.Element[] = [];
-        this.extensionDoc && (eles.push(...this.childViews()));
+        eles.push(...this.childViews());
         // this._palette && (eles.push(this._palette));
         // this.currentStroke && (eles.push(this.currentStroke));
         eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
@@ -951,7 +954,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         </div>;
     }
     @computed get marqueeView() {
-        return <MarqueeView {...this.props} extensionDoc={this.extensionDoc!} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
+        return <MarqueeView {...this.props} 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}>
@@ -973,7 +976,6 @@ 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
-        if (!this.extensionDoc) return (null);
         // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
         return <div className={"collectionfreeformview-container"}
             ref={this.createDashEventsTarget}
@@ -986,7 +988,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
                 height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
             }}>
-            {!this.Document.LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
+            {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
                 this.placeholder : this.marqueeView}
             <CollectionFreeFormOverlayView elements={this.elementFunc} />
         </div>;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 584d18372..0e02d1ef2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -29,7 +29,6 @@ interface MarqueeViewProps {
     removeDocument: (doc: Doc) => boolean;
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
-    extensionDoc: Doc;
     isAnnotationOverlay?: boolean;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
@@ -85,7 +84,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 }
                 ns.map(line => {
                     const indent = line.search(/\S|$/);
-                    const newBox = Docs.Create.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line });
+                    const newBox = Docs.Create.TextDocument({ _width: 200, _height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line });
                     this.props.addDocument(newBox);
                     y += 40 * this.props.getTransform().Scale;
                 });
@@ -95,17 +94,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             navigator.clipboard.readText().then(text => {
                 const ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
                 if (ns.length === 1 && text.startsWith("http")) {
-                    this.props.addDocument(Docs.Create.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }));// paste an image from its URL in the paste buffer
+                    this.props.addDocument(Docs.Create.ImageDocument(text, { _nativeWidth: 300, _width: 300, x: x, y: y }));// paste an image from its URL in the paste buffer
                 } else {
                     this.pasteTable(ns, x, y);
                 }
             });
         } else if (!e.ctrlKey) {
             this.props.addLiveTextDocument(
-                Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }));
+                Docs.Create.TextDocument({ _width: 200, _height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }));
         } else if (e.keyCode > 48 && e.keyCode <= 57) {
             const notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
-            const text = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" });
+            const text = Docs.Create.TextDocument({ _width: 200, _height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" });
             text.layout = notes[(e.keyCode - 49) % notes.length];
             this.props.addLiveTextDocument(text);
         }
@@ -128,7 +127,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             let groupAttr: string | number = "";
             const rowProto = new Doc();
             rowProto.title = rowProto.Id;
-            rowProto.width = 200;
+            rowProto._width = 200;
             rowProto.isPrototype = true;
             for (let i = 1; i < ns.length - 1; i++) {
                 const values = ns[i].split("\t");
@@ -144,10 +143,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 }
                 docDataProto.title = i.toString();
                 const doc = Doc.MakeDelegate(docDataProto);
-                doc.width = 200;
+                doc._width = 200;
                 docList.push(doc);
             }
-            const newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField("_group", "#f1efeb")] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, "#f1efeb"))], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 });
+            const newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField("_group", "#f1efeb")] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, "#f1efeb"))], docList, { x: x, y: y, title: "droppedTable", _width: 300, _height: 100 });
 
             this.props.addDocument(newCol);
         }
@@ -267,15 +266,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     }
 
     get inkDoc() {
-        return this.props.extensionDoc;
+        return this.props.Document;
     }
 
     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);
+        return Cast(this.props.Document.ink, InkField);
     }
 
     set ink(value: InkField | undefined) {
-        this.props.extensionDoc && (this.props.extensionDoc.ink = value);
+        this.props.Document.ink = value;
     }
 
     @action
@@ -325,13 +324,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const newCollection = Docs.Create.FreeformDocument(selected, {
             x: bounds.left,
             y: bounds.top,
-            panX: 0,
-            panY: 0,
+            _panX: 0,
+            _panY: 0,
             backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
             defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
-            width: bounds.width,
-            height: bounds.height,
-            LODdisable: true,
+            _width: bounds.width,
+            _height: bounds.height,
+            _LODdisable: true,
             title: "a nested collection",
         });
         // const dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
@@ -374,16 +373,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             d.page = -1;
             return d;
         });
-        newCollection.chromeStatus = "disabled";
-        const summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
+        newCollection._chromeStatus = "disabled";
+        const summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, _width: 300, _height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
         Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]);
         newCollection.x = bounds.left + bounds.width;
         Doc.GetProto(newCollection).summaryDoc = summary;
         Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`);
         if (e instanceof KeyboardEvent ? e.key === "s" : true) { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view.
-            const container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
-            container.viewType = CollectionViewType.Stacking;
-            container.autoHeight = true;
+            const container = Docs.Create.FreeformDocument([summary, newCollection], {
+                x: bounds.left, y: bounds.top, _width: 300, _height: 200, autoHeight: true,
+                _viewType: CollectionViewType.Stacking, _chromeStatus: "disabled", title: "-summary-"
+            });
             Doc.GetProto(summary).maximizeLocation = "inPlace";  // or "onRight"
             this.props.addLiveTextDocument(container);
         } else if (e instanceof KeyboardEvent ? e.key === "S" : false) { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them
@@ -468,8 +468,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             const layoutDoc = Doc.Layout(doc);
             const x = NumCast(doc.x);
             const y = NumCast(doc.y);
-            const w = NumCast(layoutDoc.width);
-            const h = NumCast(layoutDoc.height);
+            const w = NumCast(layoutDoc._width);
+            const h = NumCast(layoutDoc._height);
             if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
                 selection.push(doc);
             }
@@ -479,8 +479,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 const layoutDoc = Doc.Layout(doc);
                 const x = NumCast(doc.x);
                 const y = NumCast(doc.y);
-                const w = NumCast(layoutDoc.width);
-                const h = NumCast(layoutDoc.height);
+                const w = NumCast(layoutDoc._width);
+                const h = NumCast(layoutDoc._height);
                 if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
                     selection.push(doc);
                 }
@@ -496,8 +496,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 const layoutDoc = Doc.Layout(doc);
                 const x = NumCast(doc.x);
                 const y = NumCast(doc.y);
-                const w = NumCast(layoutDoc.width);
-                const h = NumCast(layoutDoc.height);
+                const w = NumCast(layoutDoc._width);
+                const 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/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index bb8a8b47b..e3bf6b5f8 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -292,7 +292,7 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
         if (index > -1) keys.splice(index, 1);
         const cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb"));
         const docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType);
-        const createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" }));
+        const createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { _width: 500, _height: 300, title: groupType + " table" }));
         const ref = React.createRef<HTMLDivElement>();
         return <div ref={ref}><button className="linkEditor-button" onPointerDown={SetupDrag(ref, createTable)} title="Drag to view relationship table"><FontAwesomeIcon icon="table" size="sm" /></button></div>;
     }
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 29e167ff7..325c92413 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -89,7 +89,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     async resetPan() {
         if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
             runInAction(() => this.canPan = false);
-            if (this.sourceView.props.ContainingCollectionDoc.viewType === CollectionViewType.Freeform) {
+            if (this.sourceView.props.ContainingCollectionDoc._viewType === CollectionViewType.Freeform) {
                 const docs = Cast(this.sourceView.props.ContainingCollectionDoc.data, listSpec(Doc), []);
                 const aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(LinkFollowBox.destinationDoc));
 
@@ -165,11 +165,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     @undoBatch
     openColFullScreen = (options: { context: Doc }) => {
         if (LinkFollowBox.destinationDoc) {
-            if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2;
-                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2;
-                options.context.panX = newPanX;
-                options.context.panY = newPanY;
+            if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2;
+                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2;
+                options.context._panX = newPanX;
+                options.context._panY = newPanY;
             }
             const view = DocumentManager.Instance.getDocumentView(options.context);
             view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
@@ -193,11 +193,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     openLinkColRight = (options: { context: Doc, shouldZoom: boolean }) => {
         if (LinkFollowBox.destinationDoc) {
             options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
-            if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2;
-                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2;
-                options.context.panX = newPanX;
-                options.context.panY = newPanY;
+            if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2;
+                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2;
+                options.context._panX = newPanX;
+                options.context._panY = newPanY;
             }
             (LinkFollowBox._addDocTab || this.props.addDocTab)(options.context, undefined, "onRight");
 
@@ -245,11 +245,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     openLinkColTab = (options: { context: Doc, shouldZoom: boolean }) => {
         if (LinkFollowBox.destinationDoc) {
             options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
-            if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2;
-                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2;
-                options.context.panX = newPanX;
-                options.context.panY = newPanY;
+            if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2;
+                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2;
+                options.context._panX = newPanX;
+                options.context._panY = newPanY;
             }
             (LinkFollowBox._addDocTab || this.props.addDocTab)(options.context, undefined, "inTab");
             if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom });
@@ -270,13 +270,13 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
                     const y = NumCast(LinkFollowBox.sourceDoc.y);
                     const x = NumCast(LinkFollowBox.sourceDoc.x);
 
-                    const width = NumCast(LinkFollowBox.sourceDoc.width);
-                    const height = NumCast(LinkFollowBox.sourceDoc.height);
+                    const width = NumCast(LinkFollowBox.sourceDoc._width);
+                    const height = NumCast(LinkFollowBox.sourceDoc._height);
 
                     alias.x = x + width + 30;
                     alias.y = y;
-                    alias.width = width;
-                    alias.height = height;
+                    alias._width = width;
+                    alias._height = height;
 
                     this.sourceView.props.addDocument(alias);
                 }
@@ -361,7 +361,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     get canOpenInPlace() {
         if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
             const colDoc = this.sourceView.props.ContainingCollectionDoc;
-            if (colDoc.viewType && colDoc.viewType === CollectionViewType.Freeform) return true;
+            if (colDoc._viewType === CollectionViewType.Freeform) return true;
         }
         return false;
     }
@@ -481,7 +481,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
         let contextMatch: boolean = false;
         if (this.selectedContextAliases) {
             this.selectedContextAliases.forEach(alias => {
-                if (alias.viewType === CollectionViewType.Freeform) contextMatch = true;
+                if (alias._viewType === CollectionViewType.Freeform) contextMatch = true;
             });
         }
         if (contextMatch) return true;
@@ -523,7 +523,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
 
     render() {
         return (
-            <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document.height), width: NumCast(this.props.Document.width) }}>
+            <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document._height), width: NumCast(this.props.Document._width) }}>
                 <div className="linkFollowBox-header">
                     <div className="topHeader">
                         {LinkFollowBox.linkDoc ? "Link Title: " + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"}
@@ -533,7 +533,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
                         LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc ? "Source: " + StrCast(LinkFollowBox.sourceDoc.title) + ", Destination: " + StrCast(LinkFollowBox.destinationDoc.title)
                             : "" : ""}</div>
                 </div>
-                <div className="linkFollowBox-content" style={{ height: NumCast(this.props.Document.height) - 110 }}>
+                <div className="linkFollowBox-content" style={{ height: NumCast(this.props.Document._height) - 110 }}>
                     <div className="linkFollowBox-item">
                         <div className="linkFollowBox-item title">Mode</div>
                         <div className="linkFollowBox-itemContent">
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index ace9a9e4c..0c38ff45c 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -58,7 +58,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
         if (index > -1) keys.splice(index, 1);
         const cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb"));
         const docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType);
-        const createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" }));
+        const createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { _width: 500, _height: 300, title: groupType + " table" }));
         const ref = React.createRef<HTMLDivElement>();
         return <div ref={ref}><button className="linkEditor-button linkEditor-tableButton" onPointerDown={SetupDrag(ref, createTable)} title="Drag to view relationship table"><FontAwesomeIcon icon="table" size="sm" /></button></div>;
     }
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 95c765e8a..8ede79edc 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -68,7 +68,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
                 this.Document.playOnSelect && sel && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFrom(DateCast(sel.creationTime).date.getTime());
             });
         this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, timeInMillisecondsFrom1970 => {
-            const start = this.extensionDoc && DateCast(this.extensionDoc.recordingStart);
+            const start = DateCast(this.dataDoc[this.props.fieldKey + "-recordingStart"]);
             start && this.playFrom((timeInMillisecondsFrom1970 - start.date.getTime()) / 1000);
         });
     }
@@ -128,13 +128,12 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
     recordAudioAnnotation = () => {
         let gumStream: any;
         const self = this;
-        const extensionDoc = this.extensionDoc;
-        extensionDoc && navigator.mediaDevices.getUserMedia({
+        navigator.mediaDevices.getUserMedia({
             audio: true
         }).then(function (stream) {
             gumStream = stream;
             self._recorder = new MediaRecorder(stream);
-            extensionDoc.recordingStart = new DateField(new Date());
+            self.dataDoc[self.props.fieldKey + "-recordingStart"] = new DateField(new Date());
             AudioBox.ActiveRecordings.push(self.props.Document);
             self._recorder.ondataavailable = async function (e: any) {
                 const formData = new FormData();
@@ -213,55 +212,53 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
 
     render() {
         const interactive = this.active() ? "-interactive" : "";
-        return (!this.extensionDoc ? (null) :
-            <div className={`audiobox-container`} onContextMenu={this.specificContextMenu}
-                onClick={!this.path ? this.recordClick : undefined}>
-                <div className="audiobox-handle"></div>
-                {!this.path ?
-                    <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this._audioState === "recording" ? "red" : "black" }}>
-                        {this._audioState === "recording" ? "STOP" : "RECORD"}
-                    </button> :
-                    <div className="audiobox-controls">
-                        <div className="audiobox-player" onClick={this.onPlay}>
-                            <div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this._playing ? "pause" : "play"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
-                            <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%" }} icon="stop" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
-                            <div className="audiobox-timeline" onClick={e => e.stopPropagation()}
-                                onPointerDown={e => {
-                                    if (e.button === 0 && !e.ctrlKey) {
-                                        const rect = (e.target as any).getBoundingClientRect();
-                                        this._ele!.currentTime = this.Document.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
-                                        this.pause();
-                                        e.stopPropagation();
-                                    }
-                                }} >
-                                {DocListCast(this.dataDoc.links).map((l, i) => {
-                                    let la1 = l.anchor1 as Doc;
-                                    let la2 = l.anchor2 as Doc;
-                                    let linkTime = NumCast(l.anchor2Timecode);
-                                    if (Doc.AreProtosEqual(la1, this.dataDoc)) {
-                                        la1 = l.anchor2 as Doc;
-                                        la2 = l.anchor1 as Doc;
-                                        linkTime = NumCast(l.anchor1Timecode);
-                                    }
-                                    return !linkTime ? (null) :
-                                        <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={l[Id]} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}>
-                                            <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
-                                                <DocumentView {...this.props} Document={l} layoutKey={Doc.LinkEndpoint(l, la2)}
-                                                    parentActive={returnTrue} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne}
-                                                    backgroundColor={returnTransparent} />
-                                            </div>
-                                            <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)}
-                                                onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }}
-                                                onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} />
-                                        </div>;
-                                })}
-                                <div className="audiobox-current" style={{ left: `${NumCast(this.Document.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} />
-                                {this.audio}
-                            </div>
+        return <div className={`audiobox-container`} onContextMenu={this.specificContextMenu}
+            onClick={!this.path ? this.recordClick : undefined}>
+            <div className="audiobox-handle"></div>
+            {!this.path ?
+                <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this._audioState === "recording" ? "red" : "black" }}>
+                    {this._audioState === "recording" ? "STOP" : "RECORD"}
+                </button> :
+                <div className="audiobox-controls">
+                    <div className="audiobox-player" onClick={this.onPlay}>
+                        <div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this._playing ? "pause" : "play"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
+                        <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%" }} icon="stop" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
+                        <div className="audiobox-timeline" onClick={e => e.stopPropagation()}
+                            onPointerDown={e => {
+                                if (e.button === 0 && !e.ctrlKey) {
+                                    const rect = (e.target as any).getBoundingClientRect();
+                                    this._ele!.currentTime = this.Document.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
+                                    this.pause();
+                                    e.stopPropagation();
+                                }
+                            }} >
+                            {DocListCast(this.dataDoc.links).map((l, i) => {
+                                let la1 = l.anchor1 as Doc;
+                                let la2 = l.anchor2 as Doc;
+                                let linkTime = NumCast(l.anchor2Timecode);
+                                if (Doc.AreProtosEqual(la1, this.dataDoc)) {
+                                    la1 = l.anchor2 as Doc;
+                                    la2 = l.anchor1 as Doc;
+                                    linkTime = NumCast(l.anchor1Timecode);
+                                }
+                                return !linkTime ? (null) :
+                                    <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={l[Id]} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}>
+                                        <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
+                                            <DocumentView {...this.props} Document={l} layoutKey={Doc.LinkEndpoint(l, la2)}
+                                                parentActive={returnTrue} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne}
+                                                backgroundColor={returnTransparent} />
+                                        </div>
+                                        <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)}
+                                            onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }}
+                                            onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} />
+                                    </div>;
+                            })}
+                            <div className="audiobox-current" style={{ left: `${NumCast(this.Document.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} />
+                            {this.audio}
                         </div>
                     </div>
-                }
-            </div>
-        );
+                </div>
+            }
+        </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index cd78f5d49..ea3c505d8 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -38,18 +38,18 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
     }
     @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document) ? this.props.dataProvider(this.props.Document) : undefined; }
-    @computed get nativeWidth() { return NumCast(this.layoutDoc.nativeWidth); }
-    @computed get nativeHeight() { return NumCast(this.layoutDoc.nativeHeight); }
+    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth); }
+    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight); }
 
     @computed get renderScriptDim() {
         if (this.Document.renderScript) {
             const someView = Cast(this.props.Document.someView, Doc);
             const minimap = Cast(this.props.Document.minimap, Doc);
             if (someView instanceof Doc && minimap instanceof Doc) {
-                const x = (NumCast(someView.panX) - NumCast(someView.width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap.width) - NumCast(minimap.width) / 2;
-                const y = (NumCast(someView.panY) - NumCast(someView.height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap.height) - NumCast(minimap.height) / 2;
-                const w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width);
-                const h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height);
+                const x = (NumCast(someView._panX) - NumCast(someView._width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap._width) - NumCast(minimap._width) / 2;
+                const y = (NumCast(someView._panY) - NumCast(someView._height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap._height) - NumCast(minimap._height) / 2;
+                const w = NumCast(someView._width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width);
+                const h = NumCast(someView._height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height);
                 return { x: x, y: y, width: w, height: h };
             }
         }
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 96102e0e8..4f1bbd33c 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -44,8 +44,8 @@ interface ContentFittingDocumentViewProps {
 export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{
     public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
     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 get nativeWidth() { return NumCast(this.layoutDoc?._nativeWidth, this.props.PanelWidth()); }
+    private get nativeHeight() { return NumCast(this.layoutDoc?._nativeHeight, this.props.PanelHeight()); }
     private contentScaling = () => {
         const wscale = this.props.PanelWidth() / (this.nativeWidth || this.props.PanelWidth() || 1);
         if (wscale * this.nativeHeight > this.props.PanelHeight()) {
@@ -68,10 +68,10 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
         }
         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 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, -this.centeringYOffset).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; }
+    private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; }
     private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight * this.contentScaling()) / 2 : 0; }
 
     @computed get borderRounding() { return StrCast(this.props.Document?.borderRounding); }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2570d232a..5663e5e3c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -108,8 +108,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     public get ContentDiv() { return this._mainCont.current; }
     @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
     @computed get topMost() { return this.props.renderDepth === 0; }
-    @computed get nativeWidth() { return this.layoutDoc.nativeWidth || 0; }
-    @computed get nativeHeight() { return this.layoutDoc.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; }
     @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
     @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
@@ -343,7 +343,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             if (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3) {
                 if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick)) {
                     this.cleanUpInteractions();
-                    this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
+                    this.startDragging(this._downX, this._downY, this.Document._dropAction ? this.Document._dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
                 }
             }
             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
@@ -387,10 +387,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
                 const doc = PositionDocument(this.props.Document);
                 const layoutDoc = PositionDocument(Doc.Layout(this.props.Document));
-                let nwidth = layoutDoc.nativeWidth || 0;
-                let nheight = layoutDoc.nativeHeight || 0;
-                const width = (layoutDoc.width || 0);
-                const height = (layoutDoc.height || (nheight / nwidth * width));
+                let nwidth = layoutDoc._nativeWidth || 0;
+                let nheight = layoutDoc._nativeHeight || 0;
+                const width = (layoutDoc._width || 0);
+                const height = (layoutDoc._height || (nheight / nwidth * width));
                 const scale = this.props.ScreenToLocalTransform().Scale * this.props.ContentScaling();
                 const actualdW = Math.max(width + (dW * scale), 20);
                 const actualdH = Math.max(height + (dH * scale), 20);
@@ -400,33 +400,33 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) {
                     layoutDoc.ignoreAspect = false;
 
-                    layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
-                    layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
+                    layoutDoc._nativeWidth = nwidth = layoutDoc._width || 0;
+                    layoutDoc._nativeHeight = nheight = layoutDoc._height || 0;
                 }
                 if (fixedAspect && (!nwidth || !nheight)) {
-                    layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
-                    layoutDoc.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) {
-                            layoutDoc.nativeWidth = actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0);
+                            layoutDoc._nativeWidth = actualdW / (layoutDoc._width || 1) * (layoutDoc._nativeWidth || 0);
                         }
-                        layoutDoc.width = actualdW;
-                        if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width;
-                        else layoutDoc.height = actualdH;
+                        layoutDoc._width = actualdW;
+                        if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width;
+                        else layoutDoc._height = actualdH;
                     }
                     else {
                         if (!fixedAspect) {
-                            layoutDoc.nativeHeight = actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0);
+                            layoutDoc._nativeHeight = actualdH / (layoutDoc._height || 1) * (doc._nativeHeight || 0);
                         }
-                        layoutDoc.height = actualdH;
-                        if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height;
-                        else layoutDoc.width = actualdW;
+                        layoutDoc._height = actualdH;
+                        if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height;
+                        else layoutDoc._width = actualdW;
                     }
                 } else {
-                    dW && (layoutDoc.width = actualdW);
-                    dH && (layoutDoc.height = actualdH);
+                    dW && (layoutDoc._width = actualdW);
+                    dH && (layoutDoc._height = actualdH);
                     dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false);
                 }
             }
@@ -484,7 +484,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
-                    this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
+                    this.startDragging(this._downX, this._downY, this.Document._dropAction ? this.Document._dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
                 }
             }
             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
@@ -520,12 +520,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         undoBatch(() => doc.layoutKey = "layout")();
     }
 
-    static makeCustomViewClicked = (doc: Doc, dataDoc: Opt<Doc>) => {
+    static makeCustomViewClicked = (doc: Doc, dataDoc: Opt<Doc>, name: string = "custom") => {
         const batch = UndoManager.StartBatch("CustomViewClicked");
-        if (doc.layoutCustom === undefined) {
-            const width = NumCast(doc.width);
-            const height = NumCast(doc.height);
-            const options = { title: "data", width, x: -width / 2, y: - height / 2, };
+        const customName = "layout-" + name;
+        if (doc[customName] === undefined) {
+            const _width = NumCast(doc.width);
+            const _height = NumCast(doc.height);
+            const options = { title: "data", _width, x: -_width / 2, y: - _height / 2, };
 
             let fieldTemplate: Doc;
             switch (doc.type) {
@@ -549,12 +550,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             fieldTemplate.heading = 1;
             fieldTemplate.autoHeight = true;
 
-            const docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: doc.title + "_layout", width: width + 20, height: Math.max(100, height + 45) });
+            const docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
 
             Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate));
-            Doc.ApplyTemplateTo(docTemplate, dataDoc || doc, "layoutCustom", undefined);
+            Doc.ApplyTemplateTo(docTemplate, dataDoc || doc, customName, undefined);
         } else {
-            doc.layoutKey = "layoutCustom";
+            doc.layoutKey = customName;
         }
         batch.end();
     }
@@ -622,9 +623,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     freezeNativeDimensions = (): void => {
         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();
+        if (!this.layoutDoc.ignoreAspect && !this.layoutDoc._nativeWidth) {
+            this.layoutDoc._nativeWidth = this.props.PanelWidth();
+            this.layoutDoc._nativeHeight = this.props.PanelHeight();
         }
     }
 
@@ -635,7 +636,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (!anchors.find(anchor2 => anchor2 && anchor2.title === this.Document.title + ".portal" ? true : false)) {
             const portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, "");
             DocServer.GetRefField(portalID).then(existingPortal => {
-                const portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.layoutDoc.width || 0) + 10, height: this.layoutDoc.height || 0, title: portalID });
+                const 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;
             });
@@ -646,13 +647,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @action
     setNarrativeView = (custom: boolean): void => {
         if (custom) {
-            this.props.Document.layout_narrative = CollectionView.LayoutString("narrative");
-            this.props.Document.nativeWidth = this.props.Document.nativeHeight = undefined;
-            !this.props.Document.narrative && (Doc.GetProto(this.props.Document).narrative = new List<Doc>([]));
-            this.props.Document.viewType = CollectionViewType.Stacking;
-            this.props.Document.layoutKey = "layout_narrative";
-        } else {
-            DocumentView.makeNativeViewClicked(this.props.Document);
+            custom ? DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc, "narrative") : DocumentView.makeNativeViewClicked(this.props.Document);
+            //     this.props.Document.layout_narrative = CollectionView.LayoutString("narrative");
+            //     this.props.Document.layoutKey = "layout_narrative";
+            //     this.props.Document._nativeWidth = this.props.Document._nativeHeight = undefined;
+            //     !this.props.Document.narrative && (Doc.GetProto(this.props.Document).narrative = new List<Doc>([]));
+            //     this.props.Document._viewType = CollectionViewType.Stacking;
+            // } else {
+            //     DocumentView.makeNativeViewClicked(this.props.Document);
         }
     }
 
@@ -709,7 +711,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         subitems.push({ description: "Open Right      ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight", this.props.LibraryPath), icon: "caret-square-right" });
         subitems.push({ description: "Open Alias Tab  ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "inTab"), icon: "folder" });
         subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "onRight"), icon: "caret-square-right" });
-        subitems.push({ description: "Open Fields     ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" });
+        subitems.push({ description: "Open Fields     ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), undefined, "onRight"), icon: "layer-group" });
         cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
 
 
@@ -737,9 +739,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (this.props.DataDoc) {
             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._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.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.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: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
         layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
@@ -793,13 +795,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
                     DocServer.setFieldWriteMode("x", mode1);
                     DocServer.setFieldWriteMode("y", mode1);
-                    DocServer.setFieldWriteMode("width", mode1);
-                    DocServer.setFieldWriteMode("height", mode1);
+                    DocServer.setFieldWriteMode("_width", mode1);
+                    DocServer.setFieldWriteMode("_height", mode1);
 
-                    DocServer.setFieldWriteMode("panX", mode2);
-                    DocServer.setFieldWriteMode("panY", mode2);
+                    DocServer.setFieldWriteMode("_panX", mode2);
+                    DocServer.setFieldWriteMode("_panY", mode2);
                     DocServer.setFieldWriteMode("scale", mode2);
-                    DocServer.setFieldWriteMode("viewType", mode2);
+                    DocServer.setFieldWriteMode("_viewType", mode2);
                 };
                 const aclsMenu: ContextMenuProps[] = [];
                 aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
@@ -858,7 +860,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const fallback = Cast(this.props.Document.layoutKey, "string");
         return typeof fallback === "string" ? fallback : "layout";
     }
-    childScaling = () => (this.layoutDoc.fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
+    childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this._nativeWidth : this.props.ContentScaling());
     @computed get contents() {
         TraceMobx();
         return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
@@ -981,7 +983,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"];
-        let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear;
+        let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear;
         highlighting = highlighting && this.props.focus !== emptyFunction;  // bcz: hack to turn off highlighting onsidebar panel documents.  need to flag a document as not highlightable in a more direct way
         return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown}
             onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
@@ -993,7 +995,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
                 border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
                 boxShadow: this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined,
-                background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor,
+                background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor,
                 width: animwidth,
                 height: animheight,
                 opacity: this.Document.opacity
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0a1a79bc4..9303ca0fa 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -152,7 +152,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
             DocServer.GetRefField(value).then(doc => {
                 DocServer.GetRefField(id).then(linkDoc => {
-                    this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
+                    this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);
                     DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
                     if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
                     else DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: this.dataDoc[key] as Doc }, "Ref:" + value, "link to named target", id);
@@ -193,8 +193,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             const tsel = this._editorView.state.selection.$from;
             tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000)));
             this._applyingChange = true;
-            this.extensionDoc && !this.extensionDoc.lastModified && (this.extensionDoc.backgroundColor = "lightGray");
-            this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now())));
+            this.dataDoc[this.props.fieldKey + "-lastModified"] && (this.dataDoc[this.props.fieldKey + "-backgroundColor"] = "lightGray");
+            this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
             this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), state.doc.textBetween(0, state.doc.content.size, "\n\n"));
             this._applyingChange = false;
             this.updateTitle();
@@ -271,7 +271,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 const target = de.complete.docDragData.droppedDocuments[0];
                 // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
                 // if (link) {
-                target.fitToBox = true;
+                target._fitToBox = true;
                 const node = schema.nodes.dashDoc.create({
                     width: target[WidthSym](), height: target[HeightSym](),
                     title: "dashDoc", docid: target[Id],
@@ -704,11 +704,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
                     if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) {
                         setTimeout(async () => {
-                            const extension = Doc.fieldExtensionDoc(pdfDoc, "data");
-                            if (extension) {
-                                const targetAnnotations = await DocListCastAsync(extension.annotations);// bcz: NO... this assumes the pdf is using its 'data' field.  need to have the PDF's view handle updating its own annotations
-                                targetAnnotations && targetAnnotations.push(pdfRegion);
-                            }
+                            const targetAnnotations = await DocListCastAsync(pdfDoc["data-annotations"]);// bcz: NO... this assumes the pdf is using its 'data' field.  need to have the PDF's view handle updating its own annotations
+                            targetAnnotations && targetAnnotations.push(pdfRegion);
                         });
 
                         const link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link");
@@ -1069,12 +1066,12 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 this.layoutDoc.limitHeight = undefined;
                 this.layoutDoc.autoHeight = false;
             }
-            const nh = this.Document.isTemplateForField ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
-            const dh = NumCast(this.layoutDoc.height, 0);
+            const nh = this.Document.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0);
+            const dh = NumCast(this.layoutDoc._height, 0);
             const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
             if (Math.abs(newHeight - dh) > 1) { // bcz: Argh!  without this, we get into a React crash if the same document is opened in a freeform view and in the treeview.  no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
-                this.layoutDoc.height = newHeight;
-                this.dataDoc.nativeHeight = nh ? scrollHeight : undefined;
+                this.layoutDoc._height = newHeight;
+                this.dataDoc._nativeHeight = nh ? scrollHeight : undefined;
             }
         }
     }
@@ -1082,6 +1079,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); }
     @computed get sidebarWidth() { return Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); }
     @computed get annotationsKey() { return "annotations"; }
+    @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
     render() {
         TraceMobx();
         const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
@@ -1122,7 +1120,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 {this.props.Document.hideSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
                     <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
                     <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
-                        style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc?.backgroundColor, "transparent")}` }}>
+                        style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
                         <CollectionFreeFormView {...this.props}
                             PanelHeight={this.props.PanelHeight}
                             PanelWidth={() => this.sidebarWidth}
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 8dedfda9b..fda3e3285 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -86,7 +86,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, { title: (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");
                 }
                 keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
                     FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 9462ff024..172338eb6 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -79,7 +79,7 @@ export class IconBox extends React.Component<FieldViewProps> {
                 <Measure offset onResize={(r) => runInAction(() => {
                     if (r.offset!.width || this.props.Document.hideLabel) {
                         this.props.Document.iconWidth = (r.offset!.width + Number(MINIMIZED_ICON_SIZE));
-                        if (this.props.Document.height === Number(MINIMIZED_ICON_SIZE)) this.props.Document.width = this.props.Document.iconWidth;
+                        if (this.props.Document._height === Number(MINIMIZED_ICON_SIZE)) this.props.Document._width = this.props.Document.iconWidth;
                     }
                 })}>
                     {({ measureRef }) =>
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index b7e904283..1a07c1598 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -39,7 +39,6 @@ library.add(faFileAudio, faAsterisk);
 export const pageSchema = createSchema({
     curPage: "number",
     fitWidth: "boolean",
-    rotation: "number",
     googlePhotosUrl: "string",
     googlePhotosTags: "string"
 });
@@ -78,7 +77,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                 e.stopPropagation();
             }
             de.metaKey && de.complete.docDragData.droppedDocuments.forEach(action((drop: Doc) => {
-                this.extensionDoc && Doc.AddDocToList(Doc.GetProto(this.extensionDoc), "Alternates", drop);
+                Doc.AddDocToList(this.dataDoc, this.props.fieldKey + "-alternates", drop);
                 e.stopPropagation();
             }));
         }
@@ -88,8 +87,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
         let gumStream: any;
         let recorder: any;
         const self = this;
-        const extensionDoc = this.extensionDoc;
-        extensionDoc && navigator.mediaDevices.getUserMedia({
+        navigator.mediaDevices.getUserMedia({
             audio: true
         }).then(function (stream) {
             gumStream = stream;
@@ -104,11 +102,11 @@ 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 
-                const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", width: 200, height: 32 });
+                const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", _width: 200, _height: 32 });
                 audioDoc.treeViewExpandedView = "layout";
-                const audioAnnos = Cast(extensionDoc.audioAnnotations, listSpec(Doc));
+                const audioAnnos = Cast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"], listSpec(Doc));
                 if (audioAnnos === undefined) {
-                    extensionDoc.audioAnnotations = new List([audioDoc]);
+                    this.dataDoc[this.props.fieldKey + "-audioAnnotations"] = new List([audioDoc]);
                 } else {
                     audioAnnos.push(audioDoc);
                 }
@@ -125,15 +123,15 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
 
     @undoBatch
     rotate = action(() => {
-        const nw = this.Document.nativeWidth;
-        const nh = this.Document.nativeHeight;
-        const w = this.Document.width;
-        const h = this.Document.height;
-        this.Document.rotation = ((this.Document.rotation || 0) + 90) % 360;
-        this.Document.nativeWidth = nh;
-        this.Document.nativeHeight = nw;
-        this.Document.width = h;
-        this.Document.height = w;
+        const nw = NumCast(this.Document[this.props.fieldKey + "-nativeWidth"]);
+        const nh = NumCast(this.Document[this.props.fieldKey + "-nativeHeight"]);
+        const w = this.Document._width;
+        const h = this.Document._height;
+        this.dataDoc[this.props.fieldKey + "-rotation"] = (NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]) + 90) % 360;
+        this.dataDoc[this.props.fieldKey + "-nativeWidth"] = nh;
+        this.dataDoc[this.props.fieldKey + "-nativeHeight"] = nw;
+        this.Document._width = h;
+        this.Document._height = w;
     });
 
     specificContextMenu = (e: React.MouseEvent): void => {
@@ -159,7 +157,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
             results.reduce((face: CognitiveServices.Image.Face, faceDocs: List<Doc>) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!), new List<Doc>());
             return faceDocs;
         };
-        this.url && this.extensionDoc && CognitiveServices.Image.Appliers.ProcessImage(this.extensionDoc, ["faces"], this.url, Service.Face, converter);
+        this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.props.fieldKey + "-faces"], this.url, Service.Face, converter);
     }
 
     generateMetadata = (threshold: Confidence = Confidence.Excellent) => {
@@ -171,12 +169,12 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                 const sanitized = tag.name.replace(" ", "_");
                 tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`);
             });
-            this.extensionDoc && (this.extensionDoc.generatedTags = tagsList);
+            this.dataDoc[this.props.fieldKey + "-generatedTags"] = tagsList;
             tagDoc.title = "Generated Tags Doc";
             tagDoc.confidence = threshold;
             return tagDoc;
         };
-        this.url && this.extensionDoc && CognitiveServices.Image.Appliers.ProcessImage(this.extensionDoc, ["generatedTagsDoc"], this.url, Service.ComputerVision, converter);
+        this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.props.fieldKey + "-generatedTagsDoc"], this.url, Service.ComputerVision, converter);
     }
 
     @computed private get url() {
@@ -216,29 +214,30 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
 
     _resized = "";
     resize = (imgPath: string) => {
-        requestImageSize(imgPath)
-            .then((size: any) => {
-                const rotation = NumCast(this.dataDoc.rotation) % 180;
-                const realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size;
-                const aspect = realsize.height / realsize.width;
-                if (this.Document.width && (Math.abs(1 - NumCast(this.Document.height) / NumCast(this.Document.width) / (realsize.height / realsize.width)) > 0.1)) {
-                    setTimeout(action(() => {
-                        if (this.paths[NumCast(this.props.Document.curPage)] === imgPath && (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc)) {
-                            this._resized = imgPath;
-                            this.Document.height = this.Document[WidthSym]() * aspect;
-                            this.Document.nativeHeight = realsize.height;
-                            this.Document.nativeWidth = realsize.width;
-                        }
-                    }), 0);
-                } else this._resized = imgPath;
-            })
-            .catch((err: any) => console.log(err));
+        if (!this.dataDoc[this.props.fieldKey + "-nativeHeight"])
+            requestImageSize(imgPath)
+                .then((size: any) => {
+                    const rotation = NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]) % 180;
+                    const realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size;
+                    const aspect = realsize.height / realsize.width;
+                    if (this.Document._width && (Math.abs(1 - NumCast(this.Document._height) / NumCast(this.Document._width) / (realsize.height / realsize.width)) > 0.1)) {
+                        setTimeout(action(() => {
+                            if (this.paths[NumCast(this.props.Document.curPage)] === imgPath && (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc)) {
+                                this._resized = imgPath;
+                                this.Document._height = this.Document[WidthSym]() * aspect;
+                                this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.dataDoc._nativeHeight = realsize.height;
+                                this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.dataDoc._nativeWidth = realsize.width;
+                            }
+                        }), 0);
+                    } else this._resized = imgPath;
+                })
+                .catch((err: any) => console.log(err));
     }
 
     @action
     onPointerEnter = () => {
         const self = this;
-        const audioAnnos = this.extensionDoc && DocListCast(this.extensionDoc.audioAnnotations);
+        const audioAnnos = DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]);
         if (audioAnnos && audioAnnos.length && this._audioState === 0) {
             const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)];
             anno.data instanceof AudioField && new Howl({
@@ -273,17 +272,16 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
 
     @computed get nativeSize() {
         const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
-        const nativeWidth = (this.Document.nativeWidth || pw);
-        const nativeHeight = (this.Document.nativeHeight || 1);
+        const nativeWidth = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"], pw);
+        const nativeHeight = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"], 1);
         return { nativeWidth, nativeHeight };
     }
 
     @computed get paths() {
-        const extensionDoc = this.extensionDoc!;
         let paths = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
         // this._curSuffix = "";
         // if (w > 20) {
-        const alts = DocListCast(extensionDoc.Alternates);
+        const alts = DocListCast(this.dataDoc[this.props.fieldKey + "-alternates"]);
         const altpaths = alts.filter(doc => doc.data instanceof ImageField).map(doc => this.choosePath((doc.data as ImageField).url));
         const field = this.dataDoc[this.props.fieldKey];
         // if (w < 100 && this._smallRetryCount < 10) this._curSuffix = "_s";
@@ -296,13 +294,11 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
 
     @computed get content() {
         TraceMobx();
-        const extensionDoc = this.extensionDoc;
-        if (!extensionDoc) return (null);
 
         const srcpath = this.paths[NumCast(this.props.Document.curPage, 0)];
         const fadepath = this.paths[Math.min(1, this.paths.length - 1)];
         const { nativeWidth, nativeHeight } = this.nativeSize;
-        const rotation = NumCast(this.Document.rotation, 0);
+        const rotation = NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]);
         const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
         const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
 
@@ -331,10 +327,11 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                 style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }}
             >
                 <FontAwesomeIcon className="imageBox-audioFont"
-                    style={{ color: [DocListCast(extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={!DocListCast(extensionDoc.audioAnnotations).length ? "microphone" : faFileAudio} size="sm" />
+                    style={{ color: [DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }}
+                    icon={!DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" />
             </div>
             {this.considerGooglePhotosLink()}
-            <FaceRectangles document={extensionDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
+            <FaceRectangles document={this.dataDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
         </div>;
     }
 
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 234a6a9d3..499cd2877 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -179,7 +179,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     }
 
     getTemplate = async () => {
-        const parent = Docs.Create.StackingDocument([], { width: 800, height: 800, title: "Template" });
+        const parent = Docs.Create.StackingDocument([], { _width: 800, _height: 800, title: "Template" });
         parent.singleColumn = false;
         parent.columnWidth = 100;
         for (const row of this.rows.filter(row => row.isChecked)) {
@@ -200,15 +200,15 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         if (!fieldTemplate) {
             return;
         }
-        const previousViewType = fieldTemplate.viewType;
+        const previousViewType = fieldTemplate._viewType;
         Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(parentStackingDoc));
-        previousViewType && (fieldTemplate.viewType = previousViewType);
+        previousViewType && (fieldTemplate._viewType = previousViewType);
 
         Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate);
     }
 
     inferType = async (data: FieldResult, metaKey: string) => {
-        const options = { width: 300, height: 300, title: metaKey };
+        const options = { _width: 300, _height: 300, title: metaKey };
         if (data instanceof RichTextField || typeof data === "string" || typeof data === "number") {
             return Docs.Create.TextDocument(options);
         } else if (data instanceof List) {
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 46466aa0f..e6b512adf 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -46,7 +46,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
         if (value instanceof Doc) {
             e.stopPropagation();
             e.preventDefault();
-            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" });
+            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), undefined, "onRight"), icon: "layer-group" });
             ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
         }
     }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 8370df6ba..49fdf56e6 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -50,9 +50,9 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     constructor(props: any) {
         super(props);
         this._initialScale = this.props.ScreenToLocalTransform().Scale;
-        const nw = this.Document.nativeWidth = NumCast(this.extensionDocSync.nativeWidth, NumCast(this.Document.nativeWidth, 927));
-        const nh = this.Document.nativeHeight = NumCast(this.extensionDocSync.nativeHeight, NumCast(this.Document.nativeHeight, 1200));
-        !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw));
+        const nw = this.Document._nativeWidth = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"], NumCast(this.Document._nativeWidth, 927));
+        const nh = this.Document._nativeHeight = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"], NumCast(this.Document._nativeHeight, 1200));
+        !this.Document._fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw));
 
         const backup = "oldPath";
         const { Document } = this.props;
@@ -90,10 +90,10 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     }
 
     loaded = (nw: number, nh: number, np: number) => {
-        this.extensionDocSync.numPages = np;
-        this.extensionDocSync.nativeWidth = this.Document.nativeWidth = nw * 96 / 72;
-        this.extensionDocSync.nativeHeight = this.Document.nativeHeight = nh * 96 / 72;
-        !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw));
+        this.dataDoc[this.props.fieldKey + "-numPages"] = np;
+        this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = nw * 96 / 72;
+        this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = nh * 96 / 72;
+        !this.Document._fitWidth && !this.Document.ignoreAspect && (this.Document._height = this.Document[WidthSym]() * (nh / nw));
     }
 
     public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); }
@@ -211,7 +211,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
         const funcs: ContextMenuProps[] = [];
         pdfUrl && funcs.push({ description: "Copy path", event: () => Utils.CopyText(pdfUrl.url.pathname), icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Fit Width " + (this.Document.fitWidth ? "Off" : "On"), event: () => this.Document.fitWidth = !this.Document.fitWidth, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Fit Width " + (this.Document._fitWidth ? "Off" : "On"), event: () => this.Document._fitWidth = !this.Document._fitWidth, icon: "expand-arrows-alt" });
 
         ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" });
     }
@@ -220,8 +220,8 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     @computed get renderTitleBox() {
         const classname = "pdfBox" + (this.active() ? "-interactive" : "");
         return <div className={classname} style={{
-            width: !this.props.Document.fitWidth ? this.Document.nativeWidth || 0 : `${100 / this.contentScaling}%`,
-            height: !this.props.Document.fitWidth ? this.Document.nativeHeight || 0 : `${100 / this.contentScaling}%`,
+            width: !this.props.Document._fitWidth ? this.Document._nativeWidth || 0 : `${100 / this.contentScaling}%`,
+            height: !this.props.Document._fitWidth ? this.Document._nativeHeight || 0 : `${100 / this.contentScaling}%`,
             transform: `scale(${this.contentScaling})`
         }}  >
             <div className="pdfBox-title-outer">
@@ -252,7 +252,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     render() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null);
         if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true;
-        if (pdfUrl && this.extensionDoc && (this._everActive || (this.extensionDoc.nativeWidth && this.props.ScreenToLocalTransform().Scale < 2.5))) {
+        if (pdfUrl && (this._everActive || (this.dataDoc[this.props.fieldKey + "-nativeWidth"] && this.props.ScreenToLocalTransform().Scale < 2.5))) {
             if (pdfUrl instanceof PdfField && this._pdf) {
                 return this.renderPdfView;
             }
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 1e6894f37..eda12d6fb 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -336,7 +336,7 @@ export class PresBox extends React.Component<FieldViewProps> {
      */
     @action
     initializeScaleViews = (docList: Doc[], viewtype: number) => {
-        this.props.Document.chromeStatus = "disabled";
+        this.props.Document._chromeStatus = "disabled";
         const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 72;
         docList.forEach((doc: Doc) => {
             doc.presBox = this.props.Document;
@@ -360,7 +360,7 @@ export class PresBox extends React.Component<FieldViewProps> {
         return this.props.ScreenToLocalTransform().translate(-10, -50);// listBox padding-left and pres-box-cont minHeight
     }
     render() {
-        this.initializeScaleViews(this.childDocs, NumCast(this.props.Document.viewType));
+        this.initializeScaleViews(this.childDocs, NumCast(this.props.Document._viewType));
         return (
             <div className="presBox-cont" onContextMenu={this.specificContextMenu}>
                 <div className="presBox-buttons">
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 52b4f391a..066c51654 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -55,12 +55,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
 
     videoLoad = () => {
         const aspect = this.player!.videoWidth / this.player!.videoHeight;
-        const nativeWidth = (this.Document.nativeWidth || 0);
-        const nativeHeight = (this.Document.nativeHeight || 0);
+        const nativeWidth = (this.Document._nativeWidth || 0);
+        const nativeHeight = (this.Document._nativeHeight || 0);
         if (!nativeWidth || !nativeHeight) {
-            if (!this.Document.nativeWidth) this.Document.nativeWidth = this.player!.videoWidth;
-            this.Document.nativeHeight = (this.Document.nativeWidth || 0) / aspect;
-            this.Document.height = (this.Document.width || 0) / aspect;
+            if (!this.Document._nativeWidth) this.Document._nativeWidth = this.player!.videoWidth;
+            this.Document._nativeHeight = (this.Document._nativeWidth || 0) / aspect;
+            this.Document._height = (this.Document._width || 0) / aspect;
         }
         if (!this.Document.duration) this.Document.duration = this.player!.duration;
     }
@@ -101,11 +101,11 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
     }
 
     @action public Snapshot() {
-        const width = this.Document.width || 0;
-        const height = this.Document.height || 0;
+        const width = this.Document._width || 0;
+        const height = this.Document._height || 0;
         const canvas = document.createElement('canvas');
         canvas.width = 640;
-        canvas.height = 640 * (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1);
+        canvas.height = 640 * (this.Document._nativeHeight || 0) / (this.Document._nativeWidth || 1);
         const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
         if (ctx) {
             ctx.rect(0, 0, canvas.width, canvas.height);
@@ -117,7 +117,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
         if (!this._videoRef) { // can't find a way to take snapshots of videos
             const b = Docs.Create.ButtonDocument({
                 x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
-                width: 150, height: 50, title: (this.Document.currentTimecode || 0).toString()
+                _width: 150, _height: 50, title: (this.Document.currentTimecode || 0).toString()
             });
             b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.Document.currentTimecode || 0)}`);
         } else {
@@ -130,7 +130,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
                     const url = this.choosePath(Utils.prepend(returnedFilename));
                     const imageSummary = Docs.Create.ImageDocument(url, {
                         x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
-                        width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-"
+                        _width: 150, _height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-"
                     });
                     imageSummary.isButton = true;
                     this.props.addDocument && this.props.addDocument(imageSummary);
@@ -151,12 +151,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
 
         if (this.youtubeVideoId) {
             const youtubeaspect = 400 / 315;
-            const nativeWidth = (this.Document.nativeWidth || 0);
-            const nativeHeight = (this.Document.nativeHeight || 0);
+            const nativeWidth = (this.Document._nativeWidth || 0);
+            const nativeHeight = (this.Document._nativeHeight || 0);
             if (!nativeWidth || !nativeHeight) {
-                if (!this.Document.nativeWidth) this.Document.nativeWidth = 600;
-                this.Document.nativeHeight = (this.Document.nativeWidth || 0) / youtubeaspect;
-                this.Document.height = (this.Document.width || 0) / youtubeaspect;
+                if (!this.Document._nativeWidth) this.Document._nativeWidth = 600;
+                this.Document._nativeHeight = (this.Document._nativeWidth || 0) / youtubeaspect;
+                this.Document._height = (this.Document._width || 0) / youtubeaspect;
             }
         }
     }
@@ -321,7 +321,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
         const style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
         const 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={(this.Document._nativeWidth || 640)} height={(this.Document._nativeHeight || 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}`} />;
     }
 
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f21f9ff46..102db8810 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -39,12 +39,12 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
         const field = Cast(this.props.Document[this.props.fieldKey], WebField);
         if (field && field.url.href.indexOf("youtube") !== -1) {
             const youtubeaspect = 400 / 315;
-            const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
-            const nativeHeight = NumCast(this.layoutDoc.nativeHeight);
+            const nativeWidth = NumCast(this.layoutDoc._nativeWidth);
+            const nativeHeight = NumCast(this.layoutDoc._nativeHeight);
             if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
-                if (!nativeWidth) this.layoutDoc.nativeWidth = 600;
-                this.layoutDoc.nativeHeight = NumCast(this.layoutDoc.nativeWidth) / youtubeaspect;
-                this.layoutDoc.height = NumCast(this.layoutDoc.width) / youtubeaspect;
+                if (!nativeWidth) this.layoutDoc._nativeWidth = 600;
+                this.layoutDoc._nativeHeight = NumCast(this.layoutDoc._nativeWidth) / youtubeaspect;
+                this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
             }
         }
 
@@ -87,8 +87,8 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
             x: NumCast(this.props.Document.x),
             y: NumCast(this.props.Document.y),
             title: url,
-            width: 200,
-            height: 70,
+            _width: 200,
+            _height: 70,
             documentText: "@@@" + url
         });
 
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 6599c0e3c..d8b340db6 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -11,10 +11,11 @@ import "./Annotation.scss";
 
 interface IAnnotationProps {
     anno: Doc;
-    extensionDoc: Doc;
     addDocTab: (document: Doc, dataDoc: Opt<Doc>, where: string) => boolean;
     pinToPres: (document: Doc) => void;
     focus: (doc: Doc) => void;
+    dataDoc: Doc;
+    fieldKey: string;
 }
 
 export default class Annotation extends React.Component<IAnnotationProps> {
@@ -29,10 +30,11 @@ interface IRegionAnnotationProps {
     y: number;
     width: number;
     height: number;
-    extensionDoc: Doc;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
     pinToPres: (document: Doc) => void;
     document: Doc;
+    dataDoc: Doc;
+    fieldKey: string;
 }
 
 @observer
@@ -62,12 +64,12 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     }
 
     deleteAnnotation = () => {
-        const annotation = DocListCast(this.props.extensionDoc.annotations);
+        const annotation = DocListCast(this.props.dataDoc[this.props.fieldKey + "-annotations"]);
         const group = FieldValue(Cast(this.props.document.group, Doc));
         if (group) {
             if (annotation.indexOf(group) !== -1) {
                 const newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc)));
-                this.props.extensionDoc.annotations = new List<Doc>(newAnnotations);
+                this.props.dataDoc[this.props.fieldKey + "-annotations"] = new List<Doc>(newAnnotations);
             }
 
             DocListCast(group.annotations).forEach(anno => anno.delete = true);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index d6829937e..a00d2a7f4 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -113,8 +113,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     private _coverPath: any;
 
     @computed get allAnnotations() {
-        return this.extensionDoc ? DocListCast(this.extensionDoc.annotations).filter(
-            anno => this._script.run({ this: anno }, console.log, true).result) : [];
+        return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]).filter(
+            anno => this._script.run({ this: anno }, console.log, true).result);
     }
 
     @computed get nonDocAnnotations() {
@@ -208,8 +208,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         await this.initialLoad();
 
         this._annotationReactionDisposer = reaction(
-            () => this.extensionDoc && DocListCast(this.extensionDoc.annotations),
-            annotations => annotations && annotations.length && (this._annotations = annotations),
+            () => DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]),
+            annotations => annotations?.length && (this._annotations = annotations),
             { fireImmediately: true });
 
         this._filterReactionDisposer = reaction(
@@ -271,8 +271,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
             const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, title: "Annotation on " + this.Document.title });
             if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
             if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
-            if (anno.style.height) annoDoc.height = parseInt(anno.style.height);
-            if (anno.style.width) annoDoc.width = parseInt(anno.style.width);
+            if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
+            if (anno.style.width) annoDoc._width = parseInt(anno.style.width);
             annoDoc.group = mainAnnoDoc;
             annoDoc.isButton = true;
             annoDocs.push(annoDoc);
@@ -286,8 +286,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
                 const annoDoc = new Doc();
                 if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
                 if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
-                if (anno.style.height) annoDoc.height = parseInt(anno.style.height);
-                if (anno.style.width) annoDoc.width = parseInt(anno.style.width);
+                if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
+                if (anno.style.width) annoDoc._width = parseInt(anno.style.width);
                 annoDoc.group = mainAnnoDoc;
                 annoDoc.backgroundColor = color;
                 annoDocs.push(annoDoc);
@@ -558,7 +558,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     startDrag = (e: PointerEvent, ele: HTMLElement): void => {
         e.preventDefault();
         e.stopPropagation();
-        const targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "Note linked to " + this.props.Document.title });
+        const targetDoc = Docs.Create.TextDocument({ _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
         const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection
         if (annotationDoc) {
             DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
@@ -573,11 +573,11 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         const data = Doc.MakeDelegate(Doc.GetProto(this.props.Document));
         data.title = StrCast(data.title) + "_snippet";
         view.proto = data;
-        view.nativeHeight = marquee.height;
-        view.height = (this.Document[WidthSym]() / (this.Document.nativeWidth || 1)) * marquee.height;
-        view.nativeWidth = this.Document.nativeWidth;
+        view._nativeHeight = marquee.height;
+        view._height = (this.Document[WidthSym]() / (this.Document._nativeWidth || 1)) * marquee.height;
+        view._nativeWidth = this.Document._nativeWidth;
         view.startY = marquee.top;
-        view.width = this.Document[WidthSym]();
+        view._width = this.Document[WidthSym]();
         DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view]), 0, 0);
     }
 
@@ -598,12 +598,12 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     getCoverImage = () => {
         if (!this.props.Document[HeightSym]() || !this.props.Document.nativeHeight) {
             setTimeout((() => {
-                this.Document.height = this.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
-                this.Document.nativeHeight = (this.Document.nativeWidth || 0) * this._coverPath.height / this._coverPath.width;
+                this.Document._height = this.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
+                this.Document._nativeHeight = (this.Document._nativeWidth || 0) * this._coverPath.height / this._coverPath.width;
             }).bind(this), 0);
         }
-        const nativeWidth = (this.Document.nativeWidth || 0);
-        const nativeHeight = (this.Document.nativeHeight || 0);
+        const nativeWidth = (this.Document._nativeWidth || 0);
+        const nativeHeight = (this.Document._nativeHeight || 0);
         const resolved = Utils.prepend(this._coverPath.path);
         return <img key={resolved} src={resolved} 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` }} />;
@@ -623,12 +623,12 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         TraceMobx();
         return <div className="pdfViewer-annotationLayer" style={{ height: (this.Document.nativeHeight || 0), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
             {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
-                <Annotation {...this.props} focus={this.props.focus} extensionDoc={this.extensionDoc!} anno={anno} key={`${anno[Id]}-annotation`} />)}
+                <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc!} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)}
         </div>;
     }
     overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
-    panelWidth = () => (this.Document.scrollHeight || this.Document.nativeHeight || 0);
-    panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document.nativeWidth || 0);
+    panelWidth = () => (this.Document.scrollHeight || this.Document._nativeHeight || 0);
+    panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document._nativeWidth || 0);
     @computed get overlayLayer() {
         return <div className={`pdfViewer-overlay${InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : ""}`} id="overlay" style={{ transform: `scale(${this._zoomed})` }}>
             <CollectionFreeFormView {...this.props}
@@ -675,20 +675,19 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     contentZoom = () => this._zoomed;
     render() {
         TraceMobx();
-        return !this.extensionDoc ? (null) :
-            <div className={"pdfViewer" + (this._zoomed !== 1 ? "-zoomed" : "")} ref={this._mainCont}
-                onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
-                style={{
-                    width: !this.props.Document.fitWidth ? NumCast(this.props.Document.nativeWidth) : `${100 / this.contentScaling}%`,
-                    height: !this.props.Document.fitWidth ? NumCast(this.props.Document.nativeHeight) : `${100 / this.contentScaling}%`,
-                    transform: `scale(${this.props.ContentScaling()})`
-                }}  >
-                {this.pdfViewerDiv}
-                {this.overlayLayer}
-                {this.annotationLayer}
-                {this.standinViews}
-                <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
-            </div >;
+        return <div className={"pdfViewer" + (this._zoomed !== 1 ? "-zoomed" : "")} ref={this._mainCont}
+            onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
+            style={{
+                width: !this.props.Document._fitWidth ? NumCast(this.props.Document._nativeWidth) : `${100 / this.contentScaling}%`,
+                height: !this.props.Document._fitWidth ? NumCast(this.props.Document._nativeHeight) : `${100 / this.contentScaling}%`,
+                transform: `scale(${this.props.ContentScaling()})`
+            }}  >
+            {this.pdfViewerDiv}
+            {this.overlayLayer}
+            {this.annotationLayer}
+            {this.standinViews}
+            <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
+        </div >;
     }
 }
 
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 92dc0f3d2..dad55e1fd 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -161,12 +161,12 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
             return (null);
         }
 
-        const propDocWidth = NumCast(this.layoutDoc.nativeWidth);
-        const propDocHeight = NumCast(this.layoutDoc.nativeHeight);
-        const scale = () => 175 / NumCast(this.layoutDoc.nativeWidth, 175);
+        const propDocWidth = NumCast(this.layoutDoc._nativeWidth);
+        const propDocHeight = NumCast(this.layoutDoc._nativeHeight);
+        const scale = () => 175 / NumCast(this.layoutDoc._nativeWidth, 175);
         return (
             <div className="presElementBox-embedded" style={{
-                height: propDocHeight === 0 ? NumCast(this.layoutDoc.height) - NumCast(this.layoutDoc.collapsedHeight) : propDocHeight * scale(),
+                height: propDocHeight === 0 ? NumCast(this.layoutDoc._height) - NumCast(this.layoutDoc.collapsedHeight) : propDocHeight * scale(),
                 width: propDocWidth === 0 ? "auto" : propDocWidth * scale(),
             }}>
                 <ContentFittingDocumentView
@@ -192,7 +192,7 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
     }
 
     render() {
-        const treecontainer = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.viewType === CollectionViewType.Tree;
+        const treecontainer = this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Tree;
         const className = "presElementBox-item" + (this.currentIndex === this.indexInPres ? " presElementBox-selected" : "");
         const pbi = "presElementBox-interaction";
         return (
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index f7ae83b2f..be13dae03 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -217,16 +217,16 @@ export class SearchBox extends React.Component {
             doc.x = x;
             doc.y = y;
             const size = 200;
-            const aspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1);
+            const aspect = NumCast(doc._nativeHeight) / NumCast(doc._nativeWidth, 1);
             if (aspect > 1) {
-                doc.height = size;
-                doc.width = size / aspect;
+                doc._height = size;
+                doc._width = size / aspect;
             } else if (aspect > 0) {
-                doc.width = size;
-                doc.height = size * aspect;
+                doc._width = size;
+                doc._height = size * aspect;
             } else {
-                doc.width = size;
-                doc.height = size;
+                doc._width = size;
+                doc._height = size;
             }
             x += 250;
             if (x > 1000) {
@@ -234,7 +234,7 @@ export class SearchBox extends React.Component {
                 y += 300;
             }
         }
-        return Docs.Create.TreeDocument(docs, { width: 200, height: 400, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` });
+        return Docs.Create.TreeDocument(docs, { _width: 200, _height: 400, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` });
     }
 
     @action.bound
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 21a4560cf..8aea737f0 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -68,11 +68,11 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
     getOnClick({ col, target }: { col: Doc, target: Doc }) {
         return () => {
             col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
-            if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-                const newPanX = NumCast(target.x) + NumCast(target.width) / 2;
-                const newPanY = NumCast(target.y) + NumCast(target.height) / 2;
-                col.panX = newPanX;
-                col.panY = newPanY;
+            if (NumCast(col._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+                const newPanX = NumCast(target.x) + NumCast(target._width) / 2;
+                const newPanY = NumCast(target.y) + NumCast(target._height) / 2;
+                col._panX = newPanX;
+                col._panY = newPanY;
             }
             CollectionDockingView.AddRightSplit(col, undefined);
         };
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 0b0280519..dab4de110 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -55,7 +55,7 @@ class Uploader extends React.Component {
                     const json = await res.json();
                     json.map(async (file: any) => {
                         const path = window.location.origin + file;
-                        const doc = Docs.Create.ImageDocument(path, { nativeWidth: 200, width: 200, title: name });
+                        const doc = Docs.Create.ImageDocument(path, { _nativeWidth: 200, _width: 200, title: name });
 
                         this.status = "getting user document";
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 4b7f10f91..16f67d419 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -162,8 +162,8 @@ export class Doc extends RefField {
 
     private [Self] = this;
     private [SelfProxy]: any;
-    public [WidthSym] = () => NumCast(this[SelfProxy].width);
-    public [HeightSym] = () => NumCast(this[SelfProxy].height);
+    public [WidthSym] = () => NumCast(this[SelfProxy]._width);
+    public [HeightSym] = () => NumCast(this[SelfProxy]._height);
 
     [ToScriptString]() {
         return "invalid";
@@ -447,19 +447,18 @@ export namespace Doc {
         if (!WillExpandTemplateLayout(templateLayoutDoc, dataDoc) || !dataDoc) return templateLayoutDoc;
 
         const templateField = StrCast(templateLayoutDoc.isTemplateForField);  // the field that the template renders
-        const extensionDoc = fieldExtensionDoc(dataDoc, templateField); // an extension doc for the field that the template renders
         // First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc
         // using the template layout doc's id as the field key.
         // If it doesn't find the expanded layout, then it makes a delegate of the template layout and
         // saves it on the data doc indexed by the template layout's id.
         //
-        const expandedLayoutFieldKey = "Layout[" + templateLayoutDoc[Id] + "]";
-        const expandedTemplateLayout = extensionDoc?.[expandedLayoutFieldKey];
-        if (expandedTemplateLayout === undefined && extensionDoc) {
+        const expandedLayoutFieldKey = templateField + "-layout[" + templateLayoutDoc[Id] + "]";
+        const expandedTemplateLayout = dataDoc?.[expandedLayoutFieldKey];
+        if (expandedTemplateLayout === undefined) {
             setTimeout(() => {
-                if (!extensionDoc[expandedLayoutFieldKey]) {
+                if (!dataDoc[expandedLayoutFieldKey]) {
                     const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]");
-                    extensionDoc[expandedLayoutFieldKey] = newLayoutDoc;
+                    dataDoc[expandedLayoutFieldKey] = newLayoutDoc;
                     newLayoutDoc.resolvedDataDoc = dataDoc;
                 }
             }, 0);
@@ -473,26 +472,6 @@ export namespace Doc {
         const resolvedDataDoc = containerDataDoc === containerDoc || !containerDataDoc ? undefined : Doc.GetDataDoc(containerDataDoc);
         return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc), data: resolvedDataDoc };
     }
-
-    //
-    // Resolves a reference to a field by returning 'doc' if no field extension is specified,
-    // otherwise, it returns the extension document stored in doc.<fieldKey>_ext.
-    // This mechanism allows any fields to be extended with an extension document that can
-    // be used to capture field-specific metadata.  For example, an image field can be extended
-    // to store annotations, ink, and other data.
-    //
-    export function fieldExtensionDoc(doc: Doc, fieldKey: string) {
-        if (!fieldKey) return undefined;
-        const extension = doc[fieldKey + "_ext"];
-        if (doc instanceof Doc && extension === undefined) {
-            setTimeout(() => CreateDocumentExtensionForField(doc, fieldKey), 0);
-        }
-        return extension ? extension as Doc : undefined;
-    }
-    export function fieldExtensionDocSync(doc: Doc, fieldKey: string) {
-        return (doc[fieldKey + "_ext"] as Doc) || CreateDocumentExtensionForField(doc, fieldKey);
-    }
-
     export function CreateDocumentExtensionForField(doc: Doc, fieldKey: string) {
         let proto: Doc | undefined = doc;
         while (proto && !Doc.IsPrototype(proto) && proto.proto) {
@@ -586,8 +565,8 @@ export namespace Doc {
     export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined = undefined) {
         if (!templateDoc) {
             target.layout = undefined;
-            target.nativeWidth = undefined;
-            target.nativeHeight = undefined;
+            target._nativeWidth = undefined;
+            target._nativeHeight = undefined;
             target.onClick = undefined;
             target.type = undefined;
             return;
@@ -648,12 +627,12 @@ export namespace Doc {
         const doc1Layout = Doc.Layout(doc1);
         const x2 = NumCast(doc2.x) - clusterDistance;
         const y2 = NumCast(doc2.y) - clusterDistance;
-        const w2 = NumCast(doc2Layout.width) + clusterDistance;
-        const h2 = NumCast(doc2Layout.height) + clusterDistance;
+        const w2 = NumCast(doc2Layout._width) + clusterDistance;
+        const h2 = NumCast(doc2Layout._height) + clusterDistance;
         const x = NumCast(doc1.x) - clusterDistance;
         const y = NumCast(doc1.y) - clusterDistance;
-        const w = NumCast(doc1Layout.width) + clusterDistance;
-        const h = NumCast(doc1Layout.height) + clusterDistance;
+        const w = NumCast(doc1Layout._width) + clusterDistance;
+        const 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 });
     }
 
@@ -812,7 +791,7 @@ Scripting.addGlobal(function matchFieldValue(doc: Doc, key: string, value: any)
     return fieldStr == value;
 });
 Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: string) {
-    const docFilters = Cast(container.docFilter, listSpec("string"), []);
+    const docFilters = Cast(container._docFilter, listSpec("string"), []);
     for (let i = 0; i < docFilters.length; i += 3) {
         if (docFilters[i] === key && docFilters[i + 1] === value) {
             docFilters.splice(i, 3);
@@ -823,7 +802,7 @@ Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: an
         docFilters.push(key);
         docFilters.push(value)
         docFilters.push(modifiers);
-        container.docFilter = new List<string>(docFilters);
+        container._docFilter = new List<string>(docFilters);
     }
     const docFilterText = Doc.MakeDocFilter(docFilters);
     container.viewSpecScript = docFilterText ? ScriptField.MakeFunction(docFilterText, { doc: Doc.name }) : undefined;
diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts
index 682206aa2..6f34c82db 100644
--- a/src/new_fields/RichTextUtils.ts
+++ b/src/new_fields/RichTextUtils.ts
@@ -273,7 +273,7 @@ export namespace RichTextUtils {
             const guid = Utils.GenerateDeterministicGuid(src);
             const backingDocId = StrCast(textNote[guid]);
             if (!backingDocId) {
-                const backingDoc = Docs.Create.ImageDocument(src, { width: 300, height: 300 });
+                const backingDoc = Docs.Create.ImageDocument(src, { _width: 300, _height: 300 });
                 DocumentView.makeCustomViewClicked(backingDoc, undefined);
                 docid = backingDoc[Id];
                 textNote[guid] = docid;
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 180c52b3d..44ba6d65c 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -8,16 +8,18 @@ export const documentSchema = createSchema({
     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",           // "
+    _dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias" or "copy")
+    _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",           // "
+    _freeformLayoutEngine: "string",// the string ID for the layout engine to use to layout freeform view documents
+    _LODdisable: "boolean",      // whether to disbale LOD switching for CollectionFreeFormViews
     color: "string",            // foreground color of document
     backgroundColor: "string",  // background color of document
     opacity: "number",          // opacity of document
     creationDate: DateField,     // when the document was created
     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)
     onPointerDown: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
@@ -56,7 +58,6 @@ export const documentSchema = createSchema({
     xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     LODarea: "number",          // area (width*height) where CollectionFreeFormViews switch from a label to rendering contents
-    LODdisable: "boolean",      // whether to disbale LOD switching for CollectionFreeFormViews
     letterSpacing: "string",
     textTransform: "string"
 });
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 3255c6172..7d093b6ea 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -7,6 +7,7 @@ import { ObjectField } from "./ObjectField";
 import { action, trace } from "mobx";
 import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
 import { DocServer } from "../client/DocServer";
+import { props } from "bluebird";
 
 function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
@@ -35,6 +36,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
         target[prop] = value;
         return true;
     }
+
     if (typeof prop === "symbol") {
         target[prop] = value;
         return true;
@@ -98,11 +100,39 @@ export function makeEditable() {
     _setter = _setterImpl;
 }
 
-export function setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+let layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
+    "LODdisable", "dropAction", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin"];
+export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
+    let prop = in_prop;
+    if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" &&
+        ((prop as string).startsWith("_") || layoutProps.includes(prop))) {
+        if (!prop.startsWith("_")) {
+            console.log(prop + " is deprecated - switch to _" + prop);
+            prop = "_" + prop;
+        }
+        const resolvedLayout = getFieldImpl(target, getFieldImpl(target, "layoutKey", receiver), receiver); // + "-" + (prop as string).substring(1);
+        if (resolvedLayout instanceof Doc) {
+            resolvedLayout[prop] = value;
+            return true;
+        }
+    }
     return _setter(target, prop, value, receiver);
 }
 
-export function getter(target: any, prop: string | symbol | number, receiver: any): any {
+export function getter(target: any, in_prop: string | symbol | number, receiver: any): any {
+    let prop = in_prop;
+    if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" &&
+        ((prop as string).startsWith("_") || layoutProps.includes(prop))) {
+        if (!prop.startsWith("_")) {
+            console.log(prop + " is deprecated - switch to _" + prop);
+            prop = "_" + prop;
+        }
+        const resolvedLayout = getFieldImpl(target, getFieldImpl(target, "layoutKey", receiver), receiver); // + "-" + (prop as string).substring(1);
+        if (resolvedLayout instanceof Doc) {
+            return resolvedLayout[prop];
+        }
+        // prop = getFieldImpl(target, "layoutKey", receiver) + "-" + (prop as string).substring(1);
+    }
     if (prop === "then") {//If we're being awaited
         return undefined;
     }
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index c7b795bbd..f16bbfd14 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -85,13 +85,14 @@ def write_collection(parse_results, display_fields, storage_key, viewType):
             "proto": protofy(data_doc["_id"]),
             "x": 10,
             "y": 10,
-            "width": 900,
-            "height": 600,
-            "panX": 0,
-            "panY": 0,
+            "_width": 900,
+            "_height": 600,
+            "_panX": 0,
+            "_panY": 0,
             "zIndex": 2,
             "libraryBrush": False,
-            "viewType": viewType
+            "_viewType": viewType,
+            "_LODdisable": True
         },
         "__type": "Doc"
     }
@@ -100,9 +101,8 @@ def write_collection(parse_results, display_fields, storage_key, viewType):
     fields[storage_key] = listify(proxify_guids(view_guids))
     fields["schemaColumns"] = listify(display_fields)
     fields["backgroundColor"] = "white"
-    fields["viewType"] = 2
+    fields["_viewType"] = 2
     fields["author"] = "Bill Buxton"
-    fields["LODdisable"] = True
     fields["creationDate"] = {
         "date": datetime.datetime.utcnow().microsecond,
         "__type": "date"
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 406d71085..361cf9216 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -44,18 +44,18 @@ export class CurrentUserUtils {
     // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
     static setupCreatorButtons(doc: Doc, buttons?: string[]) {
         const notes = CurrentUserUtils.setupNoteTypes(doc);
-        doc.noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 });
+        doc.noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", _height: 75 });
         doc.activePen = doc;
         const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
-            { title: "collection", icon: "folder", ignoreClick: true, drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, LODdisable: true, width: 150, height: 100, title: "freeform" })' },
-            { title: "preview", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(ComputedField.MakeFunction("selectedDocs(this,true,[_last_])?.[0]"), { width: 250, height: 250, title: "container" })' },
+            { title: "collection", icon: "folder", ignoreClick: true, drag: 'Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _nativeHeight: undefined, _LODdisable: true, _width: 150, _height: 100, title: "freeform" })' },
+            { title: "preview", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(ComputedField.MakeFunction("selectedDocs(this,true,[_last_])?.[0]"), { _width: 250, _height: 250, title: "container" })' },
             { title: "todo item", icon: "check", ignoreClick: true, drag: 'getCopy(this.dragFactory, true)', dragFactory: notes[notes.length - 1] },
-            { title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' },
-            { title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' },
-            { title: "record", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { width: 200, title: "ready to record audio" })` },
-            { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' },
-            { title: "presentation", icon: "tv", ignoreClick: true, drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })' },
-            { title: "import folder", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' },
+            { title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", {_width: 300, _height: 300, title: "New Webpage" })' },
+            { title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 200, title: "an image of a cat" })' },
+            { title: "record", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
+            { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: "Button" })' },
+            { title: "presentation", icon: "tv", ignoreClick: true, drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { _width: 200, _height: 500, title: "a presentation trail" })' },
+            { title: "import folder", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
             { title: "mobile view", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' },
             { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen,  this)`, activePen: doc },
             { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
@@ -65,7 +65,7 @@ export class CurrentUserUtils {
             { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc },
         ];
         return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({
-            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
+            _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
             onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
             ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen,
             backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory,
@@ -103,7 +103,7 @@ export class CurrentUserUtils {
             { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc },
         ];
         return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({
-            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
+            _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
             onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
             ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen,
             backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory,
@@ -114,11 +114,11 @@ export class CurrentUserUtils {
         const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, clipboard?: Doc, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
             { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen,  this)`, activePen: doc },
             { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
-            { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { width: 300, height: 300 }), backgroundColor: "orange", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
-            { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", clipboard: Docs.Create.FreeformDocument([], { width: 300, height: 300 }), backgroundColor: "orange", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
+            { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300 }), backgroundColor: "orange", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
+            { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300 }), backgroundColor: "orange", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
         ];
         return docProtoData.map(data => Docs.Create.FontIconDocument({
-            nativeWidth: 10, nativeHeight: 10, width: 10, height: 10, dropAction: data.pointerDown ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
+            _nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, _dropAction: data.pointerDown ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
             onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
             clipboard: data.clipboard,
             onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined,
@@ -130,7 +130,7 @@ export class CurrentUserUtils {
     static setupThumbDoc(userDoc: Doc) {
         if (!userDoc.thumbDoc) {
             userDoc.thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), {
-                width: 100, height: 50, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5, isExpanded: true, backgroundColor: "white"
+                _width: 100, _height: 50, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", autoHeight: true, _yMargin: 5, isExpanded: true, backgroundColor: "white"
             });
         }
         return userDoc.thumbDoc;
@@ -138,7 +138,7 @@ export class CurrentUserUtils {
 
     static setupMobileDoc(userDoc: Doc) {
         return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), {
-            columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5
+            columnWidth: 100, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", autoHeight: true, _yMargin: 5
         });
     }
 
@@ -146,19 +146,19 @@ export class CurrentUserUtils {
     static setupToolsPanel(sidebarContainer: Doc, doc: Doc) {
         // 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: 5
+            _width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons",
+            dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), _yMargin: 5
         });
         // setup a color picker
         const color = Docs.Create.ColorDocument({
-            title: "color picker", width: 300, dropAction: "alias", forceActive: true, removeDropProperties: new List<string>(["dropAction", "forceActive"])
+            title: "color picker", _width: 300, _dropAction: "alias", forceActive: true, removeDropProperties: new List<string>(["dropAction", "forceActive"])
         });
 
         return Docs.Create.ButtonDocument({
-            width: 35, height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Tools", fontSize: 10, targetContainer: sidebarContainer,
+            _width: 35, _height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Tools", fontSize: 10, targetContainer: sidebarContainer,
             letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
             sourcePanel: Docs.Create.StackingDocument([dragCreators, color], {
-                width: 500, lockedPosition: true, chromeStatus: "disabled", title: "tools stack"
+                _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack"
             }),
             onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel"),
         });
@@ -168,23 +168,23 @@ export class CurrentUserUtils {
     static setupLibraryPanel(sidebarContainer: Doc, doc: Doc) {
         // setup workspaces library item
         doc.workspaces = Docs.Create.TreeDocument([], {
-            title: "WORKSPACES", height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, backgroundColor: "#eeeeee"
+            title: "WORKSPACES", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, backgroundColor: "#eeeeee"
         });
 
         doc.documents = Docs.Create.TreeDocument([], {
-            title: "DOCUMENTS", height: 42, forceActive: true, boxShadow: "0 0", preventTreeViewOpen: true, lockedPosition: true, backgroundColor: "#eeeeee"
+            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", height: 75, forceActive: true, boxShadow: "0 0", preventTreeViewOpen: 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: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Library", fontSize: 10,
+            _width: 50, _height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Library", fontSize: 10,
             letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
             sourcePanel: 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, boxShadow: "0 0",
+                title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, _dropAction: "alias", lockedPosition: true, boxShadow: "0 0",
             }),
             targetContainer: sidebarContainer,
             onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
@@ -194,7 +194,7 @@ export class CurrentUserUtils {
     // setup the Search button which will display the search panel.  
     static setupSearchPanel(sidebarContainer: Doc) {
         return Docs.Create.ButtonDocument({
-            width: 50, height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Search", fontSize: 10,
+            _width: 50, _height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Search", fontSize: 10,
             letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
             sourcePanel: Docs.Create.QueryDocument({
                 title: "search stack", ignoreClick: true
@@ -208,7 +208,7 @@ export class CurrentUserUtils {
     // setup the list of sidebar mode buttons which determine what is displayed in the sidebar
     static setupSidebarButtons(doc: Doc) {
         doc.sidebarContainer = new Doc();
-        (doc.sidebarContainer as Doc).chromeStatus = "disabled";
+        (doc.sidebarContainer as Doc)._chromeStatus = "disabled";
         (doc.sidebarContainer as Doc).onClick = ScriptField.MakeScript("freezeSidebar()");
 
         doc.ToolsBtn = this.setupToolsPanel(doc.sidebarContainer as Doc, doc);
@@ -217,21 +217,21 @@ export class CurrentUserUtils {
 
         // Finally, setup the list of buttons to display in the sidebar
         doc.sidebarButtons = Docs.Create.StackingDocument([doc.SearchBtn as Doc, doc.LibraryBtn as Doc, doc.ToolsBtn as Doc], {
-            width: 500, height: 80, boxShadow: "0 0", sectionFilter: "title", hideHeadings: true, ignoreClick: true,
-            backgroundColor: "rgb(100, 100, 100)", chromeStatus: "disabled", title: "library stack",
-            yMargin: 10,
+            _width: 500, _height: 80, boxShadow: "0 0", sectionFilter: "title", hideHeadings: true, ignoreClick: true,
+            backgroundColor: "rgb(100, 100, 100)", _chromeStatus: "disabled", title: "library stack",
+            _yMargin: 10,
         });
     }
 
     /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
     static setupExpandingButtons(doc: Doc) {
         doc.undoBtn = Docs.Create.FontIconDocument(
-            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("undo()"), removeDropProperties: new List<string>(["dropAction"]), title: "undo button", icon: "undo-alt" });
+            { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onClick: ScriptField.MakeScript("undo()"), removeDropProperties: new List<string>(["dropAction"]), title: "undo button", icon: "undo-alt" });
         doc.redoBtn = Docs.Create.FontIconDocument(
-            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), removeDropProperties: new List<string>(["dropAction"]), title: "redo button", icon: "redo-alt" });
+            { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), removeDropProperties: new List<string>(["dropAction"]), title: "redo button", icon: "redo-alt" });
 
         doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc], {
-            title: "expanding buttons", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0",
+            title: "expanding buttons", _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0",
             backgroundColor: "black", preventTreeViewOpen: true, forceActive: true, lockedPosition: true,
             dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name })
         });
@@ -240,7 +240,7 @@ export class CurrentUserUtils {
     // sets up the default set of documents to be shown in the Overlay layer
     static setupOverlays(doc: Doc) {
         doc.overlays = Docs.Create.FreeformDocument([], { title: "Overlays", backgroundColor: "#aca3a6" });
-        doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, width: 500, height: 370, title: "Link Follower" });
+        doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, _width: 500, _height: 370, title: "Link Follower" });
         Doc.AddDocToList(doc.overlays as Doc, "data", doc.linkFollowBox as Doc);
     }
 
-- 
cgit v1.2.3-70-g09d2


From ac9492714eb9264b67d8033d8753fc6ed51b4e29 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 29 Jan 2020 13:54:27 -0500
Subject: ui cleanup for pivot viewer/chrome.  error fixes.  fixed layout of
 flyouts

---
 solr-8.3.1/server/solr/dash/core.properties        |   2 +-
 src/client/util/ClientUtils.ts.temp                |   3 -
 src/client/util/RichTextSchema.tsx                 |   2 +-
 src/client/views/DocumentButtonBar.tsx             | 100 +++++++++++++++++----
 src/client/views/MainView.tsx                      |   4 +-
 src/client/views/MetadataEntryMenu.scss            |   4 +
 src/client/views/MetadataEntryMenu.tsx             |   2 +-
 src/client/views/TemplateMenu.tsx                  |  66 +-------------
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 .../views/collections/CollectionPivotView.tsx      |   2 +-
 src/client/views/collections/CollectionSubView.tsx |   6 +-
 .../views/collections/CollectionTreeView.tsx       |   4 +-
 .../views/collections/CollectionViewChromes.scss   |  35 +++++++-
 .../views/collections/CollectionViewChromes.tsx    |  94 ++++++++++---------
 .../views/collections/ParentDocumentSelector.tsx   |  11 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 .../CollectionMulticolumnView.tsx                  |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   4 +-
 src/client/views/nodes/PresBox.tsx                 |   4 +-
 src/client/views/search/SearchBox.scss             |   1 +
 src/new_fields/Doc.ts                              |  11 +--
 .../authentication/models/current_user_utils.ts    |   5 +-
 22 files changed, 209 insertions(+), 159 deletions(-)
 delete mode 100644 src/client/util/ClientUtils.ts.temp

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

diff --git a/solr-8.3.1/server/solr/dash/core.properties b/solr-8.3.1/server/solr/dash/core.properties
index 0912a8303..70081ebc6 100644
--- a/solr-8.3.1/server/solr/dash/core.properties
+++ b/solr-8.3.1/server/solr/dash/core.properties
@@ -1,5 +1,5 @@
 #Written by CorePropertiesLocator
-#Wed Jan 08 14:44:34 UTC 2020
+#Wed Jan 29 14:30:24 UTC 2020
 name=dash
 config=solrconfig.xml
 schema=schema.xml
diff --git a/src/client/util/ClientUtils.ts.temp b/src/client/util/ClientUtils.ts.temp
deleted file mode 100644
index f9fad5ed9..000000000
--- a/src/client/util/ClientUtils.ts.temp
+++ /dev/null
@@ -1,3 +0,0 @@
-export namespace ClientUtils {
-    export const RELEASE = "mode";
-}
\ No newline at end of file
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 8c9e5e263..f14e09a40 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -695,7 +695,7 @@ export class DashDocCommentView {
             e.stopPropagation();
         };
         this._collapsed.onpointerenter = (e: any) => {
-            DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+            DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
             e.preventDefault();
             e.stopPropagation();
         };
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 4441356dc..6abb7108f 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,27 +1,28 @@
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
 import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, runInAction, computed } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../new_fields/Doc";
+import { Id } from '../../new_fields/FieldSymbols';
 import { RichTextField } from '../../new_fields/RichTextField';
-import { NumCast, StrCast, Cast } from "../../new_fields/Types";
+import { NumCast, StrCast } from "../../new_fields/Types";
 import { emptyFunction } from "../../Utils";
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
-import { DragManager } from "../util/DragManager";
+import RichTextMenu from '../util/RichTextMenu';
 import { UndoManager } from "../util/UndoManager";
-import './DocumentButtonBar.scss';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { ParentDocSelector } from './collections/ParentDocumentSelector';
 import './collections/ParentDocumentSelector.scss';
+import './DocumentButtonBar.scss';
 import { LinkMenu } from "./linking/LinkMenu";
-import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox";
+import { DocumentView } from './nodes/DocumentView';
+import { GoogleRef } from "./nodes/FormattedTextBox";
 import { TemplateMenu } from "./TemplateMenu";
 import { Template, Templates } from "./Templates";
 import React = require("react");
-import { DocumentView } from './nodes/DocumentView';
-import { ParentDocSelector } from './collections/ParentDocumentSelector';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import RichTextMenu from '../util/RichTextMenu';
-import { Id } from '../../new_fields/FieldSymbols';
+import { DragManager } from '../util/DragManager';
+import { MetadataEntryMenu } from './MetadataEntryMenu';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -43,6 +44,7 @@ const fetch: IconProp = "sync-alt";
 @observer
 export class DocumentButtonBar extends React.Component<{ views: (DocumentView | undefined)[], stack?: any }, {}> {
     private _linkButton = React.createRef<HTMLDivElement>();
+    private _dragRef = React.createRef<HTMLDivElement>();
     private _downX = 0;
     private _downY = 0;
     private _pullAnimating = false;
@@ -201,7 +203,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
         const view0 = this.view0;
         const linkCount = view0 && DocListCast(view0.props.Document.links).length;
         return !view0 ? (null) : <div title="Drag(create link) Tap(view links)" className="documentButtonBar-linkFlyout" ref={this._linkButton}>
-            <Flyout anchorPoint={anchorPoints.RIGHT_TOP}
+            <Flyout anchorPoint={anchorPoints.LEFT_TOP}
                 content={<LinkMenu docView={view0} addDocTab={view0.props.addDocTab} changeFlyout={emptyFunction} />}>
                 <div className={"documentButtonBar-linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >
                     {linkCount ? linkCount : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
@@ -210,6 +212,19 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
         </div>;
     }
 
+    @computed
+    get metadataButton() {
+        const view0 = this.view0;
+        return !view0 ? (null) : <div title="Show metadata panel" className="documentButtonBar-linkFlyout" ref={this._linkButton}>
+            <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+                content={<MetadataEntryMenu docs={() => this.props.views.filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction />  /* tfs: @bcz This might need to be the data document? */}>
+                <div className={"documentButtonBar-linkButton-" + "empty"} >
+                    {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" />}
+                </div>
+            </Flyout>
+        </div>;
+    }
+
     @computed
     get contextButton() {
         return !this.view0 ? (null) : <ParentDocSelector Views={this.props.views.filter(v => v).map(v => v as DocumentView)} Document={this.view0.props.Document} addDocTab={(doc, data, where) => {
@@ -220,11 +235,61 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
         }} />;
     }
 
-    render() {
-        if (!this.view0) return (null);
+    private _downx = 0;
+    private _downy = 0;
+    onAliasButtonUp = (e: PointerEvent): void => {
+        document.removeEventListener("pointermove", this.onAliasButtonMoved);
+        document.removeEventListener("pointerup", this.onAliasButtonUp);
+        e.stopPropagation();
+    }
+
+    onAliasButtonDown = (e: React.PointerEvent): void => {
+        this._downx = e.clientX;
+        this._downy = e.clientY;
+        e.stopPropagation();
+        e.preventDefault();
+        document.removeEventListener("pointermove", this.onAliasButtonMoved);
+        document.addEventListener("pointermove", this.onAliasButtonMoved);
+        document.removeEventListener("pointerup", this.onAliasButtonUp);
+        document.addEventListener("pointerup", this.onAliasButtonUp);
+    }
+    onAliasButtonMoved = (e: PointerEvent): void => {
+        if (this._dragRef.current !== null && (Math.abs(e.clientX - this._downx) > 4 || Math.abs(e.clientY - this._downy) > 4)) {
+            document.removeEventListener("pointermove", this.onAliasButtonMoved);
+            document.removeEventListener("pointerup", this.onAliasButtonUp);
+
+            const dragDocView = this.props.views[0]!;
+            const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
+            const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+            dragData.embedDoc = true;
+            dragData.dropAction = "alias";
+            DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, {
+                offsetX: dragData.offset[0],
+                offsetY: dragData.offset[1],
+                hideSource: false
+            });
+        }
+        e.stopPropagation();
+    }
+
+    @computed
+    get templateButton() {
+        const view0 = this.view0;
         const templates: Map<Template, boolean> = new Map();
         Array.from(Object.values(Templates.TemplateList)).map(template =>
             templates.set(template, this.props.views.reduce((checked, doc) => checked || doc?.getLayoutPropStr("show" + template.Name) ? true : false, false as boolean)));
+        return !view0 ? (null) : <div title="Customize layout" className="documentButtonBar-linkFlyout" ref={this._dragRef}>
+            <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+                content={<TemplateMenu docs={this.props.views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
+                <div className={"documentButtonBar-linkButton-" + "empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
+                    {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
+                </div>
+            </Flyout>
+        </div>;
+    }
+
+    render() {
+        if (!this.view0) return (null);
 
         const isText = this.view0.props.Document.data instanceof RichTextField; // bcz: Todo - can't assume layout is using the 'data' field.  need to add fieldKey to DocumentView
         const considerPull = isText && this.considerGoogleDocsPull;
@@ -234,7 +299,13 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
                 {this.linkButton}
             </div>
             <div className="documentButtonBar-button">
-                <TemplateMenu docs={this.props.views.filter(v => v).map(v => v as DocumentView)} templates={templates} />
+                {this.templateButton}
+            </div>
+            <div className="documentButtonBar-button">
+                {this.metadataButton}
+            </div>
+            <div className="documentButtonBar-button">
+                {this.contextButton}
             </div>
             <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
                 {this.considerGoogleDocsPush}
@@ -242,7 +313,6 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
             <div className="documentButtonBar-button" style={{ display: !considerPull ? "none" : "" }}>
                 {this.considerGoogleDocsPull}
             </div>
-            {this.contextButton}
         </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2297c2bb3..87a81504c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import {
-    faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
+    faArrowDown, faBullseye, faFilter, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
     faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt, faPhone, faStamp, faClipboard
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -131,6 +131,8 @@ export class MainView extends React.Component {
         library.add(faLongArrowAltRight);
         library.add(faCheck);
         library.add(faCaretUp);
+        library.add(faFilter);
+        library.add(faBullseye);
         library.add(faArrowDown);
         library.add(faArrowUp);
         library.add(faCloudUploadAlt);
diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss
index 7da55fd1c..5f4a52c0c 100644
--- a/src/client/views/MetadataEntryMenu.scss
+++ b/src/client/views/MetadataEntryMenu.scss
@@ -8,6 +8,10 @@
     }
 }
 
+#metadataEntry-outer {
+    overflow: auto !important;
+}
+
 .metadataEntry-keys {
     max-height: 80;
     overflow-y: auto; 
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index 22259e509..23b21ae0c 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -196,7 +196,7 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
     _ref = React.createRef<HTMLInputElement>();
     render() {
         return (
-            <div className="metadataEntry-outerDiv">
+            <div className="metadataEntry-outerDiv" id="metadataEntry-outer">
                 <div className="metadataEntry-inputArea">
                     Key:
                     <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 92987bde0..f2ae93b78 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -51,9 +51,6 @@ export interface TemplateMenuProps {
 @observer
 export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @observable private _hidden: boolean = true;
-    private _downx = 0;
-    private _downy = 0;
-    private _dragRef = React.createRef<HTMLUListElement>();
 
     toggleCustom = (e: React.ChangeEvent<HTMLInputElement>): void => {
         this.props.docs.map(dv => dv.setCustomView(e.target.checked));
@@ -81,19 +78,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         }
     }
 
-    @undoBatch
-    @action
-    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.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;
-            }
-        }));
-    }
-
     @action
     toggleTemplateActivity = (): void => {
         this._hidden = !this._hidden;
@@ -107,40 +91,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             layout._chromeStatus = (layout._chromeStatus !== "disabled" ? "disabled" : "enabled");
         });
     }
-    onAliasButtonUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onAliasButtonMoved);
-        document.removeEventListener("pointerup", this.onAliasButtonUp);
-        e.stopPropagation();
-    }
-
-    onAliasButtonDown = (e: React.PointerEvent): void => {
-        this._downx = e.clientX;
-        this._downy = e.clientY;
-        e.stopPropagation();
-        e.preventDefault();
-        document.removeEventListener("pointermove", this.onAliasButtonMoved);
-        document.addEventListener("pointermove", this.onAliasButtonMoved);
-        document.removeEventListener("pointerup", this.onAliasButtonUp);
-        document.addEventListener("pointerup", this.onAliasButtonUp);
-    }
-    onAliasButtonMoved = (e: PointerEvent): void => {
-        if (this._dragRef.current !== null && (Math.abs(e.clientX - this._downx) > 4 || Math.abs(e.clientY - this._downy) > 4)) {
-            document.removeEventListener("pointermove", this.onAliasButtonMoved);
-            document.removeEventListener("pointerup", this.onAliasButtonUp);
-
-            const dragDocView = this.props.docs[0];
-            const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
-            const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
-            dragData.embedDoc = true;
-            dragData.dropAction = "alias";
-            DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, {
-                offsetX: dragData.offset[0],
-                offsetY: dragData.offset[1],
-                hideSource: false
-            });
-        }
-        e.stopPropagation();
-    }
 
     render() {
         const layout = Doc.Layout(this.props.docs[0].Document);
@@ -151,18 +101,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         templateMenu.push(<OtherToggle key={"custom"} name={"Custom"} checked={StrCast(this.props.docs[0].Document.layoutKey, "layout") !== "layout"} toggle={this.toggleCustom} />);
         templateMenu.push(<OtherToggle key={"narrative"} name={"Narrative"} checked={StrCast(this.props.docs[0].Document.layoutKey, "layout") === "layout_narrative"} toggle={this.toggleNarrative} />);
         templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
-        return (
-            <div className="templating-button" onPointerDown={this.onAliasButtonDown} title="Drag:(create alias). Tap:(modify layout)." >
-                <Flyout anchorPoint={anchorPoints.LEFT_TOP}
-                    content={<ul className="template-list" ref={this._dragRef} style={{ display: "block" }}>
-                        {templateMenu}
-                        {<button onClick={this.clearTemplates}>Restore Defaults</button>}
-                    </ul>}>
-                    <span className="parentDocumentSelector-button" >
-                        <FontAwesomeIcon icon={faEdit} size={"sm"} />
-                    </span>
-                </Flyout>
-            </div>
-        );
+        return <ul className="template-list" style={{ display: "block" }}>
+            {templateMenu}
+        </ul>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 160279efd..82cb3bc88 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -645,7 +645,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         if (curPres) {
             const pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" });
             Doc.GetProto(pinDoc).presentationTargetDoc = doc;
-            Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()');
+            Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title?.toString()');
             const data = Cast(curPres.data, listSpec(Doc));
             if (data) {
                 data.push(pinDoc);
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
index 6de20593b..440b6856b 100644
--- a/src/client/views/collections/CollectionPivotView.tsx
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -110,7 +110,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
             </div>
         );
         return !facetCollection ? (null) :
-            <div className="collectionPivotView">
+            <div className="collectionPivotView" style={{ height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
                 <div className={"pivotKeyEntry"}>
                     <EditableView
                         contents={this.props.Document.pivotField}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 65d421c52..8679c8bd1 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -52,9 +52,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
         private _childLayoutDisposer?: IReactionDisposer;
         protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
-            this.dropDisposer && this.dropDisposer();
-            this.gestureDisposer && this.gestureDisposer();
-            this.multiTouchDisposer && this.multiTouchDisposer();
+            this.dropDisposer?.();
+            this.gestureDisposer?.();
+            this.multiTouchDisposer?.();
             if (ele) {
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 395eed372..a7733ab5f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -645,10 +645,12 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                 const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif";
                 const detailedTemplate = `{ "doc": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "short_description" } } ] }, { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "year" } } ] },  { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "company" } } ] }  ] }, "selection":{"type":"text","anchor":1,"head":1},"storedMarks":[] }`;
 
+                const textDoc = TextDocument("", { title: "details", _autoHeight: true });
                 const detailedLayout = Docs.Create.StackingDocument([
                     CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" }),
-                    TextDocument("", { title: "details", _autoHeight: true, _textTemplate: new RichTextField(detailedTemplate, "short_description year company") })
+                    textDoc,
                 ], { _chromeStatus: "disabled", title: "detailed layout stack" });
+                textDoc.data = new RichTextField(detailedTemplate, "short_description year company");
                 detailedLayout.isTemplateDoc = makeTemplate(detailedLayout);
 
                 const cardLayout = ImageDocument(fallbackImg, { title: "cardLayout", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work?
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 64411b5fe..efe185922 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -20,7 +20,7 @@
 
             .collectionViewBaseChrome-viewPicker {
                 font-size: 75%;
-                text-transform: uppercase;
+                //text-transform: uppercase;
                 letter-spacing: 2px;
                 background: rgb(238, 238, 238);
                 color: grey;
@@ -34,6 +34,26 @@
                 outline-color: black;
             }
 
+            .collectionViewBaseChrome-cmdPicker {
+                margin-left: 3px;
+                margin-right: 0px;
+                background: rgb(238, 238, 238);
+                border: none;
+                color: grey;
+            }
+            .commandEntry-outerDiv {
+                pointer-events: all;
+                background-color: gray;
+                display: flex;
+                flex-direction: row;
+                .commandEntry-drop {
+                    color:white;
+                    width:25px;
+                    margin-top: auto;
+                    margin-bottom: auto;
+                }
+            }
+
             .collectionViewBaseChrome-collapse {
                 transition: all .5s, opacity 0.3s;
                 position: absolute;
@@ -53,6 +73,18 @@
             .collectionViewBaseChrome-viewSpecs {
                 margin-left: 10px;
                 display: grid;
+                
+                .collectionViewBaseChrome-filterIcon {
+                    position: relative;
+                    display: flex;
+                    margin: auto;
+                    background: gray;
+                    color: white;
+                    width: 40px;
+                    height: 40px;
+                    align-items: center;
+                    justify-content: center;
+                }
 
                 .collectionViewBaseChrome-viewSpecsInput {
                     padding: 12px 10px 11px 10px;
@@ -240,7 +272,6 @@
 .commandEntry-outerDiv {
     display: flex;
     flex-direction: column;
-    width: 165px;
     height: 40px;
 }
 .commandEntry-inputArea {
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index e09f90e2c..d1919d588 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -39,25 +39,24 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
 
     _templateCommand = {
-        title: "set template", script: "setChildLayout(this.target, this.source?.[0])", params: ["target", "source"],
+        title: "=> item view", script: "setChildLayout(this.target, this.source?.[0])", params: ["target", "source"],
         initialize: emptyFunction,
         immediate: (draggedDocs: Doc[]) => Doc.setChildLayout(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined)
     };
     _narrativeCommand = {
-        title: "set detailed template", script: "setChildLayout(this.target, this.source?.[0])", params: ["target", "source"],
+        title: "=> click item view", script: "setChildDetailedLayout(this.target, this.source?.[0])", params: ["target", "source"],
         initialize: emptyFunction,
-        immediate: (draggedDocs: Doc[]) => Doc.setChildDetailed(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined)
+        immediate: (draggedDocs: Doc[]) => Doc.setChildDetailedLayout(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined)
     };
     _contentCommand = {
-        // title: "set content", script: "getProto(this.target).data = aliasDocs(this.source.map(async p => await p));", params: ["target", "source"],  // bcz: doesn't look like we can do async stuff in scripting...
-        title: "set content", script: "getProto(this.target).data = aliasDocs(this.source);", params: ["target", "source"],
+        title: "=> content", script: "getProto(this.target).data = aliasDocs(this.source);", params: ["target", "source"],
         initialize: emptyFunction,
         immediate: (draggedDocs: Doc[]) => Doc.GetProto(this.props.CollectionView.props.Document).data = new List<Doc>(draggedDocs.map((d: any) => Doc.MakeAlias(d)))
     };
     _viewCommand = {
-        title: "restore view", script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;", params: ["target"],
+        title: "=> saved view", script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;", params: ["target"],
+        initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document._panX; button.restoredPanY = this.props.CollectionView.props.Document._panY; button.restoredScale = this.props.CollectionView.props.Document.scale; },
         immediate: (draggedDocs: Doc[]) => { this.props.CollectionView.props.Document._panX = 0; this.props.CollectionView.props.Document._panY = 0; this.props.CollectionView.props.Document.scale = 1; },
-        initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document._panX; button.restoredPanY = this.props.CollectionView.props.Document._panY; button.restoredScale = this.props.CollectionView.props.Document.scale; }
     };
     _freeform_commands = [this._contentCommand, this._templateCommand, this._narrativeCommand, this._viewCommand];
     _stacking_commands = [this._contentCommand, this._templateCommand];
@@ -153,21 +152,32 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         this.props.CollectionView.props.Document._viewType = parseInt(e.target.selectedOptions[0].value);
     }
 
+    commandChanged = (e: React.ChangeEvent) => {
+        //@ts-ignore
+        runInAction(() => this._currentKey = e.target.selectedOptions[0].value);
+    }
+
     @action
     openViewSpecs = (e: React.SyntheticEvent) => {
-        this._viewSpecsOpen = true;
+        if (this._viewSpecsOpen) this.closeViewSpecs();
+        else {
+            this._viewSpecsOpen = true;
 
-        //@ts-ignore
-        if (!e.target.classList[0].startsWith("qs")) {
-            this.closeDatePicker();
-        }
+            //@ts-ignore
+            if (!e.target?.classList[0]?.startsWith("qs")) {
+                this.closeDatePicker();
+            }
 
-        e.stopPropagation();
-        document.removeEventListener("pointerdown", this.closeViewSpecs);
-        document.addEventListener("pointerdown", this.closeViewSpecs);
+            e.stopPropagation();
+            document.removeEventListener("pointerdown", this.closeViewSpecs);
+            document.addEventListener("pointerdown", this.closeViewSpecs);
+        }
     }
 
-    @action closeViewSpecs = () => { this._viewSpecsOpen = false; document.removeEventListener("pointerdown", this.closeViewSpecs); };
+    @action closeViewSpecs = () => {
+        this._viewSpecsOpen = false;
+        document.removeEventListener("pointerdown", this.closeViewSpecs);
+    };
 
     @action
     openDatePicker = (e: React.PointerEvent) => {
@@ -399,23 +409,20 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                             onPointerDown={stopPropagation}
                             onChange={this.viewChanged}
                             value={NumCast(this.props.CollectionView.props.Document._viewType)}>
-                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform View</option>
-                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema View</option>
-                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
-                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
-                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View</option>
-                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="7">Multicolumn View</option>
-                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="8">Pivot View</option>
-                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="9">Carousel View</option>
-                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="10">Linear View</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">schema</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="7">Multicolumn</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="8">Pivot</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="9">Carousel</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="10">Linear</option>
                         </select>
-                        <div className="collectionViewBaseChrome-viewSpecs" style={{ display: collapsed ? "none" : "grid" }}>
-                            <input className="collectionViewBaseChrome-viewSpecsInput"
-                                placeholder="FILTER"
-                                value={this.filterValue ? this.filterValue.script.originalScript === "return true" ? "" : this.filterValue.script.originalScript : ""}
-                                onChange={(e) => { }}
-                                onPointerDown={this.openViewSpecs} />
-                            {this.getPivotInput()}
+                        <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
+                            <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.openViewSpecs} >
+                                <FontAwesomeIcon icon="filter" size="2x" />
+                            </div>
                             <div className="collectionViewBaseChrome-viewSpecsMenu"
                                 onPointerDown={this.openViewSpecs}
                                 style={{
@@ -456,17 +463,20 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                             </div>
                         </div>
                         <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} >
-                            <div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
-                                <div className="commandEntry-inputArea" onPointerDown={this.autoSuggestDown} >
-                                    <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
-                                        getSuggestionValue={this.getSuggestionValue}
-                                        suggestions={this.suggestions}
-                                        alwaysRenderSuggestions={true}
-                                        renderSuggestion={this.renderSuggestion}
-                                        onSuggestionsFetchRequested={this.onSuggestionFetch}
-                                        onSuggestionsClearRequested={this.onSuggestionClear}
-                                        ref={this._autosuggestRef} />
+                            <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
+                                <div className="commandEntry-drop">
+                                    <FontAwesomeIcon icon="bullseye" size="2x"></FontAwesomeIcon>
                                 </div>
+                                <select
+                                    className="collectionViewBaseChrome-cmdPicker"
+                                    onPointerDown={stopPropagation}
+                                    onChange={this.commandChanged}
+                                    value={this._currentKey}>
+                                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""}>{""}</option>
+                                    {this._buttonizableCommands.map(cmd =>
+                                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
+                                    )}
+                                </select>
                             </div>
                         </div>
                     </div>
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 18f1baf44..115f8d633 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -64,22 +64,13 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
             this.props.addDocTab(col, undefined, "inTab"); // bcz: dataDoc?
         };
     }
-    get metadataMenu() {
-        return <div className="parentDocumentSelector-metadata">
-            <Flyout anchorPoint={anchorPoints.TOP_LEFT}
-                content={<MetadataEntryMenu docs={() => this.props.Views.map(dv => dv.props.Document)} suggestWithFunction />}>{/* tfs: @bcz This might need to be the data document? */}
-                <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div>
-            </Flyout>
-        </div>;
-    }
 
     render() {
         return <div >
-            <div key="metadata">Metadata: {this.metadataMenu}</div>
             <p key="contexts">Contexts:</p>
             {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title?.toString()}</a></p>)}
             {this._otherDocs.length ? <hr key="hr" /> : null}
-            {this._otherDocs.map(doc => <p key="p"><a onClick={this.getOnClick(doc)}>{doc.col.title?.toString()}</a></p>)}
+            {this._otherDocs.map(doc => <p key={"p" + doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title?.toString()}</a></p>)}
         </div>;
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bac968165..20d20f1f6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -653,7 +653,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (!init) {
                 state.initializers![this.Document[Id]] = { panX: this.Document._panX, panY: this.Document._panY };
                 HistoryUtil.pushState(state);
-            } else if (init.panX !== this.Document.panX || init.panY !== this.Document.panY) {
+            } else if (init.panX !== this.Document._panX || init.panY !== this.Document._panY) {
                 init.panX = this.Document._panX;
                 init.panY = this.Document._panY;
                 HistoryUtil.pushState(state);
@@ -899,7 +899,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     onContextMenu = (e: React.MouseEvent) => {
         const layoutItems: ContextMenuProps[] = [];
 
-        layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+        layoutItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
         layoutItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
         layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 87b2a9729..041eb69da 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -249,7 +249,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
 
     render(): JSX.Element {
         return (
-            <div className={"collectionMulticolumnView_contents"} ref={this.createDashEventsTarget} b >
+            <div className={"collectionMulticolumnView_contents"} ref={this.createDashEventsTarget}>
                 {this.contents}
             </div>
         );
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3244b7d3e..2183129cf 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -60,8 +60,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     componentWillUnmount() { this._disposer?.(); }
     componentDidMount() {
-        this._disposer = reaction(() => Array.from(Cast(this.props.Document?.animateToPos, listSpec("number"), null)),
-            target => this._animPos = !target ? undefined : target[2] ? [NumCast(this.layoutDoc.x), NumCast(this.layoutDoc.y)] :
+        this._disposer = reaction(() => Array.from(Cast(this.props.Document?.animateToPos, listSpec("number"), null) || []),
+            target => this._animPos = !target || !target?.length ? undefined : target[2] ? [NumCast(this.layoutDoc.x), NumCast(this.layoutDoc.y)] :
                 this.props.ScreenToLocalTransform().transformPoint(target[0], target[1]),
             { fireImmediately: true });
     }
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index eda12d6fb..428e9aa7b 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -44,7 +44,7 @@ export class PresBox extends React.Component<FieldViewProps> {
                     if (item instanceof Doc && item.type !== DocumentType.PRESELEMENT) {
                         const pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" });
                         Doc.GetProto(pinDoc).presentationTargetDoc = item;
-                        Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()');
+                        Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('this.presentationTargetDoc?.title?.toString()');
                         value.splice(i, 1, pinDoc);
                     }
                 });
@@ -342,7 +342,7 @@ export class PresBox extends React.Component<FieldViewProps> {
             doc.presBox = this.props.Document;
             doc.presBoxKey = this.props.fieldKey;
             doc.collapsedHeight = hgt;
-            doc.height = ComputedField.MakeFunction("this.collapsedHeight + Number(this.embedOpen ? 100:0)");
+            doc._height = ComputedField.MakeFunction("this.collapsedHeight + Number(this.embedOpen ? 100:0)");
             const curScale = NumCast(doc.viewScale, null);
             if (curScale === undefined) {
                 doc.viewScale = 1;
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 0825580b7..f492ea773 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -9,6 +9,7 @@
     position: absolute;
     font-size: 10px;
     line-height: 1;
+    overflow: hidden;
 }
 .searchBox-bar {
     height: 32px;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index d129fe33b..5f454e930 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -710,9 +710,9 @@ export namespace Doc {
     }
 
     let dt = 0;
-    export function linkFollowHighlight(destDoc: Doc) {
+    export function linkFollowHighlight(destDoc: Doc, dataAndDisplayDocs = true) {
         linkFollowUnhighlight();
-        Doc.HighlightDoc(destDoc);
+        Doc.HighlightDoc(destDoc, dataAndDisplayDocs);
         document.removeEventListener("pointerdown", linkFollowUnhighlight);
         document.addEventListener("pointerdown", linkFollowUnhighlight);
         const x = dt = Date.now();
@@ -726,10 +726,10 @@ export namespace Doc {
     export function IsHighlighted(doc: Doc) {
         return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetDataDoc(doc));
     }
-    export function HighlightDoc(doc: Doc) {
+    export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) {
         runInAction(() => {
             highlightManager.HighlightedDoc.set(doc, true);
-            highlightManager.HighlightedDoc.set(Doc.GetDataDoc(doc), true);
+            dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetDataDoc(doc), true);
         });
     }
     export function UnHighlightDoc(doc: Doc) {
@@ -756,7 +756,7 @@ export namespace Doc {
             source.dragFactory instanceof Doc && source.dragFactory.isTemplateDoc ? source.dragFactory :
             source && source.layout instanceof Doc && source.layout.isTemplateDoc ? source.layout : undefined;
     }
-    export function setChildDetailed(target: Doc, source?: Doc) {
+    export function setChildDetailedLayout(target: Doc, source?: Doc) {
         target.childDetailed = source && source.isTemplateDoc ? source : source &&
             source.dragFactory instanceof Doc && source.dragFactory.isTemplateDoc ? source.dragFactory :
             source && source.layout instanceof Doc && source.layout.isTemplateDoc ? source.layout : undefined;
@@ -776,6 +776,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 setChildLayout(target: any, source: any) { Doc.setChildLayout(target, source); });
+Scripting.addGlobal(function setChildDetailedLayout(target: any, source: any) { Doc.setChildDetailedLayout(target, source); });
 Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
 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); });
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 6f99af8f6..35b266608 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -14,6 +14,7 @@ import { Utils } from "../../../Utils";
 import { nullAudio } from "../../../new_fields/URLField";
 import { DragManager } from "../../../client/util/DragManager";
 import { InkingControl } from "../../../client/views/InkingControl";
+import { CollectionViewType } from "../../../client/views/collections/CollectionView";
 
 export class CurrentUserUtils {
     private static curr_id: string;
@@ -54,7 +55,7 @@ export class CurrentUserUtils {
             { title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 200, title: "an image of a cat" })' },
             { title: "record", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
             { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: "Button" })' },
-            { title: "presentation", icon: "tv", ignoreClick: true, drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { _width: 200, _height: 500, title: "a presentation trail" })' },
+            { title: "presentation", icon: "tv", ignoreClick: true, drag: `Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { _width: 200, _height: 500, _viewType: ${CollectionViewType.Stacking}, title: "a presentation trail" })` },
             { title: "import folder", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
             { title: "mobile view", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' },
             { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen,  this)`, activePen: doc },
@@ -246,7 +247,7 @@ export class CurrentUserUtils {
 
     // the initial presentation Doc to use
     static setupDefaultPresentation(doc: Doc) {
-        doc.curPresentation = Docs.Create.PresDocument(new List<Doc>(), { title: "Presentation", boxShadow: "0 0" });
+        doc.curPresentation = Docs.Create.PresDocument(new List<Doc>(), { title: "Presentation", _viewType: CollectionViewType.Stacking, boxShadow: "0 0" });
     }
 
     static setupMobileUploads(doc: Doc) {
-- 
cgit v1.2.3-70-g09d2


From da92903fb49b2c7d34bb44f11bf72a6cd49b8c0d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 4 Feb 2020 23:52:40 -0500
Subject: lots of small fixes to templates, presentations, default templates
 (icon, slide)

---
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/OverlayView.tsx                   |  2 +-
 .../views/collections/CollectionTimeView.tsx       | 10 ++++----
 .../CollectionMulticolumnView.scss                 |  1 +
 .../CollectionMulticolumnView.tsx                  | 13 ++++++----
 .../CollectionMultirowView.scss                    |  1 +
 .../CollectionMultirowView.tsx                     | 12 ++++++----
 src/client/views/nodes/DocumentView.tsx            | 28 ++++++++++------------
 src/client/views/nodes/PresBox.scss                |  2 +-
 src/client/views/nodes/PresBox.tsx                 |  8 +++++--
 .../views/presentationview/PresElementBox.tsx      |  2 +-
 src/new_fields/Doc.ts                              | 16 +++++++++----
 .../authentication/models/current_user_utils.ts    | 20 +++++++++++++++-
 13 files changed, 78 insertions(+), 39 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 87a81504c..0cc374cee 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import {
-    faArrowDown, faBullseye, faFilter, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
+    faStickyNote, faArrowDown, faBullseye, faFilter, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
     faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt, faPhone, faStamp, faClipboard
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 295cd7c6e..7a99bf0ae 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -144,7 +144,7 @@ export class OverlayView extends React.Component {
             return (null);
         }
         return CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => {
-            d.inOverlay = true;
+            setTimeout(() => d.inOverlay = true, 0);
             let offsetx = 0, offsety = 0;
             const onPointerMove = action((e: PointerEvent) => {
                 if (e.buttons === 1) {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index f999067d3..264423d72 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -46,6 +46,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     @computed get _allFacets() {
         const facets = new Set<string>();
         this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
+        Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
         return facets.toArray();
     }
 
@@ -108,11 +109,10 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         const docItems: ContextMenuProps[] = [];
         const keySet: Set<string> = new Set();
 
-        this.childLayoutPairs.map(pair =>
-            Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(fieldKey =>
-                pair.layout[fieldKey] instanceof RichTextField ||
-                typeof (pair.layout[fieldKey]) === "number" ||
-                typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey)));
+        this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey =>
+            pair.layout[fieldKey] instanceof RichTextField ||
+            typeof (pair.layout[fieldKey]) === "number" ||
+            typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey)));
         keySet.toArray().map(fieldKey =>
             docItems.push({ description: ":" + fieldKey, event: () => this.props.Document.pivotField = fieldKey, icon: "compress-arrows-alt" }));
         docItems.push({ description: ":(null)", event: () => this.props.Document.pivotField = undefined, icon: "compress-arrows-alt" })
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
index f57ba438a..a5d5617bc 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
@@ -7,6 +7,7 @@
     .document-wrapper {
         display: flex;
         flex-direction: column;
+        width: 100%;
 
         .label-wrapper {
             display: flex;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index cbe064ec0..56f777f43 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -224,10 +224,11 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
                         Document={layout}
                         DataDocument={layout.resolvedDataDoc as Doc}
                         CollectionDoc={this.props.Document}
-                        PanelWidth={() => this.lookupPixels(layout)}
-                        PanelHeight={() => PanelHeight() - (BoolCast(Document.showWidthLabels) ? 20 : 0)}
-                        getTransform={() => this.lookupIndividualTransform(layout)}
+                        PanelWidth={() => this.lookupPixels(layout) - 2 * NumCast(Document._xMargin)}
+                        PanelHeight={() => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0)}
+                        getTransform={() => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin))}
                         onClick={this.onChildClickHandler}
+                        renderDepth={this.props.renderDepth + 1}
                     />
                     <WidthLabel
                         layout={layout}
@@ -249,7 +250,11 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
 
     render(): JSX.Element {
         return (
-            <div className={"collectionMulticolumnView_contents"} ref={this.createDashEventsTarget}>
+            <div className={"collectionMulticolumnView_contents"}
+                style={{
+                    marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin),
+                    marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin)
+                }} ref={this.createDashEventsTarget}>
                 {this.contents}
             </div>
         );
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
index ef4b4a19c..ed737ea59 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
@@ -8,6 +8,7 @@
     .document-wrapper {
         display: flex;
         flex-direction: row;
+        height: 100%;
 
         .label-wrapper {
             display: flex;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 434bf14ff..8a2498458 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -224,9 +224,9 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
                         Document={layout}
                         DataDocument={layout.resolvedDataDoc as Doc}
                         CollectionDoc={this.props.Document}
-                        PanelHeight={() => this.lookupPixels(layout)}
-                        PanelWidth={() => PanelWidth() - (BoolCast(Document.showHeightLabels) ? 20 : 0)}
-                        getTransform={() => this.lookupIndividualTransform(layout)}
+                        PanelHeight={() => this.lookupPixels(layout) - 2 * NumCast(Document._yMargin)}
+                        PanelWidth={() => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showHeightLabels) ? 20 : 0)}
+                        getTransform={() => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin))}
                         onClick={this.onChildClickHandler}
                         renderDepth={this.props.renderDepth + 1}
                     />
@@ -250,7 +250,11 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
 
     render(): JSX.Element {
         return (
-            <div className={"collectionMultirowView_contents"} ref={this.createDashEventsTarget}>
+            <div className={"collectionMultirowView_contents"}
+                style={{
+                    marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin),
+                    marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin)
+                }} ref={this.createDashEventsTarget}>
                 {this.contents}
             </div>
         );
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d768dd7cf..c14d11cce 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -111,7 +111,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get topMost() { return this.props.renderDepth === 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; }
+    @computed get onClickHandler() { return this.props.onClick || this.layoutDoc.onClick || this.Document.onClick; }
     @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
     @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
 
@@ -520,11 +520,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); }
 
-    static makeNativeViewClicked = (doc: Doc, prevLayout: string) => {
-        undoBatch(() => {
-            if (StrCast(doc.title).endsWith("_" + prevLayout)) doc.title = StrCast(doc.title).replace("_" + prevLayout, "");
-            doc.layoutKey = "layout";
-        })();
+    static makeNativeViewClicked = (doc: Doc) => {
+        undoBatch(() => Doc.setNativeView(doc))();
     }
 
     static makeCustomViewClicked = (doc: Doc, dataDoc: Opt<Doc>, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, name: string = "custom", docLayoutTemplate?: Doc) => {
@@ -653,21 +650,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @action
     setCustomView =
         (custom: boolean, layout: string): void => {
-            if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) {
-                Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document);
-            } else if (custom) {
-                DocumentView.makeNativeViewClicked(this.props.Document, StrCast(this.props.Document.layoutKey).split("_")[1]);
-
-                let foundLayout: Opt<Doc> = undefined;
-                DocListCast(Cast(CurrentUserUtils.UserDocument.expandingButtons, Doc, null)?.data)?.map(btnDoc => {
+            // if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) {
+            //     Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document);
+            // } else 
+            if (custom) {
+                DocumentView.makeNativeViewClicked(this.props.Document);
+
+                let foundLayout: Opt<Doc> = Cast(Doc.UserDoc().iconView, Doc, null);
+                !foundLayout && DocListCast(Cast(CurrentUserUtils.UserDocument.expandingButtons, Doc, null)?.data)?.map(btnDoc => {
                     if (StrCast(Cast(btnDoc?.dragFactory, Doc, null)?.title) === layout) {
                         foundLayout = btnDoc.dragFactory as Doc;
                     }
                 })
                 DocumentView.
-                    makeCustomViewClicked(this.props.Document, this.props.DataDoc, Docs.Create.StackingDocument, layout, foundLayout && Doc.MakeDelegate(foundLayout));
+                    makeCustomViewClicked(this.props.Document, this.props.DataDoc, Docs.Create.StackingDocument, layout, foundLayout);
             } else {
-                DocumentView.makeNativeViewClicked(this.props.Document, StrCast(this.props.Document.layoutKey).split("_")[1]);
+                DocumentView.makeNativeViewClicked(this.props.Document);
             }
         }
 
diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss
index 01e7f4834..3e1967a18 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/PresBox.scss
@@ -6,7 +6,7 @@
     top: 0;
     bottom: 0;
     width: 100%;
-    min-width: 100px;
+    min-width: 120px;
     height: 100%;
     min-height: 50px;
     letter-spacing: 2px;
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 06d8e688b..44411fa30 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -6,7 +6,6 @@ import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
 import { listSpec } from "../../../new_fields/Schema";
-import { ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { Docs } from "../../documents/Documents";
@@ -17,7 +16,6 @@ import { CollectionView, CollectionViewType } from "../collections/CollectionVie
 import { ContextMenu } from "../ContextMenu";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./PresBox.scss";
-import { presSchema } from "../presentationview/PresElementBox";
 
 library.add(faArrowLeft);
 library.add(faArrowRight);
@@ -31,6 +29,7 @@ library.add(faEdit);
 @observer
 export class PresBox extends React.Component<FieldViewProps> {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
+    _childReaction: IReactionDisposer | undefined;
     componentDidMount() {
         const userDoc = CurrentUserUtils.UserDocument;
         let presTemp = Cast(userDoc.presentationTemplate, Doc);
@@ -43,6 +42,11 @@ export class PresBox extends React.Component<FieldViewProps> {
         else {
             this.props.Document.childLayout = presTemp;
         }
+        this._childReaction = reaction(() => this.childDocs.slice(),
+            (children) => children.forEach((child, i) => child.presentationIndex = i), { fireImmediately: true });
+    }
+    componentWillUnmount() {
+        this._childReaction?.();
     }
 
     @computed get childDocs() { return DocListCast(this.props.Document[this.props.fieldKey]); }
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index dcc5247d5..ea3acf75c 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -50,7 +50,7 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
 
     _heightDisposer: IReactionDisposer | undefined;
-    @computed get indexInPres() { return this.originalLayout?.presBoxKey ? DocListCast(this.presentationDoc[StrCast(this.originalLayout?.presBoxKey)]).indexOf(this.originalLayout) : 0; }
+    @computed get indexInPres() { return NumCast(this.originalLayout?.presentationIndex); }
     @computed get presentationDoc() { return Cast(this.originalLayout?.presBox, Doc) as Doc; }
     @computed get originalLayout() { return this.props.Document.expandedTemplate as Doc; }
     @computed get targetDoc() { return this.originalLayout?.presentationTargetDoc as Doc; }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 3f2335702..9b4844b19 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -437,7 +437,7 @@ export namespace Doc {
     // the lyouatDoc's layout is layout string (not a document) 
     //
     export function WillExpandTemplateLayout(layoutDoc: Doc, dataDoc?: Doc) {
-        return layoutDoc.isTemplateForField && dataDoc && layoutDoc !== dataDoc && !(Doc.LayoutField(layoutDoc) instanceof Doc);
+        return (layoutDoc.isTemplateForField || layoutDoc.isTemplateDoc) && dataDoc && layoutDoc !== dataDoc && !(Doc.LayoutField(layoutDoc) instanceof Doc);
     }
 
     //
@@ -454,7 +454,8 @@ export namespace Doc {
         // If it doesn't find the expanded layout, then it makes a delegate of the template layout and
         // saves it on the data doc indexed by the template layout's id.
         //
-        const expandedLayoutFieldKey = templateField + "-layout[" + templateLayoutDoc[Id] + "]";
+        const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc);
+        const expandedLayoutFieldKey = (templateField || layoutFielddKey) + "-layout[" + templateLayoutDoc[Id] + "]";
         let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
         if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
 
@@ -568,7 +569,7 @@ export namespace Doc {
     let _applyCount: number = 0;
     export function ApplyTemplate(templateDoc: Doc) {
         if (templateDoc) {
-            const applied = ApplyTemplateTo(Doc.MakeDelegate(templateDoc), Doc.MakeDelegate(new Doc()), "layout", templateDoc.title + "(..." + _applyCount++ + ")");
+            const applied = ApplyTemplateTo(templateDoc, Doc.MakeDelegate(new Doc()), "layout", templateDoc.title + "(..." + _applyCount++ + ")");
             applied && (Doc.GetProto(applied).layout = applied.layout);
             return applied;
         }
@@ -613,7 +614,7 @@ export namespace Doc {
             Cast(templateField.data, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc));
             (Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateField.data));
         }
-        if (templateField.data instanceof RichTextField && templateField.data.Text) {
+        if (templateField.data instanceof RichTextField && (templateField.data.Text || templateField.data.Data.toString().includes("dashField"))) {
             templateField._textTemplate = ComputedField.MakeFunction(`copyField(this.${metadataFieldKey})`, { this: Doc.name });
         }
 
@@ -780,6 +781,12 @@ export namespace Doc {
         const fieldStr = Field.toString(fieldVal as Field);
         return fieldStr === value;
     }
+
+    export function setNativeView(doc: any) {
+        const prevLayout = StrCast(doc.layoutKey).split("_")[1];
+        if (StrCast(doc.title).endsWith("_" + prevLayout)) doc.title = StrCast(doc.title).replace("_" + prevLayout, "");
+        doc.layoutKey = "layout";
+    }
 }
 
 Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; });
@@ -792,6 +799,7 @@ Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy
 Scripting.addGlobal(function aliasDocs(field: any) { return new List<Doc>(field.map((d: any) => Doc.MakeAlias(d))); });
 Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
 Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });
+Scripting.addGlobal(function setNativeView(doc: any) { Doc.setNativeView(doc); });
 Scripting.addGlobal(function undo() { return UndoManager.Undo(); });
 Scripting.addGlobal(function redo() { return UndoManager.Redo(); });
 Scripting.addGlobal(function selectDoc(doc: any) { Doc.UserDoc().SelectedDocs = new List([doc]); });
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 6bfc729de..f2c3aab35 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -15,6 +15,8 @@ import { nullAudio } from "../../../new_fields/URLField";
 import { DragManager } from "../../../client/util/DragManager";
 import { InkingControl } from "../../../client/views/InkingControl";
 import { CollectionViewType } from "../../../client/views/collections/CollectionView";
+import { makeTemplate } from "../../../client/util/DropConverter";
+import { RichTextField } from "../../../new_fields/RichTextField";
 
 export class CurrentUserUtils {
     private static curr_id: string;
@@ -227,14 +229,30 @@ export class CurrentUserUtils {
 
     /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
     static setupExpandingButtons(doc: Doc) {
+        const slideTemplate = Docs.Create.StackingDocument(
+            [
+                Docs.Create.MulticolumnDocument([], { title: "images", _height: 200, _xMargin: 10, _yMargin: 10 }),
+                Docs.Create.TextDocument("", { title: "contents", _height: 100 })
+            ],
+            { _width: 400, _height: 300, title: "slide", _chromeStatus: "disabled", _autoHeight: true });
+        slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
+
+        const iconDoc = Docs.Create.TextDocument("", { title: "icon", _width: 150, _height: 30, onClick: ScriptField.MakeScript("setNativeView(this)") });
+        Doc.GetProto(iconDoc).data = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', "");
+        doc.isTemplateDoc = makeTemplate(iconDoc);
+        doc.iconView = iconDoc;
+
         doc.undoBtn = Docs.Create.FontIconDocument(
             { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onClick: ScriptField.MakeScript("undo()"), removeDropProperties: new List<string>(["dropAction"]), title: "undo button", icon: "undo-alt" });
         doc.redoBtn = Docs.Create.FontIconDocument(
             { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), removeDropProperties: new List<string>(["dropAction"]), title: "redo button", icon: "redo-alt" });
+        doc.slidesBtn = Docs.Create.FontIconDocument(
+            { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: slideTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "slide button", icon: "sticky-note" });
 
-        doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc], {
+        doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc], {
             title: "expanding buttons", _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0",
             backgroundColor: "black", preventTreeViewOpen: true, forceActive: true, lockedPosition: true,
+
             dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name })
         });
     }
-- 
cgit v1.2.3-70-g09d2


From ab95c12485c26e5e0e6dc48cef8abd63a9c1fb56 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 5 Feb 2020 15:05:32 -0500
Subject: several layout fixes for row/col stuff.  fixes to icons.  added
 buttons for buxton app.  made PresBox not search for its own contents when
 following a link

---
 src/client/util/DocumentManager.ts                 |  4 +-
 src/client/views/MainView.tsx                      |  4 +-
 .../views/collections/CollectionDockingView.tsx    |  4 +-
 src/client/views/collections/CollectionSubView.tsx |  6 ++-
 .../views/collections/CollectionTimeView.tsx       |  2 +-
 .../views/collections/CollectionTreeView.tsx       | 47 +++++++++++++++-------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../CollectionMulticolumnView.tsx                  |  5 +--
 .../CollectionMultirowView.tsx                     |  4 +-
 src/client/views/nodes/DocumentView.tsx            | 21 +++++-----
 .../authentication/models/current_user_utils.ts    |  9 ++---
 11 files changed, 67 insertions(+), 41 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 9fff8faa7..60bb25272 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -93,9 +93,9 @@ export class DocumentManager {
         const toReturn: DocumentView[] = [];
 
         DocumentManager.Instance.DocumentViews.map(view =>
-            view.props.Document === toFind && toReturn.push(view));
+            view.props.Document.presBox === undefined && view.props.Document === toFind && toReturn.push(view));
         DocumentManager.Instance.DocumentViews.map(view =>
-            view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
+            view.props.Document.presBox === undefined && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
 
         return toReturn;
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0cc374cee..a839f9fd2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import {
-    faStickyNote, faArrowDown, faBullseye, faFilter, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
+    faFileAlt, faStickyNote, faArrowDown, faBullseye, faFilter, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
     faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt, faPhone, faStamp, faClipboard
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -101,6 +101,8 @@ export class MainView extends React.Component {
             }
         }
 
+        library.add(faFileAlt);
+        library.add(faStickyNote);
         library.add(faFont);
         library.add(faExclamation);
         library.add(faPortrait);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 861a63503..9c67d8041 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -468,7 +468,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     onPointerDown={e => {
                         e.preventDefault();
                         e.stopPropagation();
-                        DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
+                        const dragData = new DragManager.DocumentDragData([doc]);
+                        dragData.dropAction = doc._dropAction;
+                        DragManager.StartDocumentDrag([dragSpan], dragData, e.clientX, e.clientY);
                     }}>
                     <FontAwesomeIcon icon="file" size="lg" />
                 </span>, dragSpan);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a2700e75a..731b5b01d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -128,11 +128,15 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             const filteredDocs = docFilters.length ? viewedDocs.filter(d => {
                 for (const key of Object.keys(clusters)) {
                     const cluster = clusters[key];
+                    const satisfiesFacetx = !Object.keys(cluster).some((inner) => {
+                        const modifier = cluster[inner];
+                        return ((modifier === "x") === Doc.matchFieldValue(d, key, inner));
+                    });
                     const satisfiesFacet = Object.keys(cluster).some(inner => {
                         const modifier = cluster[inner];
                         return (modifier === "x") !== Doc.matchFieldValue(d, key, inner);
                     });
-                    if (!satisfiesFacet) {
+                    if (!satisfiesFacet || !satisfiesFacetx) {
                         return false;
                     }
                 }
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 264423d72..896fb74f0 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -33,7 +33,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilter"]);
 
             const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
-            const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailed'); useRightSplit(alias, shiftKey); ";
+            const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailView'); alias._dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']);  useRightSplit(alias, shiftKey); ";
             facetCollection.onCheckedClick = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "boolean", checked: "boolean", containingTreeView: Doc.name });
             this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "boolean", containingCollection: Doc.name, shiftKey: "boolean" });
             this.props.Document._facetCollection = facetCollection;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 9e3fb1d67..19900c48b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -640,27 +640,46 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                         d.captions = undefined;
                     });
                 });
-                const { TextDocument, ImageDocument, CarouselDocument } = Docs.Create;
+                const { TextDocument, ImageDocument, CarouselDocument, TreeDocument } = Docs.Create;
                 const { Document } = this.props;
                 const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif";
-                const detailedTemplate = `{ "doc": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "short_description" } } ] }, { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "year" } } ] },  { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "company" } } ] }  ] }, "selection":{"type":"text","anchor":1,"head":1},"storedMarks":[] }`;
+                const detailedTemplate = `{ "doc": { "type": "doc", "content": [  { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "year" } } ] },  { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "company" } } ] }  ] }, "selection":{"type":"text","anchor":1,"head":1},"storedMarks":[] }`;
 
                 const textDoc = TextDocument("", { title: "details", _autoHeight: true });
-                const detailedLayout = Docs.Create.StackingDocument([
+                const detailView = Docs.Create.StackingDocument([
                     CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" }),
                     textDoc,
-                ], { _chromeStatus: "disabled", title: "detailed layout stack" });
-                textDoc.data = new RichTextField(detailedTemplate, "short_description year company");
-                detailedLayout.isTemplateDoc = makeTemplate(detailedLayout);
-
-                const cardLayout = ImageDocument(fallbackImg, { title: "cardLayout", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work?
-                cardLayout.proto!.layout = ImageBox.LayoutString("hero");
-                cardLayout.showTitle = "title";
-                cardLayout.showTitleHover = "titlehover";
-
-                Document.childLayout = cardLayout;
-                Document.childDetailed = detailedLayout;
+                    TextDocument("", { title: "short_description", _autoHeight: true }),
+                    TreeDocument([], { title: "narratives", _height: 75, treeViewHideTitle: true })
+                ], { _chromeStatus: "disabled", _width: 300, _height: 300, _autoHeight: true, title: "detailView" });
+                textDoc.data = new RichTextField(detailedTemplate, "year company");
+                detailView.isTemplateDoc = makeTemplate(detailView);
+
+
+                const heroView = ImageDocument(fallbackImg, { title: "heroView", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work?
+                heroView.proto!.layout = ImageBox.LayoutString("hero");
+                heroView.showTitle = "title";
+                heroView.showTitleHover = "titlehover";
+                heroView._dropAction = "alias";
+                heroView.removeDropProperties = new List<string>(["dropAction"]);
+
+                Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data",
+                    Docs.Create.FontIconDocument({
+                        _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+                        dragFactory: heroView, removeDropProperties: new List<string>(["dropAction"]), title: "hero view", icon: "portrait"
+                    }));
+
+                Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data",
+                    Docs.Create.FontIconDocument({
+                        _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+                        dragFactory: detailView, removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "file-alt"
+                    }));
+
+
+                Document.childLayout = heroView;
+                Document.childDetailed = detailView;
                 Document._viewType = CollectionViewType.Time;
+                Document._forceActive = true;
                 Document.pivotField = "company";
             }
         });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6b81eba7d..54c45e43a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -434,7 +434,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         let y = this.Document._panY || 0;
         const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout);
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        if (!this.isAnnotationOverlay && docs.length) {
+        if (!this.isAnnotationOverlay && docs.length && this.childDataProvider(docs[0])) {
             PDFMenu.Instance.fadeOut(true);
             const minx = this.childDataProvider(docs[0]).x;//docs.length ? NumCast(docs[0].x) : 0;
             const miny = this.childDataProvider(docs[0]).y;//docs.length ? NumCast(docs[0].y) : 0;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 92b908ab6..7d8de0db4 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -5,7 +5,6 @@ import { Doc } from '../../../../new_fields/Doc';
 import { documentSchema } from '../../../../new_fields/documentSchemas';
 import { makeInterface } from '../../../../new_fields/Schema';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../new_fields/Types';
-import { Utils } from '../../../../Utils';
 import { DragManager } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
@@ -119,7 +118,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
     private get totalRatioAllocation(): number | undefined {
         const layoutInfoLen = this.resolvedLayoutInformation.widthSpecifiers.length;
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
-            return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1));
+            return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.props.Document._xMargin);
         }
     }
 
@@ -228,7 +227,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
         for (let i = 0; i < childLayoutPairs.length; i++) {
             const { layout } = childLayoutPairs[i];
             const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin));
-            const width = () => this.lookupPixels(layout) - 2 * NumCast(Document._xMargin);
+            const width = () => this.lookupPixels(layout);
             const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
             collector.push(
                 <div className={"document-wrapper"}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index d92ba9a9f..ff7c4998f 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -119,7 +119,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
     private get totalRatioAllocation(): number | undefined {
         const layoutInfoLen = this.resolvedLayoutInformation.heightSpecifiers.length;
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
-            return this.props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1));
+            return this.props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.props.Document._yMargin);
         }
     }
 
@@ -228,7 +228,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
         for (let i = 0; i < childLayoutPairs.length; i++) {
             const { layout } = childLayoutPairs[i];
             const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin));
-            const height = () => this.lookupPixels(layout) - 2 * NumCast(Document._yMargin);
+            const height = () => this.lookupPixels(layout);
             const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
             collector.push(
                 <div
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1fb3dcb36..776d4b8c3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -483,9 +483,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (e.cancelBubble && this.active) {
             document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
         }
-        else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
+        else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.onClickHandler) && !this.Document.lockedPosition && !this.Document.inOverlay) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
-                if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
+                if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
                     this.startDragging(this._downX, this._downY, this.Document._dropAction ? this.Document._dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
@@ -518,7 +518,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     @undoBatch
-    deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); }
+    deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument?.(this.props.Document); }
 
     static makeNativeViewClicked = (doc: Doc) => {
         undoBatch(() => Doc.setNativeView(doc))();
@@ -597,7 +597,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 `Link from ${StrCast(de.complete.annoDragData.annotationDocument.title)}`);
         }
         if (de.complete.docDragData && de.complete.docDragData.applyAsTemplate) {
-            Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layout_custom");
+            Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layout_custom", undefined);
             e.stopPropagation();
         }
         if (de.complete.linkDragData) {
@@ -656,12 +656,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             if (custom) {
                 DocumentView.makeNativeViewClicked(this.props.Document);
 
-                let foundLayout: Opt<Doc> = Cast(Doc.UserDoc().iconView, Doc, null);
-                !foundLayout && DocListCast(Cast(CurrentUserUtils.UserDocument.expandingButtons, Doc, null)?.data)?.map(btnDoc => {
-                    if (StrCast(Cast(btnDoc?.dragFactory, Doc, null)?.title) === layout) {
-                        foundLayout = btnDoc.dragFactory as Doc;
-                    }
-                })
+                let foundLayout: Opt<Doc>;
+                DocListCast(Cast(Doc.UserDoc().expandingButtons, Doc, null)?.data)?.concat([Cast(Doc.UserDoc().iconView, Doc, null)]).
+                    map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc).forEach(tempDoc => {
+                        if (StrCast(tempDoc.title) === layout) {
+                            foundLayout = tempDoc;
+                        }
+                    })
                 DocumentView.
                     makeCustomViewClicked(this.props.Document, this.props.DataDoc, Docs.Create.StackingDocument, layout, foundLayout);
             } else {
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index a4af46a63..74db511df 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -53,14 +53,14 @@ export class CurrentUserUtils {
         doc.noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", _height: 75 });
         doc.activePen = doc;
         const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
-            { title: "collection", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory))', drag: 'getCopy(this.dragFactory, true)', dragFactory: emptyCollection },
+            { title: "collection", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: emptyCollection },
             { title: "preview", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(ComputedField.MakeFunction("selectedDocs(this,true,[_last_])?.[0]"), { _width: 250, _height: 250, title: "container" })' },
             { title: "todo item", icon: "check", ignoreClick: true, drag: 'getCopy(this.dragFactory, true)', dragFactory: notes[notes.length - 1] },
             { title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", {_width: 300, _height: 300, title: "New Webpage" })' },
             { title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 200, title: "an image of a cat" })' },
             { title: "record", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
             { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: "Button" })' },
-            { title: "presentation", icon: "tv", click: 'openOnRight(Doc.UserDoc().curPresentation = getCopy(this.dragFactory))', drag: `Doc.UserDoc().curPresentation = getCopy(this.dragFactory,true)`, dragFactory: emptyPresentation },
+            { title: "presentation", icon: "tv", click: 'openOnRight(Doc.UserDoc().curPresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().curPresentation = getCopy(this.dragFactory,true)`, dragFactory: emptyPresentation },
             { title: "import folder", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
             { title: "mobile view", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' },
             { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen,  this)`, activePen: doc },
@@ -240,7 +240,7 @@ export class CurrentUserUtils {
             { _width: 400, _height: 300, title: "slide", _chromeStatus: "disabled", _autoHeight: true });
         slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
 
-        const iconDoc = Docs.Create.TextDocument("", { title: "icon", _width: 150, _height: 30, onClick: ScriptField.MakeScript("setNativeView(this)") });
+        const iconDoc = Docs.Create.TextDocument("", { title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onClick: ScriptField.MakeScript("setNativeView(this)") });
         Doc.GetProto(iconDoc).data = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', "");
         doc.isTemplateDoc = makeTemplate(iconDoc);
         doc.iconView = new PrefetchProxy(iconDoc);
@@ -250,8 +250,7 @@ export class CurrentUserUtils {
         doc.redoBtn = Docs.Create.FontIconDocument(
             { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), removeDropProperties: new List<string>(["dropAction"]), title: "redo button", icon: "redo-alt" });
         doc.slidesBtn = Docs.Create.FontIconDocument(
-            { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: slideTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "slide button", icon: "sticky-note" });
-
+            { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: slideTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "presentation slide", icon: "sticky-note" });
         doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc], {
             title: "expanding buttons", _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0",
             backgroundColor: "black", preventTreeViewOpen: true, forceActive: true, lockedPosition: true,
-- 
cgit v1.2.3-70-g09d2

