From 1536f8d6e5f44fd14954550e9bd670561727b417 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 28 Aug 2020 15:45:07 -0400
Subject: fixed undo for collection tabs. fixed treeview to support
 freezeChildren to prevent children from being removed from the tree.  Made
 _isBackground a layoutfield.  fixed formattedTextBox exceptions on undo from
 a tab.

---
 .../views/collections/CollectionDockingView.tsx    | 316 ++++++++-------------
 1 file changed, 126 insertions(+), 190 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 3d8ac2a23..86d27acd5 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -24,7 +24,7 @@ import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { undoBatch } from "../../util/UndoManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocumentView } from "../nodes/DocumentView";
 import { PresBox } from '../nodes/PresBox';
 import "./CollectionDockingView.scss";
@@ -59,7 +59,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
 
     @observable private _goldenLayout: any = null;
     private _containerRef = React.createRef<HTMLDivElement>();
-    private _flush: boolean = false;
+    private _flush: UndoManager.Batch | undefined;
     private _ignoreStateChange = "";
     private _isPointerDown = false;
     private _maximizedSrc: Opt<DocumentView>;
@@ -73,6 +73,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         DragManager.StartWindowDrag = this.StartOtherDrag;
     }
     public StartOtherDrag = (e: any, dragDocs: Doc[]) => {
+        console.log("START drag batch");
+        !this._flush && (this._flush = UndoManager.StartBatch("golden layout drag"));
         const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) :
             {
                 type: 'row',
@@ -104,6 +106,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         SelectionManager.DeselectAll();
     }
 
+    @undoBatch
     public CloseFullScreen = () => {
         const target = this._goldenLayout._maximisedItem;
         if (target !== null && this._maximizedSrc) {
@@ -121,25 +124,24 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     @undoBatch
     @action
     public static CloseRightSplit(document: Opt<Doc>): boolean {
-        const instance = CollectionDockingView.Instance;
         const tryClose = (childItem: any) => {
             if (childItem.config?.component === "DocumentFrameRenderer") {
                 const docView = DocumentManager.Instance.getDocumentViewById(childItem.config.props.documentId);
                 if (docView && ((!document && docView.Document.isDisplayPanel) || (document && Doc.AreProtosEqual(docView.props.Document, document)))) {
                     childItem.remove();
-                    instance.layoutChanged(document);
                     return true;
                 }
             }
             return false;
         };
-        const retVal = !instance?._goldenLayout.root.contentItems[0].isRow ? false :
-            Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => Array.from(child.contentItems).some(tryClose));
+        const retVal = !CollectionDockingView.Instance?._goldenLayout.root.contentItems[0].isRow ? false :
+            Array.from(CollectionDockingView.Instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => Array.from(child.contentItems).some(tryClose));
 
-        retVal && instance.stateChanged();
+        retVal && CollectionDockingView.Instance.layoutChanged(document);
         return retVal;
     }
 
+    @undoBatch
     @action
     layoutChanged(removed?: Doc) {
         this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
@@ -147,20 +149,17 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         this.stateChanged();
     }
-    @undoBatch
-    @action
+
     public static ReplaceRightSplit(document: Doc, libraryPath?: Doc[], addToSplit?: boolean): boolean {
         if (!CollectionDockingView.Instance) return false;
         const instance = CollectionDockingView.Instance;
-        let retVal = false;
-        if (instance._goldenLayout.root.contentItems[0].isRow) {
-            retVal = Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
+        const retVal = !instance._goldenLayout.root.contentItems[0].isRow ? false :
+            Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
                 if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
                     DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)?.Document.isDisplayPanel) {
                     const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
                     child.addChild(newItemStackConfig, undefined);
                     !addToSplit && child.contentItems[0].remove();
-                    instance.layoutChanged(document);
                     return true;
                 }
                 return Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
@@ -168,16 +167,12 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                         const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
                         child.addChild(newItemStackConfig, undefined);
                         !addToSplit && child.contentItems[j].remove();
-                        instance.layoutChanged(document);
                         return true;
                     }
                     return false;
                 });
             });
-        }
-        if (retVal) {
-            instance.stateChanged();
-        }
+        retVal && instance.layoutChanged(document);
         return retVal;
     }
 
@@ -186,34 +181,51 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     //
     @undoBatch
     @action
-    public static AddRightSplit(document: Doc, libraryPath?: Doc[]) {
+    public static AddRightSplit(document: Doc, dontSelect: boolean = false, isDisplayPanel: Opt<boolean> = undefined) {
         if (!CollectionDockingView.Instance) return false;
-        if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document);
-        const instance = CollectionDockingView.Instance;
-        const newItemStackConfig = {
-            type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath)]
-        };
 
-        const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
-
-        if (instance._goldenLayout.root.contentItems.length === 0) {
-            instance._goldenLayout.root.addChild(newContentItem);
-        } else if (instance._goldenLayout.root.contentItems[0].isRow) {
-            instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
+        const ind = CollectionDockingView.Instance._tabMap.findIndex((val) => val.doc === document);
+        if (ind !== -1) {
+            const tab = CollectionDockingView.Instance._tabMap[ind].tab;
+            const activeContentItem = tab.header.parent.getActiveContentItem();
+            if (tab.contentItem !== activeContentItem) {
+                tab.header.parent.setActiveContentItem(tab.contentItem);
+            }
+            tab.setActive(true);
         } else {
-            const collayout = instance._goldenLayout.root.contentItems[0];
-            const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
-            collayout.parent.replaceChild(collayout, newRow);
+            document.isDisplayPanel = isDisplayPanel;
 
-            newRow.addChild(newContentItem, undefined, true);
-            newRow.addChild(collayout, 0, true);
+            if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document);
+            const instance = CollectionDockingView.Instance;
+            const newItemStackConfig = {
+                type: 'stack',
+                content: [CollectionDockingView.makeDocumentConfig(document, undefined, [])]
+            };
+
+            const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
 
-            collayout.config.width = 50;
-            newContentItem.config.width = 50;
+            if (instance._goldenLayout.root.contentItems.length === 0) {
+                instance._goldenLayout.root.addChild(newContentItem);
+            } else if (instance._goldenLayout.root.contentItems[0].isRow) {
+                instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
+            } else {
+                const collayout = instance._goldenLayout.root.contentItems[0];
+                const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
+                collayout.parent.replaceChild(collayout, newRow);
+
+                newRow.addChild(newContentItem, undefined, true);
+                newRow.addChild(collayout, 0, true);
+
+                collayout.config.width = 50;
+                newContentItem.config.width = 50;
+            }
+            newContentItem.callDownwards('_$init');
+            instance.layoutChanged();
+        }
+        if (!dontSelect) {
+            const view = DocumentManager.Instance.getFirstDocumentView(document);
+            view && SelectionManager.SelectDoc(view, false);
         }
-        newContentItem.callDownwards('_$init');
-        instance.layoutChanged();
         return true;
     }
 
@@ -221,7 +233,6 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     //
     //  Creates a split on any side of the docking view based on the passed input pullSide and then adds the Document to the requested side
     //
-    @undoBatch
     @action
     public static AddSplit(document: Doc, pullSide: string, libraryPath?: Doc[]) {
         if (!CollectionDockingView.Instance) return false;
@@ -289,19 +300,13 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     //
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
     //
-    @undoBatch
-    @action
     public static UseRightSplit(document: Doc, libraryPath?: Doc[], shiftKey?: boolean) {
-        document.isDisplayPanel = true;
         if (shiftKey || !CollectionDockingView.ReplaceRightSplit(document, libraryPath, shiftKey)) {
-            CollectionDockingView.AddRightSplit(document, libraryPath);
+            CollectionDockingView.AddRightSplit(document, false, true);
         }
     }
 
-    @undoBatch
-    @action
     public AddTab = (stack: any, document: Doc, libraryPath?: Doc[]) => {
-        Doc.GetProto(document).lastOpened = new DateField;
         const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
         if (stack === undefined) {
             let stack: any = this._goldenLayout.root;
@@ -324,10 +329,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         return true;
     }
 
-    @undoBatch
-    @action
     public ReplaceTab = (stack: any, document: Doc, libraryPath?: Doc[]) => {
-        Doc.GetProto(document).lastOpened = new DateField;
         const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
         if (stack === undefined) {
             let stack: any = this._goldenLayout.root;
@@ -350,9 +352,15 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         return true;
     }
 
-    setupGoldenLayout() {
+    _tabMap: { tab: any, doc: Doc }[] = [];
+
+    async setupGoldenLayout() {
         const config = StrCast(this.props.Document.dockingConfig);
         if (config) {
+            const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+            const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) ?? [];
+            await Promise.all(docids.map(id => DocServer.GetRefField(id)));
+
             if (!this._goldenLayout) {
                 runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
             }
@@ -361,7 +369,6 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                     return;
                 }
                 try {
-                    this._goldenLayout.unbind('itemDropped', this.itemDropped);
                     this._goldenLayout.unbind('tabCreated', this.tabCreated);
                     this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
                     this._goldenLayout.unbind('stackCreated', this.stackCreated);
@@ -369,12 +376,12 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                 this._goldenLayout.destroy();
                 runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
             }
-            this._goldenLayout.on('itemDropped', this.itemDropped);
             this._goldenLayout.on('tabCreated', this.tabCreated);
             this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
             this._goldenLayout.on('stackCreated', this.stackCreated);
             this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
             this._goldenLayout.container = this._containerRef.current;
+            //this._goldenLayout.on("stateChanged", () => console.log("STATE CHANGED"));
             if (this._goldenLayout.config.maximisedItemId === '__glMaximised') {
                 try {
                     this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise();
@@ -388,46 +395,39 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     reactionDisposer?: Lambda;
     componentDidMount: () => void = () => {
         if (this._containerRef.current) {
-            const observer = new _global.ResizeObserver(action((entries: any) => {
-                for (const entry of entries) {
-                    this.onResize(null as any);
-                }
-            }));
+            const observer = new _global.ResizeObserver(this.onResize);
             observer.observe(this._containerRef.current);
             this.reactionDisposer = reaction(
-                () => this.props.Document.dockingConfig,
-                () => {
-                    if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
-                        // Because this is in a set timeout, if this component unmounts right after mounting,
-                        // we will leak a GoldenLayout, because we try to destroy it before we ever create it
-                        setTimeout(() => this.setupGoldenLayout(), 1);
+                () => StrCast(this.props.Document.dockingConfig),
+                config => {
+                    if (!this._goldenLayout || this._ignoreStateChange !== config) {
+                        this.setupGoldenLayout();
                         DocListCast((Doc.UserDoc().myDashboards as Doc).data).map(d => d.dashboardBrush = false);
                         this.props.Document.dashboardBrush = true;
                     }
                     this._ignoreStateChange = "";
-                }, { fireImmediately: true });
-
+                });
+            setTimeout(() => this.setupGoldenLayout(), 0);
             window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
         }
     }
     componentWillUnmount: () => void = () => {
         try {
             this.props.Document.dashboardBrush = false;
-            this._goldenLayout.unbind('itemDropped', this.itemDropped);
             this._goldenLayout.unbind('tabCreated', this.tabCreated);
             this._goldenLayout.unbind('stackCreated', this.stackCreated);
             this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
         } catch (e) {
 
         }
-        this._goldenLayout && this._goldenLayout.destroy();
+        this._goldenLayout?.destroy();
         runInAction(() => {
             CollectionDockingView.Instances.splice(CollectionDockingView.Instances.indexOf(this), 1);
             this._goldenLayout = null;
         });
         window.removeEventListener('resize', this.onResize);
 
-        this.reactionDisposer && this.reactionDisposer();
+        this.reactionDisposer?.();
     }
     @action
     onResize = (event: any) => {
@@ -437,26 +437,26 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     }
 
     @action
-    onPointerUp = (e: React.PointerEvent): void => {
+    onPointerUp = (e: MouseEvent): void => {
+        window.removeEventListener("pointerup", this.onPointerUp);
+        this._isPointerDown = false;
         if (this._flush) {
-            this._flush = false;
             setTimeout(() => {
                 CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
                 this.stateChanged();
+                console.log("END BATCH Up");
+                this._flush!.end();
+                this._flush = undefined;
             }, 10);
         }
     }
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        this._isPointerDown = true;
-        const onPointerUp = action(() => {
-            window.removeEventListener("pointerup", onPointerUp);
-            this._isPointerDown = false;
-        });
-        window.addEventListener("pointerup", onPointerUp);
-        const className = (e.target as any).className;
-        if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
-            this._flush = true;
+        window.addEventListener("mouseup", this.onPointerUp);
+
+        if (!(e.target as HTMLElement).closest("*.lm_content") && ((e.target as HTMLElement).closest("*.lm_tab") || (e.target as HTMLElement).closest("*.lm_stack"))) {
+            console.log("START BATCH dwn");
+            this._flush = UndoManager.StartBatch("golden layout edit");
         }
         if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
             return;
@@ -464,29 +464,11 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             e.stopPropagation();
         }
     }
-
-    updateDataField = async (json: string) => {
-        const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
-        const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", ""));
-
-        const docs = !docids ? [] : (await Promise.all(docids.map(id => DocServer.GetRefField(id)))).filter(f => f).map(f => f as Doc);
-        const sublists = DocListCast(this.props.Document[this.props.fieldKey]);
-        const tabs = Cast(sublists[0], Doc, null);
-        const other = Cast(sublists[1], Doc, null);
-        const tabdocs = DocListCast(tabs.data);
-        const otherdocs = DocListCast(other.data);
-        Doc.GetProto(tabs).data = new List<Doc>(docs);
-        const otherSet = new Set<Doc>();
-        otherdocs.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
-        tabdocs.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
-        Doc.GetProto(other).data = new List<Doc>(Array.from(otherSet.values()));
-    }
-
-    public static async Copy(doc: Doc) {
+    public static Copy(doc: Doc) {
         let json = StrCast(doc.dockingConfig);
         const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
         const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) || [];
-        const docs = (await Promise.all(docids.map(id => DocServer.GetRefField(id)))).filter(f => f).map(f => f as Doc);
+        const docs = docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc);
         const newtabs = docs.map(doc => {
             const copy = Doc.MakeAlias(doc);
             json = json.replace(doc[Id], copy[Id]);
@@ -503,34 +485,36 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         return copy;
     }
 
-    @undoBatch
+    @action
     stateChanged = () => {
         const json = JSON.stringify(this._goldenLayout.toConfig());
-        this.props.Document.dockingConfig = json;
-        this.updateDataField(json);
-    }
-
-    itemDropped = () => {
-        CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
-        this.stateChanged();
-    }
+        const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+        const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", ""));
+        const docs = !docids ? [] : docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc);
 
-    htmlToElement(html: string) {
-        const template = document.createElement('template');
-        html = html.trim(); // Never return a text node of whitespace as the result
-        template.innerHTML = html;
-        return template.content.firstChild;
+        this.props.Document.dockingConfig = json;
+        const sublists = DocListCast(this.props.Document[this.props.fieldKey]);
+        const tabs = Cast(sublists[0], Doc, null);
+        const other = Cast(sublists[1], Doc, null);
+        const tabdocs = DocListCast(tabs.data);
+        const otherdocs = DocListCast(other.data);
+        Doc.GetProto(tabs).data = new List<Doc>(docs);
+        const otherSet = new Set<Doc>();
+        otherdocs.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
+        tabdocs.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
+        Doc.GetProto(other).data = new List<Doc>(Array.from(otherSet.values()));
     }
 
-    tabCreated = async (tab: any) => {
+    tabCreated = (tab: any) => {
         tab.titleElement[0].Tab = tab;
         if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
             if (tab.contentItem.config.fixed) {
                 tab.contentItem.parent.config.fixed = true;
             }
 
-            const doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId) as Doc;
+            const doc = DocServer.GetCachedRefField(tab.contentItem.config.props.documentId) as Doc;
             if (doc instanceof Doc) {
+                this._tabMap.push({ tab: tab, doc: doc });
                 tab.titleElement[0].onclick = (e: any) => {
                     if (Date.now() - tab.titleElement[0].lastClick < 1000) tab.titleElement[0].select();
                     tab.titleElement[0].lastClick = Date.now();
@@ -550,8 +534,10 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                 gearSpan.style.paddingRight = "12px";
                 const stack = tab.contentItem.parent;
                 tab.element[0].onpointerdown = (e: any) => {
-                    const view = DocumentManager.Instance.getDocumentView(doc);
-                    view && SelectionManager.SelectDoc(view, false);
+                    if (e.target.className !== "lm_close_tab") {
+                        const view = DocumentManager.Instance.getDocumentView(doc);
+                        view && SelectionManager.SelectDoc(view, false);
+                    }
                 };
                 // shifts the focus to this tab when another tab is dragged over it
                 tab.element[0].onmouseenter = (e: any) => {
@@ -594,27 +580,23 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                 tab.reactComponents = [gearSpan];
                 tab.element.append(gearSpan);
                 tab.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
-                    tab.titleElement[0].textContent = title, { fireImmediately: true };
+                    tab.titleElement[0].value = title;
                     tab.titleElement[0].style.padding = degree ? 0 : 2;
                     tab.titleElement[0].style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
-                });
+                }, { fireImmediately: true });
                 //TODO why can't this just be doc instead of the id?
                 tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
             }
         }
         tab.closeElement.off('click') //unbind the current click handler
-            .click(async function () {
+            .click(function () {
                 tab.selectionDisposer?.();
                 tab.reactionDisposer?.();
                 tab.buttonDisposer?.();
-                const doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId);
+                const doc = DocServer.GetCachedRefField(tab.contentItem.config.props.documentId);
                 if (doc instanceof Doc) {
-                    const theDoc = doc;
-
-                    const recent = await Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc);
-                    if (recent) {
-                        Doc.AddDocToList(recent, "data", doc, undefined, true, true);
-                    }
+                    const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null);
+                    recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
                     SelectionManager.DeselectAll();
                 }
                 CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
@@ -624,6 +606,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     }
 
     tabDestroyed = (tab: any) => {
+        const ind = this._tabMap.findIndex((val) => val.tab === tab);
+        ind !== -1 && this._tabMap.splice(ind, 1);
         if (tab.reactComponents) {
             for (const ele of tab.reactComponents) {
                 ReactDOM.unmountComponentAtNode(ele);
@@ -631,15 +615,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         }
     }
 
-    stackActiveChanged = () => {
-        try {
-            CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
-            this.stateChanged();
-        } catch (e) { } // catch exception thrown when config has not been initialzed yet
-    }
 
     stackCreated = (stack: any) => {
-        stack.layoutManager.on("activeContentItemChanged", this.stackActiveChanged);
         //stack.header.controlsContainer.find('.lm_popout').hide();
         stack.header.element.on('mousedown', (e: any) => {
             if (e.target === stack.header.element[0] && e.button === 2) {
@@ -651,58 +628,28 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             }
         });
 
-        // starter code for bezel to add new pane
-        // stack.element.on("touchstart", (e: TouchEvent) => {
-        // if (e.targetTouches.length === 2) {
-        //     let pt1 = e.targetTouches.item(0);
-        //     let pt2 = e.targetTouches.item(1);
-        //     let threshold = 40 * window.devicePixelRatio;
-        //     if (pt1 && pt2 && InteractionUtils.TwoPointEuclidist(pt1, pt2) < threshold) {
-        //         let edgeThreshold = 30 * window.devicePixelRatio;
-        //         let center = InteractionUtils.CenterPoint([pt1, pt2]);
-        //         let stackRect: DOMRect = stack.element.getBoundingClientRect();
-        //         let nearLeft = center.X - stackRect.x < edgeThreshold;
-        //         let nearTop = center.Y - stackRect.y < edgeThreshold;
-        //         let nearRight = stackRect.right - center.X < edgeThreshold;
-        //         let nearBottom = stackRect.bottom - center.Y < edgeThreshold;
-        //         let ns = [nearLeft, nearTop, nearRight, nearBottom].filter(n => n);
-        //         if (ns.length === 1) {
-
-        //         }
-        //     }
-        // }
-        // });
         stack.header.controlsContainer.find('.lm_close') //get the close icon
             .off('click') //unbind the current click handler
-            .click(action(async () => {
+            .click(action(() => {
                 //if (confirm('really close this?')) {
-
                 stack.remove();
-                stack.contentItems.forEach(async (contentItem: any) => {
-                    const doc = await DocServer.GetRefField(contentItem.config.props.documentId);
+                stack.contentItems.forEach((contentItem: any) => {
+                    const doc = Cast(DocServer.GetCachedRefField(contentItem.config.props.documentId), Doc, null);
                     if (doc instanceof Doc) {
-                        let recent: Doc | undefined;
-                        if (recent = await Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc)) {
-                            Doc.AddDocToList(recent, "data", doc, undefined, true, true);
-                        }
-                        const theDoc = doc;
+                        const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null);
+                        recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
                     }
                 });
-                //}
             }));
         stack.header.controlsContainer.find('.lm_popout') //get the close icon
             .off('click') //unbind the current click handler
             .click(action(() => {
                 // stack.config.fixed = !stack.config.fixed;  // force the stack to have a fixed size
-
                 const emptyPane = Cast(Doc.UserDoc().emptyPane, Doc, null);
                 emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
                 this.AddTab(stack, Docs.Create.FreeformDocument([], {
                     _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
                 }));
-
-                // const url = Utils.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
-                // let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");
             }));
     }
 
@@ -710,10 +657,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         if (this.props.renderDepth > 0) {
             return <div style={{ width: "100%", height: "100%" }}>Nested dashboards can't be rendered</div>;
         }
-        return <div className="collectiondockingview-container" id="menuContainer"
-            onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />;
+        return <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} ref={this._containerRef} />;
     }
-
 }
 
 interface DockedFrameProps {
@@ -725,7 +670,6 @@ interface DockedFrameProps {
 @observer
 export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     _mainCont: HTMLDivElement | null = null;
-    @observable private _libraryPath: Doc[] = [];
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
@@ -741,17 +685,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
     constructor(props: any) {
         super(props);
-        DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
-        this.props.libraryPath && this.setupLibraryPath();
-    }
-
-    async setupLibraryPath() {
-        Promise.all(this.props.libraryPath.map(async docid => {
-            const d = await DocServer.GetRefField(docid);
-            return d instanceof Doc ? d : undefined;
-        })).then(action((list: (Doc | undefined)[]) => this._libraryPath = list.filter(d => d).map(d => d as Doc)));
+        this._document = Cast(DocServer.GetCachedRefField(this.props.documentId), Doc, null);
     }
-
     /**
      * Adds a document to the presentation view
      **/
@@ -792,7 +727,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
 
     componentDidMount() {
         const color = () => StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white");
-        const selected = () => SelectionManager.SelectedDocuments().some(v => Doc.AreProtosEqual(v.props.Document, this._document));
+        const selected = () => SelectionManager.SelectedDocuments().some(v => v.props.Document === this._document);
         const updateTabColor = () => this._tab && (this._tab.style.backgroundColor = selected() ? color() : "");
         const observer = new _global.ResizeObserver(action((entries: any) => {
             for (const entry of entries) {
@@ -820,6 +755,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 const dv = this._document && DocumentManager.Instance.getFirstDocumentView(this._document);
                 dv && SelectionManager.SelectDoc(dv, false);
             });
+            (CollectionDockingView.Instance as any)._goldenLayout.isInitialised && CollectionDockingView.Instance.stateChanged();
             !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
         }
     }
@@ -873,7 +809,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         if (doc._viewType === CollectionViewType.Docking) {
             return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
         } else if (location === "onRight") {
-            return CollectionDockingView.AddRightSplit(doc, libraryPath);
+            return CollectionDockingView.AddRightSplit(doc);
         } else if (location === "close") {
             return CollectionDockingView.CloseRightSplit(doc);
         } else if (location === "replace") {
@@ -968,7 +904,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         const resolvedDataDoc = !Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined;
         return <>
             <DocumentView key={document[Id]}
-                LibraryPath={this._libraryPath}
+                LibraryPath={emptyPath}
                 Document={document}
                 DataDoc={resolvedDataDoc}
                 bringToFront={emptyFunction}
@@ -1008,6 +944,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             </div >);
     }
 }
-Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc); },
-    "opens up the inputted document on the right side of the screen", "(doc: any)");
+Scripting.addGlobal(function openOnRight(doc: any, dontSelect: boolean = false) { CollectionDockingView.AddRightSplit(doc, dontSelect); },
+    "opens up the inputted document on the right side of the screen", "(doc: any, dontSelect: boolean)");
 Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.UseRightSplit(doc, undefined, shiftKey); });
-- 
cgit v1.2.3-70-g09d2


From 55f99f4bcc45b1dc797982273e93f49fb58403c3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 28 Aug 2020 18:15:46 -0400
Subject: added explicit annotation mode for videos.  fixed annotation mode for
 webpages

---
 src/client/views/DocumentButtonBar.tsx             | 30 ++++----
 .../views/collections/CollectionDockingView.tsx    |  9 +--
 src/client/views/collections/CollectionMenu.tsx    | 12 ---
 .../views/collections/ParentDocumentSelector.tsx   | 41 ++++-------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collectionFreeForm/PropertiesView.tsx          |  4 +-
 src/client/views/nodes/VideoBox.tsx                |  6 +-
 src/client/views/nodes/WebBox.scss                 | 66 +++--------------
 src/client/views/nodes/WebBox.tsx                  | 85 +++-------------------
 9 files changed, 63 insertions(+), 192 deletions(-)

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

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index ed5ba9e71..d342eb95f 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -5,7 +5,7 @@ import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../fields/Doc";
 import { RichTextField } from '../../fields/RichTextField';
-import { Cast, NumCast } from "../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { emptyFunction, setupMoveUpEvents } from "../../Utils";
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
@@ -15,7 +15,6 @@ import { DragManager } from '../util/DragManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { SharingManager } from '../util/SharingManager';
 import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
-import { ParentDocSelector } from './collections/ParentDocumentSelector';
 import './collections/ParentDocumentSelector.scss';
 import './DocumentButtonBar.scss';
 import { MetadataEntryMenu } from './MetadataEntryMenu';
@@ -25,6 +24,7 @@ import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
 import { TemplateMenu } from "./TemplateMenu";
 import { Template, Templates } from "./Templates";
 import React = require("react");
+import { DocumentType } from '../documents/DocumentTypes';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -211,6 +211,18 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             </div></Tooltip >;
     }
 
+    @computed
+    get annotateButton() {
+        const targetDoc = this.view0?.props.Document;
+        const isAnnotating = targetDoc?.isAnnotating;
+        return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${isAnnotating ? "Exit" : "Enter"} annotation mode`}</div></>}>
+            <div className="documentButtonBar-linker" style={{ backgroundColor: isAnnotating ? "white" : "", color: isAnnotating ? "black" : "white", }}
+                onClick={e => targetDoc.isAnnotating = !targetDoc.isAnnotating}>
+                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="edit"
+                />
+            </div></Tooltip >;
+    }
+
     @computed
     get moreButton() {
         const targetDoc = this.view0?.props.Document;
@@ -235,17 +247,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                 </Flyout>
             </div></Tooltip>;
     }
-
-    @computed
-    get contextButton() {
-        return !this.view0 ? (null) : <ParentDocSelector Document={this.view0.props.Document} addDocTab={(doc, where) => {
-            where === "onRight" ? CollectionDockingView.AddRightSplit(doc) :
-                this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc) :
-                    this.view0?.props.addDocTab(doc, "onRight");
-            return true;
-        }} />;
-    }
-
     @observable _aliasDown = false;
     onAliasButtonDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction);
@@ -313,6 +314,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             {!Doc.UserDoc()["documentLinksButton-hideEnd"] ? (null) : <div className="documentButtonBar-button">
                 {this.shareButton}
             </div>}
+            {![DocumentType.VID, DocumentType.WEB].includes(StrCast(this.view0.props.Document.type) as DocumentType) ? (null) : <div className="documentButtonBar-button">
+                {this.annotateButton}
+            </div>}
             <div className="documentButtonBar-button">
                 {this.moreButton}
             </div>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 86d27acd5..816f784ff 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -5,8 +5,7 @@ import { action, computed, IReactionDisposer, Lambda, observable, reaction, runI
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
 import * as GoldenLayout from "../../../client/goldenLayout";
-import { DateField } from '../../../fields/DateField';
-import { DataSym, Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
+import { DataSym, Doc, DocListCast, Opt } from "../../../fields/Doc";
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -14,9 +13,10 @@ import { FieldId } from "../../../fields/RefField";
 import { listSpec } from '../../../fields/Schema';
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils, returnEmptyDoclist } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs } from '../../documents/Documents';
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { InteractionUtils } from '../../util/InteractionUtils';
@@ -29,11 +29,10 @@ import { DocumentView } from "../nodes/DocumentView";
 import { PresBox } from '../nodes/PresBox';
 import "./CollectionDockingView.scss";
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { SubCollectionViewProps, CollectionSubView } from "./CollectionSubView";
+import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
 import { CollectionViewType } from './CollectionView';
 import { DockingViewButtonSelector } from './ParentDocumentSelector';
 import React = require("react");
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index f5d64ba0d..a41b5eaca 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -867,18 +867,6 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
                     </div>
                 </Tooltip> : null}
 
-                {!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText || this.props.isDoc ? (null) :
-                    <Tooltip key="hypothesis" title={<div className="dash-tooltip">Toggle between native iframe and annotation modes</div>} placement="bottom">
-                        <button className={"antimodeMenu-button"} key="hypothesis"
-                            style={{
-                                backgroundColor: this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined,
-                                borderRight: "1px solid gray"
-                            }}
-                            onClick={() => this.props.docView.layoutDoc.isAnnotating = !this.props.docView.layoutDoc.isAnnotating}>
-                            <FontAwesomeIcon icon={"edit"} size="lg" />    {/* ["fab", "hire-a-helper"]} size={"lg"} /> */}
-                        </button>
-                    </Tooltip>
-                }
                 {!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText || this.props.isDoc ? (null) :
                     this.urlEditor
                 }
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 6556e7889..f019ddfe6 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { Tooltip } from "@material-ui/core";
-import { computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { computed, IReactionDisposer, observable, reaction, runInAction, action } from "mobx";
 import { observer } from "mobx-react";
 import * as React from "react";
 import { Doc } from "../../../fields/Doc";
@@ -71,25 +71,6 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
         </div>;
     }
 }
-
-@observer
-export class ParentDocSelector extends React.Component<SelectorProps> {
-    render() {
-        const flyout = (
-            <div className="parentDocumentSelector-flyout" title=" ">
-                <SelectorContextMenu {...this.props} />
-            </div>
-        );
-        return <div title="Show Contexts" onPointerDown={e => e.stopPropagation()} className="parentDocumentSelector-linkFlyout">
-            <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
-                <span className="parentDocumentSelector-button" >
-                    <FontAwesomeIcon icon={"chevron-circle-up"} size={"lg"} />
-                </span>
-            </Flyout>
-        </div>;
-    }
-}
-
 @observer
 export class DockingViewButtonSelector extends React.Component<{ views: () => DocumentView[], Stack: any }> {
     customStylesheet(styles: any) {
@@ -111,18 +92,22 @@ export class DockingViewButtonSelector extends React.Component<{ views: () => Do
         );
     }
 
+    @observable _tooltipOpen: boolean = false;
     render() {
-        return <Tooltip title={<><div className="dash-tooltip">Tap for toolbar, drag to create alias in another pane</div></>} placement="bottom">
-            <span onPointerDown={e => {
-                if (getComputedStyle(this._ref.current!).width !== "100%") {
-                    e.stopPropagation(); e.preventDefault();
-                }
-                this.props.views()[0]?.select(false);
-            }} className="buttonSelector">
+        return <Tooltip open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} title={<><div className="dash-tooltip">Tap for toolbar, drag to create alias in another pane</div></>} placement="bottom">
+            <span className="buttonSelector"
+                onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}
+                onPointerDown={action(e => {
+                    if (getComputedStyle(this._ref.current!).width !== "100%") {
+                        e.stopPropagation(); e.preventDefault();
+                    }
+                    this.props.views()[0]?.select(false);
+                    this._tooltipOpen = false;
+                })} >
                 <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
                     <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} />
                 </Flyout>
             </span>
-        </Tooltip>;
+        </Tooltip >;
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b55dc467a..04c30c4f2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1637,7 +1637,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
             style={{
                 transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)`,
                 transition: this.props.transition,
-                willChange: "transform"
+                //willChange: "transform"
             }}>
             {this.props.children()}
             {this.presPaths}
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
index c316e1aad..560f09931 100644
--- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
+++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
@@ -22,11 +22,11 @@ import { ContentFittingDocumentView } from "../../nodes/ContentFittingDocumentVi
 import { KeyValueBox } from "../../nodes/KeyValueBox";
 import { PresBox } from "../../nodes/PresBox";
 import { PropertiesButtons } from "../../PropertiesButtons";
+import { CollectionDockingView } from "../CollectionDockingView";
+import { SelectorContextMenu } from "../ParentDocumentSelector";
 import { FormatShapePane } from "./FormatShapePane";
 import "./FormatShapePane.scss";
 import "./PropertiesView.scss";
-import { CollectionDockingView } from "../CollectionDockingView";
-import { ParentDocSelector, SelectorContextMenu } from "../ParentDocumentSelector";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 85d010aaa..578061344 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -275,13 +275,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
             this._youtubeReactionDisposer?.();
             this._reactionDisposer = reaction(() => this.layoutDoc._currentTimecode, () => !this._playing && this.Seek((this.layoutDoc._currentTimecode || 0)));
             this._youtubeReactionDisposer = reaction(
-                () => Doc.GetSelectedTool() === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting,
+                () => !this.props.Document.isAnnotating && Doc.GetSelectedTool() === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting,
                 (interactive) => iframe.style.pointerEvents = interactive ? "all" : "none", { fireImmediately: true });
         };
         this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, {
             events: {
-                'onReady': onYoutubePlayerReady,
-                'onStateChange': onYoutubePlayerStateChange,
+                'onReady': this.props.dontRegisterView ? undefined : onYoutubePlayerReady,
+                'onStateChange': this.props.dontRegisterView ? undefined : onYoutubePlayerStateChange,
             }
         });
 
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 875142169..23a5ad824 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -70,7 +70,6 @@
         }
     }
 
-
     .webBox-overlay {
         width: 100%;
         height: 100%;
@@ -82,63 +81,22 @@
         background:lightGray;
         width: 100%;
     }
-    .webBox-freeze {
-        display: flex;
-        align-items: center; 
-        justify-content: center;
-        margin-right: 5px;
-        width: 30px;
-    }
-
-    .webBox-urlEditor {
-        position: relative;
-        opacity: 0.9;
+    .webBox-annotationToggle {
         z-index: 9001;
-        transition: top .5s;
-
-        .urlEditor {
-            display: grid;
-            grid-template-columns: 1fr auto;
-            padding-bottom: 10px;
-            overflow: hidden;
-
-            .editorBase {
-                display: flex;
-
-                .editor-collapse {
-                    transition: all .5s, opacity 0.3s;
-                    position: absolute;
-                    width: 40px;
-                    transform-origin: top left;
-                }
-
-                .switchToText {
-                    color: $main-accent;
-                }
-
-                .switchToText:hover {
-                    color: $dark-color;
-                }
-            }
-
-            button:hover {
-                transform: scale(1);
-            }
+        position: absolute;
+        top: 2;
+        left: 2;
+        box-shadow: black 0.3em 0.3em 1em;
+        border-radius: 5px;
+        display: flex;
+        width: 25px;
+        height: 25px;
+        align-items: center;
+        > svg {
+            margin: auto;
         }
     }
 
-    .webpage-urlInput {
-        padding: 12px 10px 11px 10px;
-        border: 0px;
-        color: grey;
-        letter-spacing: 2px;
-        outline-color: black;
-        background: rgb(238, 238, 238);
-        width: 100%;
-        margin-right: 10px;
-        height: 100%;
-    }
-
     .touch-iframe-overlay {
         width: 100%;
         height: 100%;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 7758b1815..290b4a720 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -31,6 +31,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./WebBox.scss";
 import "../pdf/PDFViewer.scss";
 import React = require("react");
+import { Tooltip } from '@material-ui/core';
 const htmlToText = require("html-to-text");
 
 type WebDocument = makeInterface<[typeof documentSchema]>;
@@ -235,81 +236,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         e.stopPropagation();
     }
 
-    toggleAnnotationMode = () => {
-        this.layoutDoc.isAnnotating = !this.layoutDoc.isAnnotating;
-    }
-
-    urlEditor() {
-        return (
-            <div className="webBox-urlEditor"
-                onDrop={this.onUrlDrop}
-                onDragOver={this.onUrlDragover} style={{ top: this._collapsed ? -70 : 0 }}>
-                <div className="urlEditor">
-                    <div className="editorBase">
-                        <button className="editor-collapse"
-                            style={{
-                                top: this._collapsed ? 70 : 0,
-                                transform: `rotate(${this._collapsed ? 180 : 0}deg) scale(${this._collapsed ? 0.5 : 1}) translate(${this._collapsed ? "-100%, -100%" : "0, 0"})`,
-                                opacity: (this._collapsed && !this.props.isSelected()) ? 0 : 0.9,
-                                left: (this._collapsed ? 0 : "unset"),
-                            }}
-                            title="Collapse Url Editor" onClick={this.toggleCollapse}>
-                            <FontAwesomeIcon icon="caret-up" size="2x" />
-                        </button>
-                        <div className="webBox-buttons"
-                            onDrop={this.onUrlDrop}
-                            onDragOver={this.onUrlDragover} style={{ display: this._collapsed ? "none" : "flex" }}>
-                            <div className="webBox-freeze" title={"Annotate"} style={{ background: this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleAnnotationMode} >
-                                <FontAwesomeIcon icon={faPen} size={"2x"} />
-                            </div>
-                            <div className="webBox-freeze" title={"Select"} style={{ background: !this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleAnnotationMode} >
-                                <FontAwesomeIcon icon={faMousePointer} size={"2x"} />
-                            </div>
-                            <input className="webpage-urlInput"
-                                placeholder="ENTER URL"
-                                value={this._url}
-                                onDrop={this.onUrlDrop}
-                                onDragOver={this.onUrlDragover}
-                                onChange={this.onURLChange}
-                                onKeyDown={this.onValueKeyDown}
-                                onClick={(e) => {
-                                    this._keyInput.current!.select();
-                                    e.stopPropagation();
-                                }}
-                                ref={this._keyInput}
-                            />
-                            <div style={{
-                                display: "flex",
-                                flexDirection: "row",
-                                justifyContent: "space-between",
-                                maxWidth: "120px",
-                            }}>
-                                <button className="submitUrl" onClick={this.submitURL}
-                                    onDragOver={this.onUrlDragover} onDrop={this.onUrlDrop}>
-                                    GO
-                                </button>
-                                <button className="submitUrl" onClick={this.back}>
-                                    <FontAwesomeIcon icon="caret-left" size="lg"></FontAwesomeIcon>
-                                </button>
-                                <button className="submitUrl" onClick={this.forward}>
-                                    <FontAwesomeIcon icon="caret-right" size="lg"></FontAwesomeIcon>
-                                </button>
-                            </div>
-                        </div>
-                    </div>
-                </div>
+    editToggleBtn() {
+        return <Tooltip title={<div className="dash-tooltip" >{`${this.props.Document.isAnnotating ? "Exit" : "Enter"} annotation mode`}</div>}>
+            <div className="webBox-annotationToggle"
+                style={{ color: this.props.Document.isAnnotating ? "black" : "white", backgroundColor: this.props.Document.isAnnotating ? "white" : "black" }}
+                onClick={action(() => this.layoutDoc.isAnnotating = !this.layoutDoc.isAnnotating)}>
+                <FontAwesomeIcon icon="edit" size="lg" />
             </div>
-        );
+        </Tooltip>;
     }
 
-
-    @action
-    toggleCollapse = () => {
-        this._collapsed = !this._collapsed;
-    }
-
-
-
     _ignore = 0;
     onPreWheel = (e: React.WheelEvent) => {
         this._ignore = e.timeStamp;
@@ -474,7 +410,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 style={{ width: Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%" }}
                 onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
                 {view}
-            </div>;
+            </div>
             {!frozen ? (null) :
                 <div className="webBox-overlay" style={{ pointerEvents: this.layoutDoc._isBackground ? undefined : "all" }}
                     onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}>
@@ -483,7 +419,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                         <div className="dragger" ref={this._iframeDragRef}></div>
                     </div>
                 </div>}
-            {this.urlEditor()}
         </>);
     }
 
@@ -741,6 +676,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 {this.annotationLayer}
                 <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
             </div >
+
+            {this.props.isSelected() ? this.editToggleBtn() : null}
         </div>);
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 7b8924d203c6367edbf7e6ffbe2e8f2e6f24b859 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 29 Aug 2020 00:53:11 -0400
Subject: added some link follow options to link Editor.  Big overhaul of
 dockingView to make things undoable and cleaner

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/util/CurrentUserUtils.ts                |  12 +-
 src/client/util/DragManager.ts                     |   3 +-
 src/client/views/GlobalKeyHandler.ts               |   4 +-
 .../views/collections/CollectionDockingView.tsx    | 487 ++++++++-------------
 .../views/collections/CollectionTreeView.tsx       |   2 +-
 src/client/views/linking/LinkEditor.tsx            |   8 +
 src/client/views/search/SearchBox.tsx              |   2 +-
 src/fields/Doc.ts                                  |   4 -
 src/mobile/MobileInterface.tsx                     |   4 +-
 10 files changed, 215 insertions(+), 313 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 18ff993fe..9186cea87 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -848,7 +848,7 @@ export namespace Docs {
                     {
                         type: type,
                         content: [
-                            ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth, config.path))
+                            ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth))
                         ]
                     }
                 ]
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index c60403701..7937072da 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1025,7 +1025,7 @@ export class CurrentUserUtils {
         CurrentUserUtils.openDashboard(userDoc, copy);
     }
 
-    public static createNewDashboard = async (userDoc: Doc, id?: string) => {
+    public static createNewDashboard = (userDoc: Doc, id?: string) => {
         const myPresentations = userDoc.myPresentations as Doc;
         const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true);
         const dashboards = Cast(userDoc.myDashboards, Doc) as Doc;
@@ -1051,11 +1051,13 @@ export class CurrentUserUtils {
         dashboardDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Create Dashboard"]);
 
         Doc.AddDocToList(dashboards, "data", dashboardDoc);
-        // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
-        setTimeout(() => {
-            CurrentUserUtils.openDashboard(userDoc, dashboardDoc);
-        }, 0);
+        CurrentUserUtils.openDashboard(userDoc, dashboardDoc);
     }
+
+    public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); }
+    public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); }
+    public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); }
+    public static get EmptyPane() { return Cast(Doc.UserDoc().emptyPane, Doc, null); }
 }
 
 Scripting.addGlobal(function openDragFactory(dragFactory: Doc) {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0cca61841..8bf6faf03 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -117,10 +117,11 @@ export namespace DragManager {
     }
 
     export class DocumentDragData {
-        constructor(dragDoc: Doc[]) {
+        constructor(dragDoc: Doc[], dropAction?: dropActionType) {
             this.draggedDocuments = dragDoc;
             this.droppedDocuments = [];
             this.offset = [0, 0];
+            this.dropAction = dropAction;
         }
         draggedDocuments: Doc[];
         droppedDocuments: Doc[];
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 84b3d64fd..1cbbfd67b 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -99,7 +99,7 @@ export class KeyManager {
                 if (main.isPointerDown) {
                     DragManager.AbortDrag();
                 } else {
-                    if (CollectionDockingView.Instance.HasFullScreen()) {
+                    if (CollectionDockingView.Instance.HasFullScreen) {
                         CollectionDockingView.Instance.CloseFullScreen();
                     } else {
                         doDeselect = !ContextMenu.Instance.closeMenu();
@@ -253,7 +253,7 @@ export class KeyManager {
                 break;
             case "o":
                 const target = SelectionManager.SelectedDocuments()[0];
-                target && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(target);
+                target && CollectionDockingView.OpenFullScreen(target.props.Document);
                 break;
             case "r":
                 preventDefault = false;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 816f784ff..201cada09 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -37,9 +37,8 @@ const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
 export class CollectionDockingView extends CollectionSubView(doc => doc) {
-    @observable public static Instances: CollectionDockingView[] = [];
-    @computed public static get Instance() { return CollectionDockingView.Instances[0]; }
-    public static makeDocumentConfig(document: Doc, width?: number, libraryPath?: Doc[]) {
+    @observable public static Instance: CollectionDockingView;
+    public static makeDocumentConfig(document: Doc, width?: number) {
         return {
             type: 'react-component',
             component: 'DocumentFrameRenderer',
@@ -47,79 +46,46 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             width: width,
             props: {
                 documentId: document[Id],
-                libraryPath: libraryPath?.map(d => d[Id])
             }
         };
     }
 
-    @computed public get initialized() {
-        return this._goldenLayout !== null;
-    }
-
-    @observable private _goldenLayout: any = null;
+    private _reactionDisposer?: IReactionDisposer;
     private _containerRef = React.createRef<HTMLDivElement>();
     private _flush: UndoManager.Batch | undefined;
     private _ignoreStateChange = "";
-    private _isPointerDown = false;
-    private _maximizedSrc: Opt<DocumentView>;
+    public tabMap: Set<any> = new Set();
+    public get initialized() { return this._goldenLayout !== null; }
+    public get HasFullScreen() { return this._goldenLayout._maximisedItem !== null; }
+    @observable private _goldenLayout: any = null;
 
     constructor(props: SubCollectionViewProps) {
         super(props);
-        runInAction(() => !CollectionDockingView.Instances ? CollectionDockingView.Instances = [this] : CollectionDockingView.Instances.push(this));
+        runInAction(() => CollectionDockingView.Instance = this);
         //Why is this here?
         (window as any).React = React;
         (window as any).ReactDOM = ReactDOM;
         DragManager.StartWindowDrag = this.StartOtherDrag;
     }
+
     public StartOtherDrag = (e: any, dragDocs: Doc[]) => {
-        console.log("START drag batch");
         !this._flush && (this._flush = UndoManager.StartBatch("golden layout drag"));
         const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) :
-            {
-                type: 'row',
-                content: dragDocs.map((doc, i) => CollectionDockingView.makeDocumentConfig(doc))
-            };
+            { type: 'row', content: dragDocs.map((doc, i) => CollectionDockingView.makeDocumentConfig(doc)) };
         const dragSource = this._goldenLayout.createDragSource(document.createElement("div"), config);
-        dragSource._dragListener.on("dragStop", () => dragSource.destroy());
+        dragSource._dragListener.on("dragStop", dragSource.destroy);
         dragSource._dragListener.onMouseDown(e);
     }
 
-    @undoBatch
-    @action
-    public OpenFullScreen(docView: DocumentView, libraryPath?: Doc[]) {
-        if (docView.props.Document._viewType === CollectionViewType.Docking && docView.props.Document.layoutKey === "layout") {
-            return CurrentUserUtils.openDashboard(Doc.UserDoc(), docView.props.Document);
-        }
-        const document = Doc.MakeAlias(docView.props.Document);
-        const newItemStackConfig = {
-            type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath)]
-        };
-        const docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
-        this._goldenLayout.root.contentItems[0].addChild(docconfig);
-        docconfig.callDownwards('_$init');
-        this._goldenLayout._$maximiseItem(docconfig);
-        this._maximizedSrc = docView;
-        this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
-        this.stateChanged();
-        SelectionManager.DeselectAll();
-    }
-
     @undoBatch
     public CloseFullScreen = () => {
         const target = this._goldenLayout._maximisedItem;
-        if (target !== null && this._maximizedSrc) {
-            this._goldenLayout._maximisedItem.remove();
-            SelectionManager.SelectDoc(this._maximizedSrc, false);
-            this._maximizedSrc = undefined;
+        if (target) {
+            target.remove();
             this.stateChanged();
         }
     }
 
-    public HasFullScreen = () => {
-        return this._goldenLayout._maximisedItem !== null;
-    }
-
     @undoBatch
     @action
     public static CloseRightSplit(document: Opt<Doc>): boolean {
@@ -136,17 +102,29 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         const retVal = !CollectionDockingView.Instance?._goldenLayout.root.contentItems[0].isRow ? false :
             Array.from(CollectionDockingView.Instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => Array.from(child.contentItems).some(tryClose));
 
-        retVal && CollectionDockingView.Instance.layoutChanged(document);
+        retVal && document && CollectionDockingView.Instance.layoutChanged();
         return retVal;
     }
 
     @undoBatch
     @action
-    layoutChanged(removed?: Doc) {
-        this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
-        this._goldenLayout.emit('stateChanged');
-        this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
-        this.stateChanged();
+    public static OpenFullScreen(doc: Doc, libraryPath?: Doc[]) {
+        const instance = CollectionDockingView.Instance;
+        if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") {
+            return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
+        }
+        const newItemStackConfig = {
+            type: 'stack',
+            content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc), undefined)]
+        };
+        const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+        instance._goldenLayout.root.contentItems[0].addChild(docconfig);
+        docconfig.callDownwards('_$init');
+        instance._goldenLayout._$maximiseItem(docconfig);
+        instance._goldenLayout.emit('stateChanged');
+        instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig());
+        instance.stateChanged();
+        return true;
     }
 
     public static ReplaceRightSplit(document: Doc, libraryPath?: Doc[], addToSplit?: boolean): boolean {
@@ -156,14 +134,16 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
                 if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
                     DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)?.Document.isDisplayPanel) {
-                    const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
+                    const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined);
+                    runInAction(() => document.isDisplayPanel = true);
                     child.addChild(newItemStackConfig, undefined);
                     !addToSplit && child.contentItems[0].remove();
                     return true;
                 }
                 return Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
                     if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)?.Document.isDisplayPanel) {
-                        const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
+                        const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined);
+                        runInAction(() => document.isDisplayPanel = true);
                         child.addChild(newItemStackConfig, undefined);
                         !addToSplit && child.contentItems[j].remove();
                         return true;
@@ -171,7 +151,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                     return false;
                 });
             });
-        retVal && instance.layoutChanged(document);
+        retVal && instance.layoutChanged();
         return retVal;
     }
 
@@ -183,9 +163,9 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     public static AddRightSplit(document: Doc, dontSelect: boolean = false, isDisplayPanel: Opt<boolean> = undefined) {
         if (!CollectionDockingView.Instance) return false;
 
-        const ind = CollectionDockingView.Instance._tabMap.findIndex((val) => val.doc === document);
+        const ind = Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === document);
         if (ind !== -1) {
-            const tab = CollectionDockingView.Instance._tabMap[ind].tab;
+            const tab = Array.from(CollectionDockingView.Instance.tabMap.keys())[ind];
             const activeContentItem = tab.header.parent.getActiveContentItem();
             if (tab.contentItem !== activeContentItem) {
                 tab.header.parent.setActiveContentItem(tab.contentItem);
@@ -198,7 +178,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             const instance = CollectionDockingView.Instance;
             const newItemStackConfig = {
                 type: 'stack',
-                content: [CollectionDockingView.makeDocumentConfig(document, undefined, [])]
+                content: [CollectionDockingView.makeDocumentConfig(document, undefined)]
             };
 
             const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
@@ -238,7 +218,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         const instance = CollectionDockingView.Instance;
         const newItemStackConfig = {
             type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath)]
+            content: [CollectionDockingView.makeDocumentConfig(document, undefined)]
         };
 
         const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
@@ -299,16 +279,20 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     //
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
     //
+    @undoBatch
     public static UseRightSplit(document: Doc, libraryPath?: Doc[], shiftKey?: boolean) {
         if (shiftKey || !CollectionDockingView.ReplaceRightSplit(document, libraryPath, shiftKey)) {
-            CollectionDockingView.AddRightSplit(document, false, true);
+            return CollectionDockingView.AddRightSplit(document, false, true);
         }
+        return false;
     }
 
-    public AddTab = (stack: any, document: Doc, libraryPath?: Doc[]) => {
-        const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
+    @undoBatch
+    public static AddTab(stack: any, document: Doc, libraryPath?: Doc[]) {
+        const instance = CollectionDockingView.Instance;
+        const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined);
         if (stack === undefined) {
-            let stack: any = this._goldenLayout.root;
+            stack = instance._goldenLayout.root;
             while (!stack.isStack) {
                 if (stack.contentItems.length) {
                     stack = stack.contentItems[0];
@@ -318,41 +302,21 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                     break;
                 }
             }
-            if (stack) {
-                stack.addChild(docContentConfig);
-            }
-        } else {
-            stack.addChild(docContentConfig, undefined);
         }
-        this.layoutChanged();
+        stack?.addChild(docContentConfig, undefined);
+        instance.layoutChanged();
         return true;
     }
 
-    public ReplaceTab = (stack: any, document: Doc, libraryPath?: Doc[]) => {
-        const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
-        if (stack === undefined) {
-            let stack: any = this._goldenLayout.root;
-            while (!stack.isStack) {
-                if (stack.contentItems.length) {
-                    stack = stack.contentItems[0];
-                } else {
-                    stack.addChild({ type: 'stack', content: [docContentConfig] });
-                    stack = undefined;
-                    break;
-                }
-            }
-            if (stack) {
-                stack.addChild(docContentConfig);
-            }
-        } else {
-            stack.addChild(docContentConfig, undefined);
-        }
-        this.layoutChanged();
-        return true;
+    @undoBatch
+    @action
+    layoutChanged() {
+        this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
+        this._goldenLayout.emit('stateChanged');
+        this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
+        this.stateChanged();
     }
 
-    _tabMap: { tab: any, doc: Doc }[] = [];
-
     async setupGoldenLayout() {
         const config = StrCast(this.props.Document.dockingConfig);
         if (config) {
@@ -360,27 +324,23 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) ?? [];
             await Promise.all(docids.map(id => DocServer.GetRefField(id)));
 
-            if (!this._goldenLayout) {
-                runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
-            }
-            else {
+            if (this._goldenLayout) {
                 if (config === JSON.stringify(this._goldenLayout.toConfig())) {
                     return;
+                } else {
+                    try {
+                        this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
+                        this._goldenLayout.unbind('stackCreated', this.stackCreated);
+                    } catch (e) { }
                 }
-                try {
-                    this._goldenLayout.unbind('tabCreated', this.tabCreated);
-                    this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
-                    this._goldenLayout.unbind('stackCreated', this.stackCreated);
-                } catch (e) { }
-                this._goldenLayout.destroy();
-                runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
             }
-            this._goldenLayout.on('tabCreated', this.tabCreated);
+            this.tabMap.clear();
+            this._goldenLayout?.destroy();
+            runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
             this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
             this._goldenLayout.on('stackCreated', this.stackCreated);
             this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
             this._goldenLayout.container = this._containerRef.current;
-            //this._goldenLayout.on("stateChanged", () => console.log("STATE CHANGED"));
             if (this._goldenLayout.config.maximisedItemId === '__glMaximised') {
                 try {
                     this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise();
@@ -391,17 +351,15 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             this._goldenLayout.init();
         }
     }
-    reactionDisposer?: Lambda;
+
     componentDidMount: () => void = () => {
         if (this._containerRef.current) {
-            const observer = new _global.ResizeObserver(this.onResize);
-            observer.observe(this._containerRef.current);
-            this.reactionDisposer = reaction(
-                () => StrCast(this.props.Document.dockingConfig),
+            new _global.ResizeObserver(this.onResize).observe(this._containerRef.current);
+            this._reactionDisposer = reaction(() => StrCast(this.props.Document.dockingConfig),
                 config => {
                     if (!this._goldenLayout || this._ignoreStateChange !== config) {
                         this.setupGoldenLayout();
-                        DocListCast((Doc.UserDoc().myDashboards as Doc).data).map(d => d.dashboardBrush = false);
+                        DocListCast(CurrentUserUtils.MyDashboards.data).map(d => d.dashboardBrush = false);
                         this.props.Document.dashboardBrush = true;
                     }
                     this._ignoreStateChange = "";
@@ -410,24 +368,19 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
         }
     }
+
     componentWillUnmount: () => void = () => {
         try {
             this.props.Document.dashboardBrush = false;
-            this._goldenLayout.unbind('tabCreated', this.tabCreated);
             this._goldenLayout.unbind('stackCreated', this.stackCreated);
             this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
-        } catch (e) {
-
-        }
+        } catch (e) { }
         this._goldenLayout?.destroy();
-        runInAction(() => {
-            CollectionDockingView.Instances.splice(CollectionDockingView.Instances.indexOf(this), 1);
-            this._goldenLayout = null;
-        });
         window.removeEventListener('resize', this.onResize);
 
-        this.reactionDisposer?.();
+        this._reactionDisposer?.();
     }
+
     @action
     onResize = (event: any) => {
         const cur = this._containerRef.current;
@@ -438,31 +391,29 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     @action
     onPointerUp = (e: MouseEvent): void => {
         window.removeEventListener("pointerup", this.onPointerUp);
-        this._isPointerDown = false;
         if (this._flush) {
             setTimeout(() => {
                 CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
                 this.stateChanged();
-                console.log("END BATCH Up");
                 this._flush!.end();
                 this._flush = undefined;
             }, 10);
         }
     }
+
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         window.addEventListener("mouseup", this.onPointerUp);
 
         if (!(e.target as HTMLElement).closest("*.lm_content") && ((e.target as HTMLElement).closest("*.lm_tab") || (e.target as HTMLElement).closest("*.lm_stack"))) {
-            console.log("START BATCH dwn");
             this._flush = UndoManager.StartBatch("golden layout edit");
         }
-        if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
-            return;
-        } else {
+        if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) &&
+            Doc.GetSelectedTool() !== InkTool.Highlighter && Doc.GetSelectedTool() !== InkTool.Pen) {
             e.stopPropagation();
         }
     }
+
     public static Copy(doc: Doc) {
         let json = StrCast(doc.dockingConfig);
         const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
@@ -504,124 +455,18 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         Doc.GetProto(other).data = new List<Doc>(Array.from(otherSet.values()));
     }
 
-    tabCreated = (tab: any) => {
-        tab.titleElement[0].Tab = tab;
-        if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
-            if (tab.contentItem.config.fixed) {
-                tab.contentItem.parent.config.fixed = true;
-            }
-
-            const doc = DocServer.GetCachedRefField(tab.contentItem.config.props.documentId) as Doc;
-            if (doc instanceof Doc) {
-                this._tabMap.push({ tab: tab, doc: doc });
-                tab.titleElement[0].onclick = (e: any) => {
-                    if (Date.now() - tab.titleElement[0].lastClick < 1000) tab.titleElement[0].select();
-                    tab.titleElement[0].lastClick = Date.now();
-                    tab.titleElement[0].focus();
-                };
-                tab.titleElement[0].onchange = (e: any) => {
-                    tab.titleElement[0].size = e.currentTarget.value.length + 1;
-                    Doc.GetProto(doc).title = e.currentTarget.value, true;
-                };
-                tab.titleElement[0].size = StrCast(doc.title).length + 1;
-                tab.titleElement[0].value = doc.title;
-                tab.titleElement[0].style["max-width"] = "100px";
-                const gearSpan = document.createElement("span");
-                gearSpan.className = "collectionDockingView-gear";
-                gearSpan.style.position = "relative";
-                gearSpan.style.paddingLeft = "0px";
-                gearSpan.style.paddingRight = "12px";
-                const stack = tab.contentItem.parent;
-                tab.element[0].onpointerdown = (e: any) => {
-                    if (e.target.className !== "lm_close_tab") {
-                        const view = DocumentManager.Instance.getDocumentView(doc);
-                        view && SelectionManager.SelectDoc(view, false);
-                    }
-                };
-                // shifts the focus to this tab when another tab is dragged over it
-                tab.element[0].onmouseenter = (e: any) => {
-                    if (!this._isPointerDown || !SnappingManager.GetIsDragging()) return;
-                    const activeContentItem = tab.header.parent.getActiveContentItem();
-                    if (tab.contentItem !== activeContentItem) {
-                        tab.header.parent.setActiveContentItem(tab.contentItem);
-                    }
-                    tab.setActive(true);
-                };
-                const onDown = (e: React.PointerEvent) => {
-                    setupMoveUpEvents(this, e, (e) => {
-                        if (!(e as any).defaultPrevented) {
-                            const dragData = new DragManager.DocumentDragData([doc]);
-                            dragData.dropAction = doc.dropAction as dropActionType;
-                            DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY);
-                            return true;
-                        }
-                        return false;
-                    }, returnFalse, emptyFunction);
-                };
-
-                tab.selectionDisposer = reaction(() => SelectionManager.SelectedDocuments(),
-                    (sel) => {
-                        const selected = sel.some(v => v.props.Document === doc);
-                        selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && tab.header.parent.setActiveContentItem(tab.contentItem);
-                    }
-                );
-                tab.buttonDisposer = reaction(() => ((view: Opt<DocumentView>) => view ? [view] : [])(DocumentManager.Instance.getDocumentView(doc)),
-                    (views) => {
-                        if (views.length) {
-                            ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} >
-                                <DockingViewButtonSelector views={() => views} Stack={stack} />
-                            </span>,
-                                gearSpan);
-                            tab.buttonDisposer?.();
-                        }
-                    }, { fireImmediately: true });
-
-                tab.reactComponents = [gearSpan];
-                tab.element.append(gearSpan);
-                tab.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
-                    tab.titleElement[0].value = title;
-                    tab.titleElement[0].style.padding = degree ? 0 : 2;
-                    tab.titleElement[0].style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
-                }, { fireImmediately: true });
-                //TODO why can't this just be doc instead of the id?
-                tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
-            }
-        }
-        tab.closeElement.off('click') //unbind the current click handler
-            .click(function () {
-                tab.selectionDisposer?.();
-                tab.reactionDisposer?.();
-                tab.buttonDisposer?.();
-                const doc = DocServer.GetCachedRefField(tab.contentItem.config.props.documentId);
-                if (doc instanceof Doc) {
-                    const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null);
-                    recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
-                    SelectionManager.DeselectAll();
-                }
-                CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
-                tab.contentItem.remove();
-                CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
-            });
-    }
-
     tabDestroyed = (tab: any) => {
-        const ind = this._tabMap.findIndex((val) => val.tab === tab);
-        ind !== -1 && this._tabMap.splice(ind, 1);
-        if (tab.reactComponents) {
-            for (const ele of tab.reactComponents) {
-                ReactDOM.unmountComponentAtNode(ele);
-            }
-        }
+        this.tabMap.delete(tab);
+        Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
+        tab.reactComponents && Array.from(tab.reactComponents).forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
     }
 
-
     stackCreated = (stack: any) => {
-        //stack.header.controlsContainer.find('.lm_popout').hide();
         stack.header.element.on('mousedown', (e: any) => {
             if (e.target === stack.header.element[0] && e.button === 2) {
-                const emptyPane = Cast(Doc.UserDoc().emptyPane, Doc, null);
+                const emptyPane = CurrentUserUtils.EmptyPane;
                 emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
-                this.AddTab(stack, Docs.Create.FreeformDocument([], {
+                CollectionDockingView.AddTab(stack, Docs.Create.FreeformDocument([], {
                     _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
                 }));
             }
@@ -632,59 +477,125 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             .click(action(() => {
                 //if (confirm('really close this?')) {
                 stack.remove();
-                stack.contentItems.forEach((contentItem: any) => {
-                    const doc = Cast(DocServer.GetCachedRefField(contentItem.config.props.documentId), Doc, null);
-                    if (doc instanceof Doc) {
-                        const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null);
-                        recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
-                    }
-                });
+                stack.contentItems.forEach((contentItem: any) => Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", contentItem.tab.DashDoc, undefined, true, true));
             }));
         stack.header.controlsContainer.find('.lm_popout') //get the close icon
             .off('click') //unbind the current click handler
             .click(action(() => {
                 // stack.config.fixed = !stack.config.fixed;  // force the stack to have a fixed size
-                const emptyPane = Cast(Doc.UserDoc().emptyPane, Doc, null);
+                const emptyPane = CurrentUserUtils.EmptyPane;
                 emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
-                this.AddTab(stack, Docs.Create.FreeformDocument([], {
+                CollectionDockingView.AddTab(stack, Docs.Create.FreeformDocument([], {
                     _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
                 }));
             }));
     }
 
     render() {
-        if (this.props.renderDepth > 0) {
-            return <div style={{ width: "100%", height: "100%" }}>Nested dashboards can't be rendered</div>;
-        }
-        return <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} ref={this._containerRef} />;
+        return <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} ref={this._containerRef}>
+            {this.props.renderDepth > 0 ? "Nested dashboards can't be rendered" : (null)}
+        </div>;
     }
 }
 
 interface DockedFrameProps {
     documentId: FieldId;
     glContainer: any;
-    libraryPath: (FieldId[]);
-    //collectionDockingView: CollectionDockingView
 }
 @observer
 export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     _mainCont: HTMLDivElement | null = null;
+    _tabReaction: IReactionDisposer | undefined;
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
-    @observable private _document: Opt<Doc>;
     @observable private _isActive: boolean = false;
-    _tabReaction: IReactionDisposer | undefined;
 
-    get _stack(): any {
-        return (this.props as any).glContainer.parent.parent;
-    }
-    get _tab(): any {
-        const tab = (this.props as any).glContainer.tab?.element[0] as HTMLElement;
-        return tab?.getElementsByClassName("lm_title")?.[0];
-    }
+    get stack(): any { return (this.props as any).glContainer.parent.parent; }
+    get tab() { return (this.props as any).glContainer.tab; }
+    get tabTitle(): any { return (this.tab?.element[0] as HTMLElement).getElementsByClassName("lm_title")?.[0]; }
+    get view() { return this._document && DocumentManager.Instance.getDocumentView(this._document); }
+    @observable _document: Doc | undefined;
+
     constructor(props: any) {
         super(props);
-        this._document = Cast(DocServer.GetCachedRefField(this.props.documentId), Doc, null);
+        setTimeout(() => DocServer.GetRefField(this.tab.contentItem.config.props.documentId).then(doc => this.init(doc as Doc)), 0);
+    }
+
+    @action
+    init = (doc: Doc) => {
+        const tab = this.tab;
+        tab._disposers = {} as { [name: string]: IReactionDisposer };
+        tab.DashDoc = this._document = doc;
+        if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
+            tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
+
+            CollectionDockingView.Instance.tabMap.add(tab);
+            tab.titleElement[0].onclick = (e: any) => {
+                if (Date.now() - tab.titleElement[0].lastClick < 1000) tab.titleElement[0].select();
+                tab.titleElement[0].lastClick = Date.now();
+                tab.titleElement[0].focus();
+            };
+            tab.titleElement[0].onchange = (e: any) => {
+                tab.titleElement[0].size = e.currentTarget.value.length + 1;
+                Doc.GetProto(doc).title = e.currentTarget.value;
+            };
+            tab.titleElement[0].size = StrCast(this._document.title).length + 1;
+            tab.titleElement[0].value = this._document.title;
+            tab.titleElement[0].style["max-width"] = "100px";
+            const gearSpan = document.createElement("span");
+            gearSpan.className = "collectionDockingView-gear";
+            gearSpan.style.position = "relative";
+            gearSpan.style.paddingLeft = "0px";
+            gearSpan.style.paddingRight = "12px";
+            const stack = tab.contentItem.parent;
+            tab.element[0].onpointerdown = (e: any) => {
+                e.target.className !== "lm_close_tab" && this.view && SelectionManager.SelectDoc(this.view, false);
+            };
+            // shifts the focus to this tab when another tab is dragged over it
+            tab.element[0].onmouseenter = (e: MouseEvent) => {
+                if (SnappingManager.GetIsDragging() && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
+                    tab.header.parent.setActiveContentItem(tab.contentItem);
+                }
+                tab.setActive(true);
+            };
+            const onDown = (e: React.PointerEvent) => {
+                setupMoveUpEvents(this, e, (e) => {
+                    !e.defaultPrevented && DragManager.StartDocumentDrag([gearSpan], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY);
+                    return !e.defaultPrevented;
+                }, returnFalse, emptyFunction);
+            };
+
+            tab._disposers.selectionDisposer = reaction(() => SelectionManager.SelectedDocuments().some(v => v.props.Document === doc),
+                (selected) => {
+                    selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && tab.header.parent.setActiveContentItem(tab.contentItem);
+                }
+            );
+            tab._disposers.buttonDisposer = reaction(() => this.view,
+                (view) => {
+                    if (view) {
+                        ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} >
+                            <DockingViewButtonSelector views={() => [view]} Stack={stack} />
+                        </span>,
+                            gearSpan);
+                        tab._disposers.buttonDisposer?.();
+                    }
+                }, { fireImmediately: true });
+
+            tab.reactComponents = [gearSpan];
+            tab.element.append(gearSpan);
+            tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
+                tab.titleElement[0].value = title;
+                tab.titleElement[0].style.padding = degree ? 0 : 2;
+                tab.titleElement[0].style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
+            }, { fireImmediately: true });
+        }
+        tab.closeElement.off('click') //unbind the current click handler
+            .click(function () {
+                Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
+                Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true);
+                SelectionManager.DeselectAll();
+                tab.contentItem.remove();
+            });
     }
     /**
      * Adds a document to the presentation view
@@ -695,7 +606,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         if (unpin) DockedFrameRenderer.UnpinDoc(doc);
         else {
             //add this new doc to props.Document
-            const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
+            const curPres = CurrentUserUtils.ActivePresentation;
             if (curPres) {
                 const pinDoc = Doc.MakeAlias(doc);
                 pinDoc.presentationTargetDoc = doc;
@@ -717,7 +628,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @action
     public static UnpinDoc(doc: Doc) {
         //add this new doc to props.Document
-        const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
+        const curPres = CurrentUserUtils.ActivePresentation;
         if (curPres) {
             const ind = DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc));
             ind !== -1 && Doc.RemoveDocFromList(curPres, "data", DocListCast(curPres.data)[ind]);
@@ -727,7 +638,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     componentDidMount() {
         const color = () => StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white");
         const selected = () => SelectionManager.SelectedDocuments().some(v => v.props.Document === this._document);
-        const updateTabColor = () => this._tab && (this._tab.style.backgroundColor = selected() ? color() : "");
+        const updateTabColor = () => this.tabTitle && (this.tabTitle.style.backgroundColor = selected() ? color() : "");
         const observer = new _global.ResizeObserver(action((entries: any) => {
             for (const entry of entries) {
                 this._panelWidth = entry.contentRect.width;
@@ -750,10 +661,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     private onActiveContentItemChanged() {
         if (this.props.glContainer.tab && this._isActive !== this.props.glContainer.tab.isActive) {
             this._isActive = this.props.glContainer.tab.isActive;
-            this._isActive && setTimeout(() => {
-                const dv = this._document && DocumentManager.Instance.getFirstDocumentView(this._document);
-                dv && SelectionManager.SelectDoc(dv, false);
-            });
+            this._isActive && setTimeout(() => this.view && SelectionManager.SelectDoc(this.view, false), 0);
             (CollectionDockingView.Instance as any)._goldenLayout.isInitialised && CollectionDockingView.Instance.stateChanged();
             !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
         }
@@ -764,7 +672,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     panelWidth = () => this.layoutDoc?.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) :
         (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth)
     panelHeight = () => this.nativeAspect() && this.nativeAspect() > this._panelWidth / this._panelHeight ? this._panelWidth / this.nativeAspect() : this._panelHeight;
-
     nativeWidth = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeWidth) || this._panelWidth : 0;
     nativeHeight = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeHeight) || this._panelHeight : 0;
 
@@ -778,17 +685,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             this._panelHeight / NumCast(this.layoutDoc!._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!._nativeWidth))) {
             scaling = this._panelWidth / NumCast(this.layoutDoc!._nativeWidth);
         } else if (nativeW && nativeH) {
-            // if (this.layoutDoc!.type === DocumentType.PDF || this.layoutDoc!.type === DocumentType.WEB) {
-            //     if ((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);
-            //     }
-            // }
             const wscale = this.panelWidth() / nativeW;
             scaling = wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
-        } else scaling = 1;
+        }
         return scaling;
     }
 
@@ -805,17 +704,14 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
 
     addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => {
         SelectionManager.DeselectAll();
-        if (doc._viewType === CollectionViewType.Docking) {
-            return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
-        } else if (location === "onRight") {
-            return CollectionDockingView.AddRightSplit(doc);
-        } else if (location === "close") {
-            return CollectionDockingView.CloseRightSplit(doc);
-        } else if (location === "replace") {
-            CollectionDockingView.UseRightSplit(doc);
-            return true;
-        } else {// if (location === "inPlace") {
-            return CollectionDockingView.Instance.AddTab(this._stack, doc, libraryPath);
+        if (doc._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
+        switch (location) {
+            case "onRight": return CollectionDockingView.AddRightSplit(doc);
+            case "close": return CollectionDockingView.CloseRightSplit(doc);
+            case "replace": return CollectionDockingView.UseRightSplit(doc);
+            case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
+            case "inPlace":
+            default: return CollectionDockingView.AddTab(this.stack, doc);
         }
     }
 
@@ -833,7 +729,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     childLayoutTemplate = () => Cast(this._document?.childLayoutTemplate, Doc, null);
     returnMiniSize = () => NumCast(this._document?._miniMapSize, 150);
     miniDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
+        this._document && setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
             this._document!._panX = clamp(NumCast(this._document!._panX) + delta[0] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.l, this.renderContentBounds.l + this.renderContentBounds.dim);
             this._document!._panY = clamp(NumCast(this._document!._panY) + delta[1] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.t, this.renderContentBounds.t + this.renderContentBounds.dim);
             return false;
@@ -899,12 +795,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @computed get docView() {
         TraceMobx();
         if (!this._document) return (null);
-        const document = this._document;
         const resolvedDataDoc = !Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined;
         return <>
-            <DocumentView key={document[Id]}
+            <DocumentView key={this._document[Id]}
                 LibraryPath={emptyPath}
-                Document={document}
+                Document={this._document}
                 DataDoc={resolvedDataDoc}
                 bringToFront={emptyFunction}
                 rootSelected={returnTrue}
@@ -927,7 +822,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
                 ContainingCollectionView={undefined}
                 ContainingCollectionDoc={undefined} />
-            {document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
+            {this._document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
         </>;
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index cdfa67769..fffbe65a3 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -738,7 +738,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
     }
     onContextMenu = (e: React.MouseEvent): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
-        if (!e.isPropagationStopped() && this.doc === Doc.UserDoc().myDashboards) {
+        if (!e.isPropagationStopped() && this.doc === CurrentUserUtils.MyDashboards) {
             ContextMenu.Instance.addItem({ description: "Create Dashboard", event: () => CurrentUserUtils.createNewDashboard(Doc.UserDoc()), icon: "plus" });
             ContextMenu.Instance.addItem({ description: "Delete Dashboard", event: () => this.remove(this.doc), icon: "minus" });
             e.stopPropagation();
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 11a905fb6..8e391f995 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -379,6 +379,14 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
                         onPointerDown={() => this.changeFollowBehavior("onRight")}>
                         Always open in a new pane
                         </div>
+                    <div className="linkEditor-followingDropdown-option"
+                        onPointerDown={() => this.changeFollowBehavior("replace")}>
+                        Always replace right tab
+                        </div>
+                    <div className="linkEditor-followingDropdown-option"
+                        onPointerDown={() => this.changeFollowBehavior("fullScreen")}>
+                        Always open full screen
+                        </div>
                     <div className="linkEditor-followingDropdown-option"
                         onPointerDown={() => this.changeFollowBehavior("inTab")}>
                         Always open in a new tab
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index a24761195..d7a8ed404 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -492,7 +492,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
 
     }
     render() {
-        const myDashboards = DocListCast(Cast(Doc.UserDoc().myDashboards, Doc, null).data);
+        const myDashboards = DocListCast(CurrentUserUtils.MyDashboards.data);
         return (
             <div style={{ pointerEvents: "all" }} className="searchBox-container">
                 <div style={{ position: "absolute", left: 15, height: 32, alignItems: "center", display: "flex" }}>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index cb4ca9a25..1401dd6a1 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -939,19 +939,16 @@ export namespace Doc {
     }
 
     export function IsBrushed(doc: Doc) {
-        return false;
         return computedFn(function IsBrushed(doc: Doc) {
             return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc));
         })(doc);
     }
     // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
     export function IsBrushedDegreeUnmemoized(doc: Doc) {
-        return 0;
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0;
         return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
     }
     export function IsBrushedDegree(doc: Doc) {
-        return 0;
         return computedFn(function IsBrushDegree(doc: Doc) {
             return Doc.IsBrushedDegreeUnmemoized(doc);
         })(doc);
@@ -980,7 +977,6 @@ export namespace Doc {
 
     let _lastDate = 0;
     export function linkFollowHighlight(destDoc: Doc, dataAndDisplayDocs = true) {
-        return;
         linkFollowUnhighlight();
         Doc.HighlightDoc(destDoc, dataAndDisplayDocs);
         document.removeEventListener("pointerdown", linkFollowUnhighlight);
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 841862f49..d4eb76ecd 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -324,7 +324,7 @@ export class MobileInterface extends React.Component {
             );
         }
         // stores dashboards documents as 'dashboards' variable
-        let dashboards = Cast(Doc.UserDoc().myDashboards, Doc) as Doc;
+        let dashboards = CurrentUserUtils.MyDashboards;
         if (this.dashboards) {
             dashboards = this.dashboards;
         }
@@ -394,7 +394,7 @@ export class MobileInterface extends React.Component {
      */
     @action
     createNewDashboard = async (id?: string) => {
-        const scens = Cast(Doc.UserDoc().myDashboards, Doc) as Doc;
+        const scens = CurrentUserUtils.MyDashboards;
         const dashboardCount = DocListCast(scens.data).length + 1;
         const freeformOptions: DocumentOptions = {
             x: 0,
-- 
cgit v1.2.3-70-g09d2


From 53e29fd0fcb09f443978b26dc31c37cf35241dae Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 29 Aug 2020 11:06:12 -0400
Subject: fixes to tab dragging broken in last commits

---
 .../views/collections/CollectionDockingView.tsx    | 79 +++++++++++-----------
 1 file changed, 40 insertions(+), 39 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 201cada09..cc3d017be 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -73,7 +73,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) :
             { type: 'row', content: dragDocs.map((doc, i) => CollectionDockingView.makeDocumentConfig(doc)) };
         const dragSource = this._goldenLayout.createDragSource(document.createElement("div"), config);
-        dragSource._dragListener.on("dragStop", dragSource.destroy);
+        //dragSource._dragListener.on("dragStop", dragSource.destroy);
         dragSource._dragListener.onMouseDown(e);
     }
 
@@ -329,6 +329,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                     return;
                 } else {
                     try {
+                        this._goldenLayout.unbind('tabCreated', this.tabCreated);
                         this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
                         this._goldenLayout.unbind('stackCreated', this.stackCreated);
                     } catch (e) { }
@@ -337,6 +338,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             this.tabMap.clear();
             this._goldenLayout?.destroy();
             runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
+            this._goldenLayout.on('tabCreated', this.tabCreated);
             this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
             this._goldenLayout.on('stackCreated', this.stackCreated);
             this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
@@ -458,7 +460,10 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     tabDestroyed = (tab: any) => {
         this.tabMap.delete(tab);
         Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
-        tab.reactComponents && Array.from(tab.reactComponents).forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
+        tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
+    }
+    tabCreated = (tab: any) => {
+        (tab.contentItem.element[0]?.firstChild?.firstChild as any)?.InitTab(tab);
     }
 
     stackCreated = (stack: any) => {
@@ -509,25 +514,18 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
     @observable private _isActive: boolean = false;
+    @observable private _document: Doc | undefined;
 
     get stack(): any { return (this.props as any).glContainer.parent.parent; }
     get tab() { return (this.props as any).glContainer.tab; }
-    get tabTitle(): any { return (this.tab?.element[0] as HTMLElement).getElementsByClassName("lm_title")?.[0]; }
     get view() { return this._document && DocumentManager.Instance.getDocumentView(this._document); }
-    @observable _document: Doc | undefined;
-
-    constructor(props: any) {
-        super(props);
-        setTimeout(() => DocServer.GetRefField(this.tab.contentItem.config.props.documentId).then(doc => this.init(doc as Doc)), 0);
-    }
 
     @action
-    init = (doc: Doc) => {
-        const tab = this.tab;
-        tab._disposers = {} as { [name: string]: IReactionDisposer };
-        tab.DashDoc = this._document = doc;
-        if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
+    init = (tab: any, doc: Opt<Doc>) => {
+        if (tab.DashDoc !== doc && doc && tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
+            tab._disposers = {} as { [name: string]: IReactionDisposer };
             tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
+            tab.DashDoc = doc;
 
             CollectionDockingView.Instance.tabMap.add(tab);
             tab.titleElement[0].onclick = (e: any) => {
@@ -539,8 +537,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 tab.titleElement[0].size = e.currentTarget.value.length + 1;
                 Doc.GetProto(doc).title = e.currentTarget.value;
             };
-            tab.titleElement[0].size = StrCast(this._document.title).length + 1;
-            tab.titleElement[0].value = this._document.title;
+            tab.titleElement[0].size = StrCast(doc.title).length + 1;
+            tab.titleElement[0].value = doc.title;
             tab.titleElement[0].style["max-width"] = "100px";
             const gearSpan = document.createElement("span");
             gearSpan.className = "collectionDockingView-gear";
@@ -588,14 +586,14 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 tab.titleElement[0].style.padding = degree ? 0 : 2;
                 tab.titleElement[0].style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
             }, { fireImmediately: true });
+            tab.closeElement.off('click') //unbind the current click handler
+                .click(function () {
+                    Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
+                    Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true);
+                    SelectionManager.DeselectAll();
+                    tab.contentItem.remove();
+                });
         }
-        tab.closeElement.off('click') //unbind the current click handler
-            .click(function () {
-                Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
-                Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true);
-                SelectionManager.DeselectAll();
-                tab.contentItem.remove();
-            });
     }
     /**
      * Adds a document to the presentation view
@@ -638,7 +636,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     componentDidMount() {
         const color = () => StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white");
         const selected = () => SelectionManager.SelectedDocuments().some(v => v.props.Document === this._document);
-        const updateTabColor = () => this.tabTitle && (this.tabTitle.style.backgroundColor = selected() ? color() : "");
+        const updateTabColor = () => this.tab?.titleElement[0] && (this.tab.titleElement[0].style.backgroundColor = selected() ? color() : "");
         const observer = new _global.ResizeObserver(action((entries: any) => {
             for (const entry of entries) {
                 this._panelWidth = entry.contentRect.width;
@@ -672,8 +670,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     panelWidth = () => this.layoutDoc?.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) :
         (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth)
     panelHeight = () => this.nativeAspect() && this.nativeAspect() > this._panelWidth / this._panelHeight ? this._panelWidth / this.nativeAspect() : this._panelHeight;
-    nativeWidth = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeWidth) || this._panelWidth : 0;
-    nativeHeight = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeHeight) || this._panelHeight : 0;
+    nativeWidth = () => !this.layoutDoc?._fitWidth ? NumCast(this.layoutDoc?._nativeWidth) || this._panelWidth : 0;
+    nativeHeight = () => !this.layoutDoc?._fitWidth ? NumCast(this.layoutDoc?._nativeHeight) || this._panelHeight : 0;
 
     contentScaling = () => {
         const nativeH = this.nativeHeight();
@@ -681,9 +679,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         let scaling = 1;
         if (!this.layoutDoc?._fitWidth && (!nativeW || !nativeH)) {
             scaling = 1;
-        } else if (NumCast(this.layoutDoc!._nativeWidth) && ((this.layoutDoc?._fitWidth) ||
-            this._panelHeight / NumCast(this.layoutDoc!._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!._nativeWidth))) {
-            scaling = this._panelWidth / NumCast(this.layoutDoc!._nativeWidth);
+        } else if (NumCast(this.layoutDoc?._nativeWidth) && ((this.layoutDoc?._fitWidth) ||
+            this._panelHeight / NumCast(this.layoutDoc?._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc?._nativeWidth))) {
+            scaling = this._panelWidth / NumCast(this.layoutDoc?._nativeWidth);
         } else if (nativeW && nativeH) {
             const wscale = this.panelWidth() / nativeW;
             scaling = wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
@@ -794,13 +792,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
     @computed get docView() {
         TraceMobx();
-        if (!this._document) return (null);
-        const resolvedDataDoc = !Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined;
-        return <>
-            <DocumentView key={this._document[Id]}
+        return !this._document ? (null) :
+            <><DocumentView key={this._document[Id]}
                 LibraryPath={emptyPath}
                 Document={this._document}
-                DataDoc={resolvedDataDoc}
+                DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
                 bringToFront={emptyFunction}
                 rootSelected={returnTrue}
                 addDocument={undefined}
@@ -822,16 +818,21 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
                 ContainingCollectionView={undefined}
                 ContainingCollectionDoc={undefined} />
-            {this._document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
-        </>;
+                {this._document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
+            </>;
     }
 
     render() {
-        return (!this._isActive || !this.layoutDoc) ? (null) :
-            (<div className="collectionDockingView-content" ref={ref => this._mainCont = ref}
+        return !this._isActive ? (null) :
+            (<div className="collectionDockingView-content" ref={ref => {
+                if (this._mainCont = ref) {
+                    (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
+                    DocServer.GetRefField(this.tab.contentItem.config.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.init(this.tab, this._document)));
+                }
+            }}
                 style={{
                     transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`,
-                    height: this.layoutDoc && this.layoutDoc._fitWidth ? undefined : "100%",
+                    height: this.layoutDoc?._fitWidth ? undefined : "100%",
                     width: this.widthpercent
                 }}>
                 {this.docView}
-- 
cgit v1.2.3-70-g09d2


From 6414d1a344c80a75c2d4fb91e3cd7126e23c4cb1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 29 Aug 2020 11:53:43 -0400
Subject: made isDisplayPanel a prop of the docking layout instead of a field
 on a Doc

---
 src/client/documents/Documents.ts                  |  3 +-
 .../views/collections/CollectionDockingView.tsx    | 46 ++++++++++------------
 src/client/views/nodes/DocumentView.tsx            |  6 +++
 3 files changed, 27 insertions(+), 28 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9186cea87..2ca1b95d7 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -101,7 +101,6 @@ export interface DocumentOptions {
     page?: number;
     description?: string; // added for links
     _viewScale?: number;
-    isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
     forceActive?: boolean;
     layout?: string | Doc; // default layout string for a document
     childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view)
@@ -848,7 +847,7 @@ export namespace Docs {
                     {
                         type: type,
                         content: [
-                            ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth))
+                            ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, false, config.initialWidth))
                         ]
                     }
                 ]
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index cc3d017be..06cadcacf 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -38,7 +38,7 @@ const _global = (window /* browser */ || global /* node */) as any;
 @observer
 export class CollectionDockingView extends CollectionSubView(doc => doc) {
     @observable public static Instance: CollectionDockingView;
-    public static makeDocumentConfig(document: Doc, width?: number) {
+    public static makeDocumentConfig(document: Doc, isDisplayPanel?: boolean, width?: number) {
         return {
             type: 'react-component',
             component: 'DocumentFrameRenderer',
@@ -46,6 +46,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             width: width,
             props: {
                 documentId: document[Id],
+                isDisplayPanel   // flag for whether a tab should be considered a placeholder that has its contents replaced with new content
             }
         };
     }
@@ -91,8 +92,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     public static CloseRightSplit(document: Opt<Doc>): boolean {
         const tryClose = (childItem: any) => {
             if (childItem.config?.component === "DocumentFrameRenderer") {
-                const docView = DocumentManager.Instance.getDocumentViewById(childItem.config.props.documentId);
-                if (docView && ((!document && docView.Document.isDisplayPanel) || (document && Doc.AreProtosEqual(docView.props.Document, document)))) {
+                if ((!document && childItem.config.props.isDisplayPanel) || (document && childItem.config.props.documentId === document[Id])) {
                     childItem.remove();
                     return true;
                 }
@@ -115,7 +115,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         }
         const newItemStackConfig = {
             type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc), undefined)]
+            content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))]
         };
         const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
         instance._goldenLayout.root.contentItems[0].addChild(docconfig);
@@ -133,17 +133,15 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         const retVal = !instance._goldenLayout.root.contentItems[0].isRow ? false :
             Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
                 if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
-                    DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)?.Document.isDisplayPanel) {
-                    const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined);
-                    runInAction(() => document.isDisplayPanel = true);
+                    child.contentItems[0].config.isDisplayPanel) {
+                    const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, true);
                     child.addChild(newItemStackConfig, undefined);
                     !addToSplit && child.contentItems[0].remove();
                     return true;
                 }
                 return Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
-                    if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)?.Document.isDisplayPanel) {
-                        const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined);
-                        runInAction(() => document.isDisplayPanel = true);
+                    if (tab.config.props.isDisplayPanel) {
+                        const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, true);
                         child.addChild(newItemStackConfig, undefined);
                         !addToSplit && child.contentItems[j].remove();
                         return true;
@@ -160,7 +158,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     //
     @undoBatch
     @action
-    public static AddRightSplit(document: Doc, dontSelect: boolean = false, isDisplayPanel: Opt<boolean> = undefined) {
+    public static AddRightSplit(document: Doc, isDisplayPanel: Opt<boolean> = undefined) {
         if (!CollectionDockingView.Instance) return false;
 
         const ind = Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === document);
@@ -172,13 +170,11 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             }
             tab.setActive(true);
         } else {
-            document.isDisplayPanel = isDisplayPanel;
-
             if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document);
             const instance = CollectionDockingView.Instance;
             const newItemStackConfig = {
                 type: 'stack',
-                content: [CollectionDockingView.makeDocumentConfig(document, undefined)]
+                content: [CollectionDockingView.makeDocumentConfig(document, isDisplayPanel)]
             };
 
             const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
@@ -201,10 +197,6 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             newContentItem.callDownwards('_$init');
             instance.layoutChanged();
         }
-        if (!dontSelect) {
-            const view = DocumentManager.Instance.getFirstDocumentView(document);
-            view && SelectionManager.SelectDoc(view, false);
-        }
         return true;
     }
 
@@ -275,14 +267,13 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         return true;
     }
 
-
     //
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
     //
     @undoBatch
     public static UseRightSplit(document: Doc, libraryPath?: Doc[], shiftKey?: boolean) {
         if (shiftKey || !CollectionDockingView.ReplaceRightSplit(document, libraryPath, shiftKey)) {
-            return CollectionDockingView.AddRightSplit(document, false, true);
+            return CollectionDockingView.AddRightSplit(document, true);
         }
         return false;
     }
@@ -406,7 +397,6 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         window.addEventListener("mouseup", this.onPointerUp);
-
         if (!(e.target as HTMLElement).closest("*.lm_content") && ((e.target as HTMLElement).closest("*.lm_tab") || (e.target as HTMLElement).closest("*.lm_stack"))) {
             this._flush = UndoManager.StartBatch("golden layout edit");
         }
@@ -463,7 +453,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
     }
     tabCreated = (tab: any) => {
-        (tab.contentItem.element[0]?.firstChild?.firstChild as any)?.InitTab(tab);
+        (tab.contentItem.element[0]?.firstChild?.firstChild as any)?.InitTab(tab);  // have to explicitly initialize tabs that reuse contents from previous abs (ie, when dragging a tab around a new tab is created for the old content)
     }
 
     stackCreated = (stack: any) => {
@@ -515,10 +505,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _panelHeight = 0;
     @observable private _isActive: boolean = false;
     @observable private _document: Doc | undefined;
+    @observable private _view: DocumentView | undefined;
 
     get stack(): any { return (this.props as any).glContainer.parent.parent; }
     get tab() { return (this.props as any).glContainer.tab; }
-    get view() { return this._document && DocumentManager.Instance.getDocumentView(this._document); }
+    get view() { return this._view; }
 
     @action
     init = (tab: any, doc: Opt<Doc>) => {
@@ -704,7 +695,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         SelectionManager.DeselectAll();
         if (doc._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
         switch (location) {
-            case "onRight": return CollectionDockingView.AddRightSplit(doc);
+            case "onRight": return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === doc) !== -1 ?
+                CollectionDockingView.CloseRightSplit(doc) : CollectionDockingView.AddRightSplit(doc);
             case "close": return CollectionDockingView.CloseRightSplit(doc);
             case "replace": return CollectionDockingView.UseRightSplit(doc);
             case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
@@ -790,12 +782,14 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             </div>
         </div>;
     }
+    setView = action((view: DocumentView) => this._view = view);
     @computed get docView() {
         TraceMobx();
         return !this._document ? (null) :
             <><DocumentView key={this._document[Id]}
                 LibraryPath={emptyPath}
                 Document={this._document}
+                getView={this.setView}
                 DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
                 bringToFront={emptyFunction}
                 rootSelected={returnTrue}
@@ -839,6 +833,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             </div >);
     }
 }
-Scripting.addGlobal(function openOnRight(doc: any, dontSelect: boolean = false) { CollectionDockingView.AddRightSplit(doc, dontSelect); },
-    "opens up the inputted document on the right side of the screen", "(doc: any, dontSelect: boolean)");
+Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc); },
+    "opens up the inputted document on the right side of the screen", "(doc: any)");
 Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.UseRightSplit(doc, undefined, shiftKey); });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index eb6988362..6e1357b41 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -52,6 +52,7 @@ export interface DocumentViewProps {
     NativeHeight: () => number;
     Document: Doc;
     DataDoc?: Doc;
+    getView?: (view: DocumentView) => any;
     LayoutTemplateString?: string;
     LayoutTemplate?: () => Opt<Doc>;
     LibraryPath: Doc[];
@@ -128,6 +129,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     onClickFunc = () => this.onClickHandler;
     onDoubleClickFunc = () => this.onDoubleClickHandler;
 
+    constructor(props: any) {
+        super(props);
+        props.getView?.(this);
+    }
+
     handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
         this.removeMoveListeners();
         this.removeEndListeners();
-- 
cgit v1.2.3-70-g09d2


From f5a9d196a96f9a81436fa8de8659eaa51a131d81 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 29 Aug 2020 13:58:16 -0400
Subject: reorganized source files into proper directories.  split contents of
 ParendDocSelector into CollectionDockingViewMenu and
 PropetiesDocContextSelector

---
 src/client/views/DocumentButtonBar.tsx             |    3 +-
 src/client/views/DocumentDecorations.tsx           |    2 +-
 src/client/views/FormatShapePane.scss              |   68 ++
 src/client/views/FormatShapePane.tsx               |  545 ++++++++++
 src/client/views/GlobalKeyHandler.ts               |   10 +-
 src/client/views/InkingStroke.tsx                  |    2 +-
 src/client/views/MainView.tsx                      |   21 +-
 src/client/views/PropertiesButtons.tsx             |    1 -
 src/client/views/PropertiesDocContextSelector.scss |    0
 src/client/views/PropertiesDocContextSelector.tsx  |   58 ++
 src/client/views/PropertiesView.scss               |  779 ++++++++++++++
 src/client/views/PropertiesView.tsx                | 1089 +++++++++++++++++++
 .../views/collections/CollectionDockingView.tsx    |   13 +-
 .../collections/CollectionDockingViewMenu.scss     |   34 +
 .../collections/CollectionDockingViewMenu.tsx      |   51 +
 .../views/collections/ParentDocumentSelector.scss  |   63 --
 .../views/collections/ParentDocumentSelector.tsx   |  113 --
 .../collectionFreeForm/FormatShapePane.scss        |   68 --
 .../collectionFreeForm/FormatShapePane.tsx         |  545 ----------
 .../collectionFreeForm/PropertiesView.scss         |  779 --------------
 .../collectionFreeForm/PropertiesView.tsx          | 1090 --------------------
 src/client/views/nodes/DocumentView.tsx            |    2 +-
 src/client/views/nodes/LinkAnchorBox.scss          |    5 +
 src/client/views/nodes/LinkAnchorBox.tsx           |    2 +-
 24 files changed, 2653 insertions(+), 2690 deletions(-)
 create mode 100644 src/client/views/FormatShapePane.scss
 create mode 100644 src/client/views/FormatShapePane.tsx
 create mode 100644 src/client/views/PropertiesDocContextSelector.scss
 create mode 100644 src/client/views/PropertiesDocContextSelector.tsx
 create mode 100644 src/client/views/PropertiesView.scss
 create mode 100644 src/client/views/PropertiesView.tsx
 create mode 100644 src/client/views/collections/CollectionDockingViewMenu.scss
 create mode 100644 src/client/views/collections/CollectionDockingViewMenu.tsx
 delete mode 100644 src/client/views/collections/ParentDocumentSelector.scss
 delete mode 100644 src/client/views/collections/ParentDocumentSelector.tsx
 delete mode 100644 src/client/views/collections/collectionFreeForm/FormatShapePane.scss
 delete mode 100644 src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
 delete mode 100644 src/client/views/collections/collectionFreeForm/PropertiesView.scss
 delete mode 100644 src/client/views/collections/collectionFreeForm/PropertiesView.tsx

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

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index d342eb95f..dd223390a 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -10,12 +10,12 @@ import { emptyFunction, setupMoveUpEvents } from "../../Utils";
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
 import { Docs } from '../documents/Documents';
+import { DocumentType } from '../documents/DocumentTypes';
 import { CurrentUserUtils } from '../util/CurrentUserUtils';
 import { DragManager } from '../util/DragManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { SharingManager } from '../util/SharingManager';
 import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
-import './collections/ParentDocumentSelector.scss';
 import './DocumentButtonBar.scss';
 import { MetadataEntryMenu } from './MetadataEntryMenu';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
@@ -24,7 +24,6 @@ import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
 import { TemplateMenu } from "./TemplateMenu";
 import { Template, Templates } from "./Templates";
 import React = require("react");
-import { DocumentType } from '../documents/DocumentTypes';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index fad6b1a0b..6c64a3714 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -18,9 +18,9 @@ import { SelectionManager } from "../util/SelectionManager";
 import { SnappingManager } from '../util/SnappingManager';
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { CollectionDockingView } from './collections/CollectionDockingView';
-import { FormatShapePane } from './collections/collectionFreeForm/FormatShapePane';
 import { DocumentButtonBar } from './DocumentButtonBar';
 import './DocumentDecorations.scss';
+import { FormatShapePane } from './FormatShapePane';
 import { DocumentView } from "./nodes/DocumentView";
 import React = require("react");
 import e = require('express');
diff --git a/src/client/views/FormatShapePane.scss b/src/client/views/FormatShapePane.scss
new file mode 100644
index 000000000..d49ab27fb
--- /dev/null
+++ b/src/client/views/FormatShapePane.scss
@@ -0,0 +1,68 @@
+.antimodeMenu-button {
+    width: 200px;
+    position: relative;
+    text-align: left;
+
+    .color-previewI {
+        width: 100%;
+        height: 40%;
+    }
+
+    .color-previewII {
+        width: 100%;
+        height: 100%;
+    }
+}
+
+.antimenu-Buttonup {
+    position: absolute;
+    width: 20;
+    height: 10;
+    right: 0;
+    padding: 0;
+}
+
+.formatShapePane-inputBtn {
+    width: inherit;
+    position: absolute;
+}
+
+.btn-group-palette {
+    .sketch-picker {
+        background: #323232;
+        width: 160px !important;
+        height: 80% !important;
+
+        .flexbox-fit {
+            background: #323232;
+        }
+    }
+}
+
+.btn-group {
+    display: grid;
+    grid-template-columns: auto auto auto auto;
+    /* Make the buttons appear below each other */
+}
+
+.btn-group-palette {
+    display: block;
+    /* Make the buttons appear below each other */
+}
+
+.btn-draw {
+    display: inline;
+    /* Make the buttons appear below each other */
+}
+
+.btn2-group {
+    display: block;
+    background: #323232;
+    grid-template-columns: auto;
+
+    /* Make the buttons appear below each other */
+    .antimodeMenu-button {
+        background: #323232;
+        display: block;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/FormatShapePane.tsx b/src/client/views/FormatShapePane.tsx
new file mode 100644
index 000000000..293a06dc0
--- /dev/null
+++ b/src/client/views/FormatShapePane.tsx
@@ -0,0 +1,545 @@
+import React = require("react");
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { ColorState, SketchPicker } from 'react-color';
+import { Doc, Field, Opt } from "../../fields/Doc";
+import { Document } from "../../fields/documentSchemas";
+import { InkField } from "../../fields/InkField";
+import { BoolCast, Cast, NumCast } from "../../fields/Types";
+import { DocumentType } from "../documents/DocumentTypes";
+import { SelectionManager } from "../util/SelectionManager";
+import { undoBatch } from "../util/UndoManager";
+import { AntimodeMenu, AntimodeMenuProps } from "./AntimodeMenu";
+import "./FormatShapePane.scss";
+
+@observer
+export class FormatShapePane extends AntimodeMenu<AntimodeMenuProps> {
+    static Instance: FormatShapePane;
+
+    private _lastFill = "#D0021B";
+    private _lastLine = "#D0021B";
+    private _lastDash = "2";
+    private _mode = ["fill-drip", "ruler-combined"];
+
+    @observable private _subOpen = [false, false];
+    @observable private _currMode = "fill-drip";
+    @observable _lock = false;
+    @observable private _fillBtn = false;
+    @observable private _lineBtn = false;
+    @observable _controlBtn = false;
+    @observable private _controlPoints: { X: number, Y: number }[] = [];
+    @observable _currPoint = -1;
+
+    getField(key: string) {
+        return this.selectedInk?.reduce((p, i) =>
+            (p === undefined || (p && p === i.rootDoc[key])) && i.rootDoc[key] !== "0" ? Field.toString(i.rootDoc[key] as Field) : "", undefined as Opt<string>);
+    }
+
+    @computed get selectedInk() {
+        const inks = SelectionManager.SelectedDocuments().filter(i => Document(i.rootDoc).type === DocumentType.INK);
+        return inks.length ? inks : undefined;
+    }
+    @computed get unFilled() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; }
+    @computed get unStrokd() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; }
+    @computed get solidFil() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; }
+    @computed get solidStk() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; }
+    @computed get dashdStk() { return !this.unStrokd && this.getField("strokeDash") || ""; }
+    @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
+    @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
+    @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
+    @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
+    @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
+    @computed get shapeHgt() { return this.getField("_height"); }
+    @computed get shapeWid() { return this.getField("_width"); }
+    @computed get shapeXps() { return this.getField("x"); }
+    @computed get shapeYps() { return this.getField("y"); }
+    @computed get shapeRot() { return this.getField("rotation"); }
+    set unFilled(value) { this.colorFil = value ? "" : this._lastFill; }
+    set solidFil(value) { this.unFilled = !value; }
+    set colorFil(value) { value && (this._lastFill = value); this.selectedInk?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); }
+    set colorStk(value) { value && (this._lastLine = value); this.selectedInk?.forEach(i => i.rootDoc.color = value ? value : undefined); }
+    set markHead(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeStartMarker = value); }
+    set markTail(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeEndMarker = value); }
+    set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
+    set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
+    set dashdStk(value) {
+        value && (this._lastDash = value) && (this.unStrokd = false);
+        this.selectedInk?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined);
+    }
+    set shapeXps(value) { this.selectedInk?.forEach(i => i.rootDoc.x = Number(value)); }
+    set shapeYps(value) { this.selectedInk?.forEach(i => i.rootDoc.y = Number(value)); }
+    set shapeRot(value) { this.selectedInk?.forEach(i => i.rootDoc.rotation = Number(value)); }
+    set widthStk(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = Number(value)); }
+    set shapeWid(value) {
+        this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+            const oldWidth = NumCast(i.rootDoc._width);
+            i.rootDoc._width = Number(value);
+            this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth);
+        });
+    }
+    set shapeHgt(value) {
+        this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+            const oldHeight = NumCast(i.rootDoc._height);
+            i.rootDoc._height = Number(value);
+            this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight);
+        });
+    }
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+        FormatShapePane.Instance = this;
+        this._canFade = false;
+        runInAction(() => this.Pinned = BoolCast(Doc.UserDoc()["menuFormatShape-pinned"]));
+    }
+
+    @action
+    closePane = () => {
+        this.fadeOut(false);
+        this.Pinned = false;
+    }
+
+    @action
+    upDownButtons = (dirs: string, field: string) => {
+        switch (field) {
+            case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break;
+            // case "rot": this.selectedInk?.forEach(i => i.rootDoc.rotation = NumCast(i.rootDoc.rotation) + (dirs === "up" ? 0.1 : -0.1)); break;
+            case "Xps": this.selectedInk?.forEach(i => i.rootDoc.x = NumCast(i.rootDoc.x) + (dirs === "up" ? 10 : -10)); break;
+            case "Yps": this.selectedInk?.forEach(i => i.rootDoc.y = NumCast(i.rootDoc.y) + (dirs === "up" ? 10 : -10)); break;
+            case "stk": this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = NumCast(i.rootDoc.strokeWidth) + (dirs === "up" ? .1 : -.1)); break;
+            case "wid": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+                //redraw points
+                const oldWidth = NumCast(i.rootDoc._width);
+                const oldHeight = NumCast(i.rootDoc._height);
+                const oldX = NumCast(i.rootDoc.x);
+                const oldY = NumCast(i.rootDoc.y);
+                i.rootDoc._width = oldWidth + (dirs === "up" ? 10 : - 10);
+                this._lock && (i.rootDoc._height = (i.rootDoc._width / oldWidth * NumCast(i.rootDoc._height)));
+                const doc = Document(i.rootDoc);
+                if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
+                    const ink = Cast(doc.data, InkField)?.inkData;
+                    if (ink) {
+                        const newPoints: { X: number, Y: number }[] = [];
+                        ink.forEach(i => {
+                            // (new x — oldx) + (oldxpoint * newWidt)/oldWidth 
+                            const newX = ((doc.x || 0) - oldX) + (i.X * (doc._width || 0)) / oldWidth;
+                            const newY = ((doc.y || 0) - oldY) + (i.Y * (doc._height || 0)) / oldHeight;
+                            newPoints.push({ X: newX, Y: newY });
+                        });
+                        Doc.GetProto(doc).data = new InkField(newPoints);
+                    }
+                }
+            });
+                break;
+            case "hgt": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+                const oldWidth = NumCast(i.rootDoc._width);
+                const oldHeight = NumCast(i.rootDoc._height);
+                const oldX = NumCast(i.rootDoc.x);
+                const oldY = NumCast(i.rootDoc.y); i.rootDoc._height = oldHeight + (dirs === "up" ? 10 : - 10);
+                this._lock && (i.rootDoc._width = (i.rootDoc._height / oldHeight * NumCast(i.rootDoc._width)));
+                const doc = Document(i.rootDoc);
+                if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
+                    const ink = Cast(doc.data, InkField)?.inkData;
+                    if (ink) {
+                        const newPoints: { X: number, Y: number }[] = [];
+                        ink.forEach(i => {
+                            // (new x — oldx) + (oldxpoint * newWidt)/oldWidth 
+                            const newX = ((doc.x || 0) - oldX) + (i.X * (doc._width || 0)) / oldWidth;
+                            const newY = ((doc.y || 0) - oldY) + (i.Y * (doc._height || 0)) / oldHeight;
+                            newPoints.push({ X: newX, Y: newY });
+                        });
+                        Doc.GetProto(doc).data = new InkField(newPoints);
+                    }
+                }
+            });
+                break;
+        }
+    }
+
+    @undoBatch
+    @action
+    addPoints = (x: number, y: number, pts: { X: number, Y: number }[], index: number, control: { X: number, Y: number }[]) => {
+        this.selectedInk?.forEach(action(inkView => {
+            if (this.selectedInk?.length === 1) {
+                const doc = Document(inkView.rootDoc);
+                if (doc.type === DocumentType.INK) {
+                    const ink = Cast(doc.data, InkField)?.inkData;
+                    if (ink) {
+                        const newPoints: { X: number, Y: number }[] = [];
+                        var counter = 0;
+                        for (var k = 0; k < index; k++) {
+                            control.forEach(pt => (pts[k].X === pt.X && pts[k].Y === pt.Y) && counter++);
+                        }
+                        //decide where to put the new coordinate
+                        const spNum = Math.floor(counter / 2) * 4 + 2;
+
+                        for (var i = 0; i < spNum; i++) {
+                            ink[i] && newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+                        }
+                        for (var j = 0; j < 4; j++) {
+                            newPoints.push({ X: x, Y: y });
+
+                        }
+                        for (var i = spNum; i < ink.length; i++) {
+                            newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+                        }
+                        this._currPoint = -1;
+                        Doc.GetProto(doc).data = new InkField(newPoints);
+                    }
+                }
+            }
+        }));
+    }
+
+    @undoBatch
+    @action
+    deletePoints = () => {
+        this.selectedInk?.forEach(action(inkView => {
+            if (this.selectedInk?.length === 1 && this._currPoint !== -1) {
+                const doc = Document(inkView.rootDoc);
+                if (doc.type === DocumentType.INK) {
+                    const ink = Cast(doc.data, InkField)?.inkData;
+                    if (ink && ink.length > 4) {
+                        const newPoints: { X: number, Y: number }[] = [];
+                        const toRemove = Math.floor(((this._currPoint + 2) / 4));
+                        for (var i = 0; i < ink.length; i++) {
+                            if (Math.floor((i + 2) / 4) !== toRemove) {
+                                newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+                            }
+                        }
+                        this._currPoint = -1;
+                        Doc.GetProto(doc).data = new InkField(newPoints);
+                        if (newPoints.length === 4) {
+                            const newerPoints: { X: number, Y: number }[] = [];
+                            newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y });
+                            newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y });
+                            newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y });
+                            newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y });
+                            Doc.GetProto(doc).data = new InkField(newerPoints);
+
+                        }
+                    }
+                }
+            }
+        }));
+    }
+
+    @undoBatch
+    @action
+    rotate = (angle: number) => {
+        const _centerPoints: { X: number, Y: number }[] = [];
+        SelectionManager.SelectedDocuments().forEach(action(inkView => {
+            const doc = Document(inkView.rootDoc);
+            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+                const ink = Cast(doc.data, InkField)?.inkData;
+                if (ink) {
+                    const xs = ink.map(p => p.X);
+                    const ys = ink.map(p => p.Y);
+                    const left = Math.min(...xs);
+                    const top = Math.min(...ys);
+                    const right = Math.max(...xs);
+                    const bottom = Math.max(...ys);
+                    _centerPoints.push({ X: left, Y: top });
+                }
+            }
+        }));
+
+        var index = 0;
+        SelectionManager.SelectedDocuments().forEach(action(inkView => {
+            const doc = Document(inkView.rootDoc);
+            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+                doc.rotation = Number(doc.rotation) + Number(angle);
+                const ink = Cast(doc.data, InkField)?.inkData;
+                if (ink) {
+
+                    const newPoints: { X: number, Y: number }[] = [];
+                    ink.forEach(i => {
+                        const newX = Math.cos(angle) * (i.X - _centerPoints[index].X) - Math.sin(angle) * (i.Y - _centerPoints[index].Y) + _centerPoints[index].X;
+                        const newY = Math.sin(angle) * (i.X - _centerPoints[index].X) + Math.cos(angle) * (i.Y - _centerPoints[index].Y) + _centerPoints[index].Y;
+                        newPoints.push({ X: newX, Y: newY });
+                    });
+                    Doc.GetProto(doc).data = new InkField(newPoints);
+                    const xs = newPoints.map(p => p.X);
+                    const ys = newPoints.map(p => p.Y);
+                    const left = Math.min(...xs);
+                    const top = Math.min(...ys);
+                    const right = Math.max(...xs);
+                    const bottom = Math.max(...ys);
+
+                    doc._height = (bottom - top);
+                    doc._width = (right - left);
+                }
+                index++;
+            }
+        }));
+    }
+
+    @undoBatch
+    @action
+    control = (xDiff: number, yDiff: number, controlNum: number) => {
+        this.selectedInk?.forEach(action(inkView => {
+            if (this.selectedInk?.length === 1) {
+                const doc = Document(inkView.rootDoc);
+                if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+                    const ink = Cast(doc.data, InkField)?.inkData;
+                    if (ink) {
+
+                        const newPoints: { X: number, Y: number }[] = [];
+                        const order = controlNum % 4;
+                        for (var i = 0; i < ink.length; i++) {
+                            if (controlNum === i ||
+                                (order === 0 && i === controlNum + 1) ||
+                                (order === 0 && controlNum !== 0 && i === controlNum - 2) ||
+                                (order === 0 && controlNum !== 0 && i === controlNum - 1) ||
+                                (order === 3 && i === controlNum - 1) ||
+                                (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 1) ||
+                                (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 2)
+                                || ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlNum === 0 || controlNum === ink.length - 1))
+                            ) {
+                                newPoints.push({ X: ink[i].X - (xDiff * inkView.props.ScreenToLocalTransform().Scale), Y: ink[i].Y - (yDiff * inkView.props.ScreenToLocalTransform().Scale) });
+                            }
+                            else {
+                                newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+                            }
+                        }
+                        const oldx = doc.x;
+                        const oldy = doc.y;
+                        const xs = ink.map(p => p.X);
+                        const ys = ink.map(p => p.Y);
+                        const left = Math.min(...xs);
+                        const top = Math.min(...ys);
+                        Doc.GetProto(doc).data = new InkField(newPoints);
+                        const xs2 = newPoints.map(p => p.X);
+                        const ys2 = newPoints.map(p => p.Y);
+                        const left2 = Math.min(...xs2);
+                        const top2 = Math.min(...ys2);
+                        const right2 = Math.max(...xs2);
+                        const bottom2 = Math.max(...ys2);
+                        doc._height = (bottom2 - top2);
+                        doc._width = (right2 - left2);
+                        //if points move out of bounds
+
+                        doc.x = oldx - (left - left2);
+                        doc.y = oldy - (top - top2);
+
+                    }
+                }
+            }
+        }));
+    }
+
+    @undoBatch
+    @action
+    switchStk = (color: ColorState) => {
+        const val = String(color.hex);
+        this.colorStk = val;
+        return true;
+    }
+
+    @undoBatch
+    @action
+    switchFil = (color: ColorState) => {
+        const val = String(color.hex);
+        this.colorFil = val;
+        return true;
+    }
+
+
+    colorPicker(setter: (color: string) => {}, type: string) {
+        return <div className="btn-group-palette" key="colorpicker" style={{ width: 160, margin: 10 }}>
+            <SketchPicker onChange={type === "stk" ? this.switchStk : this.switchFil} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
+                color={type === "stk" ? this.colorStk : this.colorFil} />
+        </div>;
+    }
+    inputBox = (key: string, value: any, setter: (val: string) => {}) => {
+        return <>
+            <input style={{ color: "black", width: 40, position: "absolute", right: 20 }}
+                type="text" defaultValue={value}
+                onChange={undoBatch(action((e) => setter(e.target.value)))}
+                autoFocus />
+            <button className="antiMenu-Buttonup" key="up1" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))}>
+                ˄
+            </button>
+            <br />
+            <button className="antiMenu-Buttonup" key="down1" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: -8 }}>
+                ˅
+            </button>
+        </>;
+    }
+
+    inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => {
+        return <>
+            {title1}
+            <p style={{ marginTop: -20, right: 70, position: "absolute" }}>{title2}</p>
+
+            <input style={{ color: "black", width: 40, position: "absolute", right: 130 }}
+                type="text" defaultValue={value}
+                onChange={e => setter(e.target.value)}
+                autoFocus />
+            <button className="antiMenu-Buttonup" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} style={{ right: 110 }}>
+                ˄
+        </button>
+            <button className="antiMenu-Buttonup" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: 12, right: 110 }}>
+                ˅
+        </button>
+            {title2 === "" ? "" : <>
+                <input style={{ color: "black", width: 40, position: "absolute", right: 20 }}
+                    type="text" defaultValue={value2}
+                    onChange={e => setter2(e.target.value)}
+                    autoFocus />
+                <button className="antiMenu-Buttonup" key="up3" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key2)))}>
+                    ˄
+      </button>
+                <br />
+                <button className="antiMenu-Buttonup" key="down3" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key2)))} style={{ marginTop: -8 }}>
+                    ˅
+      </button></>}
+        </>;
+    }
+
+
+    colorButton(value: string, setter: () => {}) {
+        return <>
+            <button className="antimodeMenu-button" key="color" onPointerDown={undoBatch(action(e => setter()))} style={{ position: "relative", marginTop: -5 }}>
+                <div className="color-previewII" style={{ backgroundColor: value ?? "121212" }} />
+                {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -23, position: "fixed" }}>☒</p> : ""}
+            </button>
+        </>;
+    }
+
+    controlPointsButton() {
+        return <>
+            <button className="antimodeMenu-button" title="Edit points" key="bezier" onPointerDown={action(() => this._controlBtn = this._controlBtn ? false : true)} style={{ position: "relative", marginTop: 10, backgroundColor: this._controlBtn ? "black" : "" }}>
+                <FontAwesomeIcon icon="bezier-curve" size="lg" />
+            </button>
+            <button className="antimodeMenu-button" title="Lock ratio" key="ratio" onPointerDown={action(() => this._lock = this._lock ? false : true)} style={{ position: "relative", marginTop: 10, backgroundColor: this._lock ? "black" : "" }}>
+                <FontAwesomeIcon icon="lock" size="lg" />
+
+            </button>
+            <button className="antimodeMenu-button" key="rotate" title="Rotate 90˚" onPointerDown={action(() => this.rotate(Math.PI / 2))} style={{ position: "relative", marginTop: 10, fontSize: 15 }}>
+                ⟲
+            </button>
+            <br /> <br />
+        </>;
+    }
+
+    lockRatioButton() {
+        return <>
+            <button className="antimodeMenu-button" key="lock" onPointerDown={action(() => this._lock = this._lock ? false : true)} style={{ position: "absolute", right: 80, backgroundColor: this._lock ? "black" : "" }}>
+                {/* <FontAwesomeIcon icon="bezier-curve" size="lg" /> */}
+                <FontAwesomeIcon icon="lock" size="lg" />
+
+            </button>
+            <br /> <br />
+        </>;
+    }
+
+    rotate90Button() {
+        return <>
+            <button className="antimodeMenu-button" key="rot" onPointerDown={action(() => this.rotate(Math.PI / 2))} style={{ position: "absolute", right: 80, }}>
+                {/* <FontAwesomeIcon icon="bezier-curve" size="lg" /> */}
+                ⟲
+            </button>
+            <br /> <br />
+        </>;
+    }
+    @computed get fillButton() { return this.colorButton(this.colorFil, () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); }
+    @computed get lineButton() { return this.colorButton(this.colorStk, () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); }
+
+    @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); }
+    @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); }
+
+    @computed get stkInput() { return this.inputBox("stk", this.widthStk, (val: string) => this.widthStk = val); }
+    @computed get dashInput() { return this.inputBox("dsh", this.widthStk, (val: string) => this.widthStk = val); }
+
+    @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => this.shapeHgt = val, "H:", "wid", this.shapeWid, (val: string) => this.shapeWid = val, "W:"); }
+    @computed get widInput() { return this.inputBox("wid", this.shapeWid, (val: string) => this.shapeWid = val); }
+    @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; return true; }, "∠:", "rot", this.shapeRot, (val: string) => this.shapeRot = val, ""); }
+
+    @computed get YpsInput() { return this.inputBox("Yps", this.shapeYps, (val: string) => this.shapeYps = val); }
+
+    @computed get controlPoints() { return this.controlPointsButton(); }
+    @computed get lockRatio() { return this.lockRatioButton(); }
+    @computed get rotate90() { return this.rotate90Button(); }
+    @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => this.shapeXps = val, "X:", "Yps", this.shapeYps, (val: string) => this.shapeYps = val, "Y:"); }
+
+
+    @computed get propertyGroupItems() {
+        const fillCheck = <div key="fill" style={{ display: (this._subOpen[0] && this.selectedInk && this.selectedInk.length >= 1) ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
+            Fill:
+            {this.fillButton}
+            <div style={{ float: "left", width: 100 }} >
+                Stroke:
+            {this.lineButton}
+            </div>
+
+            {this._fillBtn ? this.fillPicker : ""}
+            {this._lineBtn ? this.linePicker : ""}
+            {this._fillBtn || this._lineBtn ? "" : <br />}
+            {(this.solidStk || this.dashdStk) ? "Width" : ""}
+            {(this.solidStk || this.dashdStk) ? this.stkInput : ""}
+            {(this.solidStk || this.dashdStk) ? <input type="range" defaultValue={Number(this.widthStk)} min={1} max={100} onChange={undoBatch(action((e) => this.widthStk = e.target.value))} /> : (null)}
+            <br />
+            {(this.solidStk || this.dashdStk) ? <>
+                <p style={{ position: "absolute", fontSize: 12 }}>Arrow Head</p>
+                <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} style={{ position: "absolute", right: 110, width: 20 }} />
+                <p style={{ position: "absolute", fontSize: 12, right: 30 }}>Arrow End</p>
+                <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} style={{ position: "absolute", right: 0, width: 20 }} />
+                <br />
+            </> : ""}
+            Dash:
+            <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.dashdStk === "2"} onChange={undoBatch(action(() => this.dashdStk = this.dashdStk === "2" ? "0" : "2"))} style={{ position: "absolute", right: 110, width: 20 }} />
+        </div>;
+
+
+
+        const sizeCheck =
+
+            <div key="sizeCheck" style={{ display: (this._subOpen[1] && this.selectedInk && this.selectedInk.length >= 1) ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
+                {this.controlPoints}
+                {this.hgtInput}
+                {this.XpsInput}
+                {this.rotInput}
+
+            </div>;
+
+
+        const subMenus = this._currMode === "fill-drip" ? [`Appearance`, 'Transform'] : [];
+        const menuItems = this._currMode === "fill-drip" ? [fillCheck, sizeCheck] : [];
+        const indexOffset = 0;
+
+        return <div className="antimodeMenu-sub" key="submenu" style={{ position: "absolute", width: "inherit", top: 60 }}>
+            {subMenus.map((subMenu, i) =>
+                <div key={subMenu} style={{ width: "inherit" }}>
+                    <button className="antimodeMenu-button" onPointerDown={action(() => this._subOpen[i + indexOffset] = !this._subOpen[i + indexOffset])}
+                        style={{ backgroundColor: "121212", position: "relative", width: "inherit" }}>
+                        {this._subOpen[i + indexOffset] ? "▼" : "▶︎"}
+                        {subMenu}
+                    </button>
+                    {menuItems[i]}
+                </div>)}
+        </div>;
+    }
+
+    @computed get closeBtn() {
+        return <button className="antimodeMenu-button" key="close" onPointerDown={action(() => this.closePane())} style={{ position: "absolute", right: 0 }}>
+            X
+        </button>;
+    }
+
+    @computed get propertyGroupBtn() {
+        return <div className="antimodeMenu-button-tab" key="modes">
+            {this._mode.map(mode =>
+                <button className="antimodeMenu-button" key={mode} onPointerDown={action(() => this._currMode = mode)}
+                    style={{ backgroundColor: this._currMode === mode ? "121212" : "", position: "relative", top: 30 }}>
+                    <FontAwesomeIcon icon={mode as IconProp} size="lg" />
+                </button>)}
+        </div>;
+    }
+
+    render() {
+        return this.getElementVert([this.closeBtn,
+        this.propertyGroupItems]);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 1cbbfd67b..2794a9f4e 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -11,19 +11,19 @@ import { DocServer } from "../DocServer";
 import { DocumentType } from "../documents/DocumentTypes";
 import { DictationManager } from "../util/DictationManager";
 import { DragManager } from "../util/DragManager";
+import { GroupManager } from "../util/GroupManager";
 import { SelectionManager } from "../util/SelectionManager";
 import { SharingManager } from "../util/SharingManager";
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { CollectionDockingView } from "./collections/CollectionDockingView";
+import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
+import { ContextMenu } from "./ContextMenu";
 import { DocumentDecorations } from "./DocumentDecorations";
+import { FormatShapePane } from "./FormatShapePane";
 import { MainView } from "./MainView";
-import { DocumentView } from "./nodes/DocumentView";
 import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
+import { DocumentView } from "./nodes/DocumentView";
 import { PDFMenu } from "./pdf/PDFMenu";
-import { ContextMenu } from "./ContextMenu";
-import { GroupManager } from "../util/GroupManager";
-import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
-import { FormatShapePane } from "./collections/collectionFreeForm/FormatShapePane";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 18ecc225c..02628527f 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -11,9 +11,9 @@ import { CognitiveServices } from "../cognitive_services/CognitiveServices";
 import { InteractionUtils } from "../util/InteractionUtils";
 import { Scripting } from "../util/Scripting";
 import { UndoManager } from "../util/UndoManager";
-import { FormatShapePane } from "./collections/collectionFreeForm/FormatShapePane";
 import { ContextMenu } from "./ContextMenu";
 import { ViewBoxBaseComponent } from "./DocComponent";
+import { FormatShapePane } from "./FormatShapePane";
 import "./InkingStroke.scss";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
 import React = require("react");
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 3afda13c2..6a960f4d5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -10,17 +10,15 @@ import * as ReactDOM from 'react-dom';
 import Measure from 'react-measure';
 import * as rp from 'request-promise';
 import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
-import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
 import { listSpec } from '../../fields/Schema';
-import { ScriptField } from '../../fields/ScriptField';
-import { BoolCast, Cast, FieldValue, NumCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, FieldValue, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
 import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
-import { Docs, DocumentOptions } from '../documents/Documents';
+import { Docs } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
 import { Networking } from '../Network';
 import { CurrentUserUtils } from '../util/CurrentUserUtils';
@@ -38,19 +36,19 @@ import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
 import { TimelineMenu } from './animationtimeline/TimelineMenu';
 import { CollectionDockingView } from './collections/CollectionDockingView';
-import { FormatShapePane } from "./collections/collectionFreeForm/FormatShapePane";
 import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
-import { PropertiesView } from './collections/collectionFreeForm/PropertiesView';
 import { CollectionLinearView } from './collections/CollectionLinearView';
 import { CollectionMenu } from './collections/CollectionMenu';
-import { CollectionView, CollectionViewType } from './collections/CollectionView';
+import { CollectionViewType } from './collections/CollectionView';
 import { ContextMenu } from './ContextMenu';
 import { DictationOverlay } from './DictationOverlay';
 import { DocumentDecorations } from './DocumentDecorations';
+import { FormatShapePane } from "./FormatShapePane";
 import { GestureOverlay } from './GestureOverlay';
-import { ANTIMODEMENU_HEIGHT, SEARCH_PANEL_HEIGHT } from './globalCssVariables.scss';
+import { SEARCH_PANEL_HEIGHT } from './globalCssVariables.scss';
 import { KeyManager } from './GlobalKeyHandler';
 import { LinkMenu } from './linking/LinkMenu';
+import "./MainView.scss";
 import { AudioBox } from './nodes/AudioBox';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { DocumentView } from './nodes/DocumentView';
@@ -63,8 +61,8 @@ import { WebBox } from './nodes/WebBox';
 import { OverlayView } from './OverlayView';
 import { PDFMenu } from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
+import { PropertiesView } from './PropertiesView';
 import { SearchBox } from './search/SearchBox';
-import "./MainView.scss";
 
 @observer
 export class MainView extends React.Component {
@@ -521,7 +519,7 @@ export class MainView extends React.Component {
         }}>
             <PropertiesView
                 width={this.propertiesWidth()}
-                height={this._panelHeight}
+                height={this.getContentsHeight()}
                 renderDepth={1}
                 ScreenToLocalTransform={Transform.Identity}
                 onDown={this.closeProperties}
@@ -567,8 +565,7 @@ export class MainView extends React.Component {
                             <FontAwesomeIcon icon={this.propertiesIcon} color="black" size="sm" /> </div>
                     </div>
                 }
-                {this.propertiesWidth() < 10 ? (null) :
-                    <div style={{ width: this.propertiesWidth(), height: "calc(100% - 35px)" }}> {this.propertiesView} </div>}
+                {this.propertiesWidth() < 10 ? (null) : this.propertiesView}
             </div>
         </>;
     }
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index f79a68b5a..e95b28d6c 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -16,7 +16,6 @@ import { DocumentType } from '../documents/DocumentTypes';
 import { SelectionManager } from '../util/SelectionManager';
 import { undoBatch } from '../util/UndoManager';
 import { CollectionDockingView } from './collections/CollectionDockingView';
-import './collections/ParentDocumentSelector.scss';
 import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
 import './PropertiesButtons.scss';
 import React = require("react");
diff --git a/src/client/views/PropertiesDocContextSelector.scss b/src/client/views/PropertiesDocContextSelector.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
new file mode 100644
index 000000000..b382298f3
--- /dev/null
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -0,0 +1,58 @@
+import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import { Doc } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
+import { NumCast, StrCast } from "../../fields/Types";
+import { CollectionViewType } from "./collections/CollectionView";
+import { CollectionDockingView } from "./collections/CollectionDockingView";
+import './PropertiesDocContextSelector.scss';
+import { SearchUtil } from "../util/SearchUtil";
+
+type PropertiesDocContextSelectorProps = {
+    Document: Doc,
+    Stack?: any,
+    hideTitle?: boolean,
+    addDocTab(doc: Doc, location: string): void
+};
+
+@observer
+export class PropertiesDocContextSelector extends React.Component<PropertiesDocContextSelectorProps> {
+    @observable private _docs: { col: Doc, target: Doc }[] = [];
+    @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
+    _reaction: IReactionDisposer | undefined;
+
+    componentDidMount() { this._reaction = reaction(() => this.props.Document, () => this.fetchDocuments(), { fireImmediately: true }); }
+    componentWillUnmount() { this._reaction?.(); }
+    async fetchDocuments() {
+        const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
+        const containerProtoSets = await Promise.all(aliases.map(async alias => ((await SearchUtil.Search("", true, { fq: `data_l:"${alias[Id]}"` })).docs)));
+        const containerProtos = containerProtoSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
+        const containerSets = await Promise.all(Array.from(containerProtos.keys()).map(async container => SearchUtil.GetAliasesOfDocument(container)));
+        const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
+        const doclayoutSets = await Promise.all(Array.from(containers.keys()).map(async (dp) => SearchUtil.GetAliasesOfDocument(dp)));
+        const doclayouts = Array.from(doclayoutSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>()).keys());
+        runInAction(() => {
+            this._docs = doclayouts.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).filter(doc => !Doc.IsSystem(doc)).map(doc => ({ col: doc, target: this.props.Document }));
+            this._otherDocs = [];
+        });
+    }
+
+    getOnClick = (col: Doc, target: Doc) => {
+        col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
+        if (col._viewType === CollectionViewType.Freeform) {
+            col._panX = NumCast(target.x) + NumCast(target._width) / 2;
+            col._panY = NumCast(target.y) + NumCast(target._height) / 2;
+        }
+        this.props.addDocTab(col, "inTab");
+    }
+
+    render() {
+        return <div>
+            {this.props.hideTitle ? (null) : <p key="contexts">Contexts:</p>}
+            {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={() => this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)}</a></p>)}
+            {this._otherDocs.length ? <hr key="hr" /> : null}
+            {this._otherDocs.map(doc => <p key={"p" + doc.col[Id] + doc.target[Id]}><a onClick={() => this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)}</a></p>)}
+        </div>;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
new file mode 100644
index 000000000..254afeb0a
--- /dev/null
+++ b/src/client/views/PropertiesView.scss
@@ -0,0 +1,779 @@
+.propertiesView {
+
+    background-color: rgb(205, 205, 205);
+    height: 100%;
+    font-family: "Noto Sans";
+    cursor: auto;
+
+    overflow-x: hidden;
+    overflow-y: scroll;
+
+    .propertiesView-title {
+        background-color: rgb(159, 159, 159);
+        text-align: center;
+        padding-top: 12px;
+        padding-bottom: 12px;
+        display: flex;
+        font-size: 18px;
+        font-weight: bold;
+        justify-content: center;
+
+        .propertiesView-title-icon {
+            width: 20px;
+            height: 20px;
+            padding-left: 38px;
+            margin-top: -5px;
+            align-items: flex-end;
+            margin-left: auto;
+            margin-right: 10px;
+
+            &:hover {
+                color: grey;
+                cursor: pointer;
+            }
+
+        }
+
+    }
+
+    .propertiesView-name {
+        border-bottom: 1px solid black;
+        padding: 8.5px;
+        font-size: 12.5px;
+
+        &:hover {
+            cursor: text;
+        }
+    }
+
+    .propertiesView-settings {
+        border-bottom: 1px solid black;
+        //padding: 8.5px;
+        font-size: 12.5px;
+        font-weight: bold;
+
+        .propertiesView-settings-title {
+            font-weight: bold;
+            font-size: 12.5px;
+            padding: 4px;
+            display: flex;
+            color: white;
+            padding-left: 8px;
+            background-color: rgb(51, 51, 51);
+
+            &:hover {
+                cursor: pointer;
+            }
+
+            .propertiesView-settings-title-icon {
+                float: right;
+                justify-items: right;
+                align-items: flex-end;
+                margin-left: auto;
+                margin-right: 9px;
+
+                &:hover {
+                    cursor: pointer;
+                }
+            }
+        }
+
+        .propertiesView-settings-content {
+            margin-left: 12px;
+            padding-bottom: 10px;
+            padding-top: 8px;
+        }
+
+    }
+
+    .propertiesView-sharing {
+        border-bottom: 1px solid black;
+        //padding: 8.5px;
+
+        .propertiesView-sharing-title {
+            font-weight: bold;
+            font-size: 12.5px;
+            padding: 4px;
+            display: flex;
+            color: white;
+            padding-left: 8px;
+            background-color: rgb(51, 51, 51);
+
+            &:hover {
+                cursor: pointer;
+            }
+
+            .propertiesView-sharing-title-icon {
+                float: right;
+                justify-items: right;
+                align-items: flex-end;
+                margin-left: auto;
+                margin-right: 9px;
+
+                &:hover {
+                    cursor: pointer;
+                }
+            }
+        }
+
+        .propertiesView-sharing-content {
+            font-size: 10px;
+            padding: 10px;
+            margin-left: 5px;
+
+            .propertiesView-acls-checkbox {
+                float: right;
+                height: 20px;
+                margin-top: -20px;
+                margin-right: -15;
+
+                .propertiesView-acls-checkbox-text {
+                    font-size: 7px;
+                    margin-top: -10px;
+                    margin-left: 6px;
+                }
+            }
+
+            .change-buttons {
+                display: flex;
+
+                button {
+                    width: 5;
+                    height: 5;
+                }
+
+                input {
+                    width: 100%;
+                }
+            }
+        }
+    }
+
+    .propertiesView-appearance {
+        border-bottom: 1px solid black;
+        //padding: 8.5px;
+
+        .propertiesView-appearance-title {
+            font-weight: bold;
+            font-size: 12.5px;
+            padding: 4px;
+            display: flex;
+            color: white;
+            padding-left: 8px;
+            background-color: rgb(51, 51, 51);
+
+            &:hover {
+                cursor: pointer;
+            }
+
+            .propertiesView-appearance-title-icon {
+                float: right;
+                justify-items: right;
+                align-items: flex-end;
+                margin-left: auto;
+                margin-right: 9px;
+
+                &:hover {
+                    cursor: pointer;
+                }
+            }
+        }
+
+        .propertiesView-appearance-content {
+            font-size: 10px;
+            padding: 10px;
+            margin-left: 5px;
+        }
+    }
+
+    .propertiesView-transform {
+        border-bottom: 1px solid black;
+        //padding: 8.5px;
+
+        .propertiesView-transform-title {
+            font-weight: bold;
+            font-size: 12.5px;
+            padding: 4px;
+            display: flex;
+            color: white;
+            padding-left: 8px;
+            background-color: rgb(51, 51, 51);
+
+            &:hover {
+                cursor: pointer;
+            }
+
+            .propertiesView-transform-title-icon {
+                float: right;
+                justify-items: right;
+                align-items: flex-end;
+                margin-left: auto;
+                margin-right: 9px;
+
+                &:hover {
+                    cursor: pointer;
+                }
+            }
+        }
+
+        .propertiesView-transform-content {
+            font-size: 10px;
+            padding: 10px;
+            margin-left: 5px;
+        }
+    }
+
+    .notify-button {
+        padding: 2px;
+        width: 12px;
+        height: 12px;
+        background-color: black;
+        border-radius: 10px;
+        padding-left: 2px;
+        padding-right: 2px;
+        margin-top: 2px;
+        margin-left: 3px;
+
+        .notify-button-icon {
+            width: 6px;
+            height: 6.5px;
+            margin-left: .5px;
+        }
+
+        &:hover {
+            background-color: rgb(158, 158, 158);
+            cursor: pointer;
+        }
+    }
+
+    .expansion-button-icon {
+        width: 11px;
+        height: 11px;
+        color: black;
+        margin-left: 27px;
+
+        &:hover {
+            color: rgb(131, 131, 131);
+            cursor: pointer;
+        }
+    }
+
+    .propertiesView-sharingTable {
+
+        // whatever's commented out - add it back in when adding the buttons
+
+        // border: 1.5px solid black;
+        border: 1px solid black;
+        padding: 5px; // remove when adding buttons
+        border-radius: 6px; // remove when adding buttons
+        margin-right: 10px; // remove when adding buttons
+        // width: 100%;
+        // display: inline-table;
+        background-color: #ececec;
+        max-height: 130px;
+        overflow-y: scroll;
+        width: 92%;
+
+        .propertiesView-sharingTable-item {
+
+            display: flex;
+            // padding: 5px;
+            padding: 3px;
+            align-items: center;
+            border-bottom: 0.5px solid grey;
+
+            &:hover .propertiesView-sharingTable-item-name {
+                overflow-x: unset;
+                white-space: unset;
+                overflow-wrap: break-word;
+            }
+
+            .propertiesView-sharingTable-item-name {
+                font-weight: bold;
+                width: 95px;
+                overflow-x: hidden;
+                display: inline-block;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+            }
+
+            .propertiesView-sharingTable-item-permission {
+                display: flex;
+                align-items: flex-end;
+                margin-left: auto;
+
+                .permissions-select {
+                    border: none;
+                    background-color: inherit;
+                    width: 75px;
+                    //text-align: justify; // for Edge
+                    //text-align-last: end;
+
+                    &:hover {
+                        cursor: pointer;
+                    }
+                }
+            }
+
+            &:last-child {
+                border-bottom: none;
+            }
+        }
+    }
+
+    .propertiesView-fields {
+        border-bottom: 1px solid black;
+        //padding: 8.5px;
+
+        .propertiesView-fields-title {
+            font-weight: bold;
+            font-size: 12.5px;
+            padding: 4px;
+            display: flex;
+            color: white;
+            padding-left: 8px;
+            background-color: rgb(51, 51, 51);
+
+            &:hover {
+                cursor: pointer;
+            }
+
+            .propertiesView-fields-title-icon {
+                float: right;
+                justify-items: right;
+                align-items: flex-end;
+                margin-left: auto;
+                margin-right: 9px;
+
+                &:hover {
+                    cursor: pointer;
+                }
+            }
+
+        }
+
+        .propertiesView-fields-checkbox {
+            float: right;
+            height: 20px;
+            margin-top: -9px;
+
+            .propertiesView-fields-checkbox-text {
+                font-size: 7px;
+                margin-top: -10px;
+                margin-left: 6px;
+            }
+        }
+
+        .propertiesView-fields-content {
+            font-size: 10px;
+            margin-left: 2px;
+            padding: 10px;
+
+            &:hover {
+                cursor: pointer;
+            }
+        }
+    }
+
+    .field {
+        display: flex;
+        font-size: 7px;
+        background-color: #e8e8e8;
+        padding-right: 3px;
+        border: 0.5px solid grey;
+        border-radius: 5px;
+        padding-left: 3px;
+    }
+
+    .uneditable-field {
+        display: flex;
+        overflow-y: visible;
+        margin-bottom: 2px;
+
+        &:hover {
+            cursor: auto;
+        }
+    }
+    .propertiesView-contexts {
+
+        .propertiesView-contexts-title {
+            font-weight: bold;
+            font-size: 12.5px;
+            padding: 4px;
+            display: flex;
+            color: white;
+            padding-left: 8px;
+            background-color: rgb(51, 51, 51);
+
+            &:hover {
+                cursor: pointer;
+            }
+
+            .propertiesView-contexts-title-icon {
+                float: right;
+                justify-items: right;
+                align-items: flex-end;
+                margin-left: auto;
+                margin-right: 9px;
+
+                &:hover {
+                    cursor: pointer;
+                }
+            }
+        }
+
+        .propertiesView-contexts-content {
+            overflow: hidden;
+            padding: 10px;
+        }
+
+    }
+
+    .propertiesView-layout {
+
+        .propertiesView-layout-title {
+            font-weight: bold;
+            font-size: 12.5px;
+            padding: 4px;
+            display: flex;
+            color: white;
+            padding-left: 8px;
+            background-color: rgb(51, 51, 51);
+
+            &:hover {
+                cursor: pointer;
+            }
+
+            .propertiesView-layout-title-icon {
+                float: right;
+                justify-items: right;
+                align-items: flex-end;
+                margin-left: auto;
+                margin-right: 9px;
+
+                &:hover {
+                    cursor: pointer;
+                }
+            }
+        }
+
+        .propertiesView-layout-content {
+            overflow: hidden;
+            padding: 10px;
+        }
+
+    }
+
+    .propertiesView-presTrails {
+        border-bottom: 1px solid black;
+        //padding: 8.5px;
+
+        .propertiesView-presTrails-title {
+            font-weight: bold;
+            font-size: 12.5px;
+            padding: 4px;
+            display: flex;
+            color: white;
+            padding-left: 8px;
+            background-color: rgb(51, 51, 51);
+
+            &:hover {
+                cursor: pointer;
+            }
+
+            .propertiesView-presTrails-title-icon {
+                float: right;
+                justify-items: right;
+                align-items: flex-end;
+                margin-left: auto;
+                margin-right: 9px;
+
+                &:hover {
+                    cursor: pointer;
+                }
+            }
+        }
+
+        .propertiesView-presTrails-content {
+            font-size: 10px;
+            padding: 10px;
+            margin-left: 5px;
+        }
+    }
+}
+
+.inking-button {
+
+    display: flex;
+
+    .inking-button-points {
+        background-color: #333333;
+        padding: 7px;
+        border-radius: 7px;
+        margin-right: 32px;
+        width: 32;
+        height: 32;
+        padding-top: 9px;
+        margin-left: 18px;
+
+        &:hover {
+            background: rgb(131, 131, 131);
+            transform: scale(1.05);
+            cursor: pointer;
+        }
+    }
+
+    .inking-button-lock {
+        background-color: #333333;
+        padding: 7px;
+        border-radius: 7px;
+        margin-right: 32px;
+        width: 32;
+        height: 32;
+        padding-top: 9px;
+        padding-left: 10px;
+
+        &:hover {
+            background: rgb(131, 131, 131);
+            transform: scale(1.05);
+            cursor: pointer;
+        }
+    }
+
+    .inking-button-rotate {
+        background-color: #333333;
+        padding: 7px;
+        border-radius: 7px;
+        width: 32;
+        height: 32;
+        padding-top: 9px;
+        padding-left: 10px;
+
+        &:hover {
+            background: rgb(131, 131, 131);
+            transform: scale(1.05);
+            cursor: pointer;
+        }
+    }
+}
+
+.inputBox-duo {
+    display: flex;
+}
+
+.inputBox {
+
+    margin-top: 10px;
+    display: flex;
+    height: 19px;
+    margin-right: 15px;
+
+    .inputBox-title {
+        font-size: 12px;
+        padding-right: 5px;
+    }
+
+    .inputBox-input {
+        font-size: 10px;
+        width: 50px;
+        margin-right: 1px;
+        border-radius: 3px;
+
+        &:hover {
+            cursor: pointer;
+        }
+    }
+
+    .inputBox-button {
+
+        .inputBox-button-up {
+            background-color: #333333;
+            height: 9px;
+            padding-left: 3px;
+            padding-right: 3px;
+            padding-top: 1px;
+            padding-bottom: 1px;
+            border-radius: 1.5px;
+
+            &:hover {
+                background: rgb(131, 131, 131);
+                transform: scale(1.05);
+                cursor: pointer;
+            }
+        }
+
+        .inputBox-button-down {
+            background-color: #333333;
+            height: 9px;
+            padding-left: 3px;
+            padding-right: 3px;
+            padding-top: 1px;
+            padding-bottom: 1px;
+            border-radius: 1.5px;
+
+            &:hover {
+                background: rgb(131, 131, 131);
+                transform: scale(1.05);
+                cursor: pointer;
+            }
+        }
+
+    }
+}
+
+.color-palette {
+    width: 160px;
+    height: 360;
+}
+
+.strokeAndFill {
+    display: flex;
+    margin-top: 10px;
+
+    .fill {
+        margin-right: 40px;
+        display: flex;
+        padding-bottom: 7px;
+        margin-left: 35px;
+
+        .fill-title {
+            font-size: 12px;
+            margin-right: 2px;
+        }
+
+        .fill-button {
+            padding-top: 2px;
+            margin-top: -1px;
+        }
+    }
+
+    .stroke {
+        display: flex;
+
+        .stroke-title {
+            font-size: 12px;
+        }
+
+        .stroke-button {
+            padding-top: 2px;
+            margin-left: 2px;
+            margin-top: -1px;
+        }
+    }
+}
+
+.propertiesView-presSelected {
+    border-top: solid 1px darkgrey;
+    width: 100%;
+    padding-top: 5px;
+    font-family: Roboto;
+    font-weight: 500;
+    display: inline-flex;
+
+    .propertiesView-selectedList {
+        border-left: solid 1px darkgrey;
+        margin-left: 10px;
+        padding-left: 5px;
+
+        .selectedList-items {
+            font-size: 12;
+            font-weight: 300;
+            margin-top: 1;
+        }
+    }
+}
+
+.widthAndDash {
+
+    .width {
+        .width-top {
+            display: flex;
+
+            .width-title {
+                font-size: 12;
+                margin-right: 20px;
+                margin-left: 35px;
+                text-align: center;
+            }
+
+            .width-input {
+                margin-right: 30px;
+                margin-top: -10px;
+            }
+        }
+
+        .width-range {
+            margin-right: 1px;
+            margin-bottom: 6;
+        }
+    }
+
+    .arrows {
+
+        display: flex;
+        margin-bottom: 3px;
+        margin-left: 4px;
+
+        .arrows-head {
+
+            display: flex;
+            margin-right: 35px;
+
+            .arrows-head-title {
+                font-size: 10;
+            }
+
+            .arrows-head-input {
+                margin-left: 6px;
+                margin-top: 2px;
+            }
+        }
+
+        .arrows-tail {
+            display: flex;
+
+            .arrows-tail-title {
+                font-size: 10;
+            }
+
+            .arrows-tail-input {
+                margin-left: 6px;
+                margin-top: 2px;
+            }
+        }
+    }
+
+    .dashed {
+
+        display: flex;
+        margin-left: 64px;
+        margin-bottom: 6px;
+
+        .dashed-title {
+            font-size: 10;
+        }
+
+        .dashed-input {
+            margin-left: 6px;
+            margin-top: 2px;
+        }
+    }
+}
+
+.editable-title {
+    border: none;
+    padding: 6px;
+    padding-bottom: 2px;
+    background: #eeeeee;
+    border-top: 1px solid;
+    border-left: 1px solid;
+
+    &:hover {
+        border: 0.75px solid rgb(122, 28, 28);
+    }
+}
+
+
+.properties-flyout {
+    grid-column: 2/4;
+}
\ No newline at end of file
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
new file mode 100644
index 000000000..fef0a3e00
--- /dev/null
+++ b/src/client/views/PropertiesView.tsx
@@ -0,0 +1,1089 @@
+import React = require("react");
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Checkbox, Tooltip } from "@material-ui/core";
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import { ColorState, SketchPicker } from "react-color";
+import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, Field, HeightSym, WidthSym } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
+import { InkField } from "../../fields/InkField";
+import { ComputedField } from "../../fields/ScriptField";
+import { Cast, NumCast, StrCast } from "../../fields/Types";
+import { GetEffectiveAcl, SharingPermissions } from "../../fields/util";
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../Utils";
+import { DocumentType } from "../documents/DocumentTypes";
+import { DocumentManager } from "../util/DocumentManager";
+import { SelectionManager } from "../util/SelectionManager";
+import { SharingManager } from "../util/SharingManager";
+import { Transform } from "../util/Transform";
+import { undoBatch, UndoManager } from "../util/UndoManager";
+import { CollectionDockingView } from "./collections/CollectionDockingView";
+import { EditableView } from "./EditableView";
+import { FormatShapePane } from "./FormatShapePane";
+import { ContentFittingDocumentView } from "./nodes/ContentFittingDocumentView";
+import { KeyValueBox } from "./nodes/KeyValueBox";
+import { PresBox } from "./nodes/PresBox";
+import { PropertiesButtons } from "./PropertiesButtons";
+import { PropertiesDocContextSelector } from "./PropertiesDocContextSelector";
+import "./PropertiesView.scss";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+const _global = (window /* browser */ || global /* node */) as any;
+
+interface PropertiesViewProps {
+    width: number;
+    height: number;
+    renderDepth: number;
+    ScreenToLocalTransform: () => Transform;
+    onDown: (event: any) => void;
+}
+
+@observer
+export class PropertiesView extends React.Component<PropertiesViewProps> {
+    private _widthUndo?: UndoManager.Batch;
+
+    @computed get MAX_EMBED_HEIGHT() { return 200; }
+
+    @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; }
+    @computed get selectedDocumentView() {
+        if (SelectionManager.SelectedDocuments().length) {
+            return SelectionManager.SelectedDocuments()[0];
+        } else if (PresBox.Instance && PresBox.Instance._selectedArray.length) {
+            return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
+        } else { return undefined; }
+    }
+    @computed get isPres(): boolean {
+        if (this.selectedDoc?.type === DocumentType.PRES) return true;
+        return false;
+    }
+    @computed get dataDoc() { return this.selectedDoc?.[DataSym]; }
+
+    @observable layoutFields: boolean = false;
+
+    @observable openOptions: boolean = true;
+    @observable openSharing: boolean = true;
+    @observable openFields: boolean = true;
+    @observable openLayout: boolean = true;
+    @observable openContexts: boolean = true;
+    @observable openAppearance: boolean = true;
+    @observable openTransform: boolean = true;
+    // @observable selectedUser: string = "";
+    // @observable addButtonPressed: boolean = false;
+    @observable layoutDocAcls: boolean = false;
+
+    //Pres Trails booleans:
+    @observable openPresTransitions: boolean = false;
+    @observable openPresProgressivize: boolean = false;
+    @observable openAddSlide: boolean = false;
+    @observable openSlideOptions: boolean = false;
+
+    @observable inOptions: boolean = false;
+    @observable _controlBtn: boolean = false;
+    @observable _lock: boolean = false;
+
+    @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; }
+
+    @action
+    rtfWidth = () => {
+        if (this.selectedDoc) {
+            return Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20);
+        } else {
+            return 0;
+        }
+    }
+    @action
+    rtfHeight = () => {
+        if (this.selectedDoc) {
+            return this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+        } else {
+            return 0;
+        }
+    }
+
+    @action
+    docWidth = () => {
+        if (this.selectedDoc) {
+            const layoutDoc = this.selectedDoc;
+            const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
+            if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.width - 20));
+            return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.width - 20) : this.props.width - 20;
+        } else {
+            return 0;
+        }
+    }
+
+    @action
+    docHeight = () => {
+        if (this.selectedDoc && this.dataDoc) {
+            const layoutDoc = this.selectedDoc;
+            return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => {
+                const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
+                if (aspect) return this.docWidth() * aspect;
+                return layoutDoc._fitWidth ? (!this.dataDoc._nativeHeight ? NumCast(this.props.height) :
+                    Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth,
+                        NumCast(this.props.height)))) :
+                    NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50;
+            })()));
+        } else {
+            return 0;
+        }
+    }
+
+    @computed get expandedField() {
+        if (this.dataDoc && this.selectedDoc) {
+            const ids: { [key: string]: string } = {};
+            const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] :
+                SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? Doc.Layout(dv.layoutDoc) : dv.dataDoc);
+            docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)));
+            const rows: JSX.Element[] = [];
+            for (const key of Object.keys(ids).slice().sort()) {
+                const docvals = new Set<any>();
+                docs.forEach(doc => docvals.add(doc[key]));
+                const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key];
+                if (key[0] === "#") {
+                    rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "2px" }} key={key}>
+                        <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key}</span>
+                    &nbsp;
+                </div>);
+                } else {
+                    const contentElement = <EditableView key="editableView"
+                        contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
+                        height={13}
+                        fontSize={10}
+                        GetValue={() => contents !== undefined ? Field.toString(contents as Field) : "null"}
+                        SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }}
+                    />;
+                    rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "-1px" }} key={key}>
+                        <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ":"}</span>
+                        &nbsp;
+                        {contentElement}
+                    </div>);
+                }
+            }
+            rows.push(<div className="field" key={"newKeyValue"} style={{ marginTop: "3px" }}>
+                <EditableView
+                    key="editableView"
+                    contents={"add key:value or #tags"}
+                    height={13}
+                    fontSize={10}
+                    GetValue={() => ""}
+                    SetValue={this.setKeyValue} />
+            </div>);
+            return rows;
+        }
+    }
+
+    @computed get noviceFields() {
+        if (this.dataDoc) {
+            const ids: { [key: string]: string } = {};
+            const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => dv.dataDoc);
+            docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)));
+            const rows: JSX.Element[] = [];
+            const noviceReqFields = ["author", "creationDate"];
+            const noviceLayoutFields = ["_curPage"];
+            const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("ACL") && key !== "UseCors")),
+            ...noviceReqFields, ...noviceLayoutFields];
+            for (const key of noviceKeys.sort()) {
+                const docvals = new Set<any>();
+                docs.forEach(doc => docvals.add(doc[key]));
+                const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key];
+                if (key[0] === "#") {
+                    rows.push(<div className="uneditable-field" key={key}>
+                        <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key}</span>
+                        &nbsp;
+                    </div>);
+                } else if (contents !== undefined) {
+                    const value = Field.toString(contents as Field);
+                    if (noviceReqFields.includes(key) || key.indexOf("lastModified") !== -1) {
+                        rows.push(<div className="uneditable-field" key={key}>
+                            <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ": "}</span>
+                            <div style={{ whiteSpace: "nowrap", overflowX: "hidden" }}>{value}</div>
+                        </div>);
+                    } else {
+                        const contentElement = <EditableView key="editableView"
+                            contents={value}
+                            height={13}
+                            fontSize={10}
+                            GetValue={() => contents !== undefined ? Field.toString(contents as Field) : "null"}
+                            SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }}
+                        />;
+
+                        rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "-1px" }} key={key}>
+                            <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ":"}</span>
+                        &nbsp;
+                        {contentElement}
+                        </div>);
+                    }
+                }
+            }
+            rows.push(<div className="field" key={"newKeyValue"} style={{ marginTop: "3px" }}>
+                <EditableView
+                    key="editableView"
+                    contents={"add key:value or #tags"}
+                    height={13}
+                    fontSize={10}
+                    GetValue={() => ""}
+                    SetValue={this.setKeyValue} />
+            </div>);
+            return rows;
+        }
+    }
+
+    @undoBatch
+    setKeyValue = (value: string) => {
+        const docs = SelectionManager.SelectedDocuments().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? dv.layoutDoc : dv.dataDoc);
+        docs.forEach(doc => {
+            if (value.indexOf(":") !== -1) {
+                const newVal = value[0].toUpperCase() + value.substring(1, value.length);
+                KeyValueBox.SetField(doc, newVal.substring(0, newVal.indexOf(":")), newVal.substring(newVal.indexOf(":") + 1, newVal.length), true);
+                return true;
+            } else if (value[0] === "#") {
+                const newVal = value + `:'${value}'`;
+                KeyValueBox.SetField(doc, newVal.substring(0, newVal.indexOf(":")), newVal.substring(newVal.indexOf(":") + 1, newVal.length), true);
+                return true;
+            }
+        });
+        return false;
+    }
+
+    @observable transform: Transform = Transform.Identity();
+    getTransform = () => this.transform;
+    propertiesDocViewRef = (ref: HTMLDivElement) => {
+        const observer = new _global.ResizeObserver(action((entries: any) => {
+            const cliRect = ref.getBoundingClientRect();
+            this.transform = new Transform(-cliRect.x, -cliRect.y, 1);
+        }));
+        ref && observer.observe(ref);
+    }
+
+    @computed get contexts() {
+        return !this.selectedDoc ? (null) : <PropertiesDocContextSelector Document={this.selectedDoc} hideTitle={true} addDocTab={(doc, where) => CollectionDockingView.AddRightSplit(doc)} />;
+    }
+
+    previewBackground = () => "lightgrey";
+    @computed get layoutPreview() {
+        if (SelectionManager.SelectedDocuments().length > 1) {
+            return "-- multiple selected --";
+        }
+        if (this.selectedDoc) {
+            const layoutDoc = Doc.Layout(this.selectedDoc);
+            const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : this.docHeight;
+            const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : this.docWidth;
+            return <div ref={this.propertiesDocViewRef} style={{ pointerEvents: "none", display: "inline-block", height: panelHeight() }} key={this.selectedDoc[Id]}>
+                <ContentFittingDocumentView
+                    Document={layoutDoc}
+                    DataDoc={this.dataDoc}
+                    LibraryPath={emptyPath}
+                    renderDepth={this.props.renderDepth + 1}
+                    rootSelected={returnFalse}
+                    treeViewDoc={undefined}
+                    backgroundColor={this.previewBackground}
+                    fitToBox={true}
+                    FreezeDimensions={true}
+                    NativeWidth={layoutDoc.type ===
+                        StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : returnZero}
+                    NativeHeight={layoutDoc.type ===
+                        StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : returnZero}
+                    PanelWidth={panelWidth}
+                    PanelHeight={panelHeight}
+                    focus={returnFalse}
+                    ScreenToLocalTransform={this.getTransform}
+                    docFilters={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                    ContainingCollectionDoc={undefined}
+                    ContainingCollectionView={undefined}
+                    addDocument={returnFalse}
+                    moveDocument={undefined}
+                    removeDocument={returnFalse}
+                    parentActive={() => false}
+                    whenActiveChanged={emptyFunction}
+                    addDocTab={returnFalse}
+                    pinToPres={emptyFunction}
+                    bringToFront={returnFalse}
+                    ContentScaling={returnOne}
+                    dontRegisterView={true}
+                    dropAction={undefined}
+                />
+            </div>;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Handles the changing of a user's permissions from the permissions panel.
+     */
+    @undoBatch
+    changePermissions = (e: any, user: string) => {
+        SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, this.selectedDoc!);
+    }
+
+    /**
+     * @returns the options for the permissions dropdown.
+     */
+    getPermissionsSelect(user: string, permission: string) {
+        return <select className="permissions-select"
+            value={permission}
+            onChange={e => this.changePermissions(e, user)}>
+            {Object.values(SharingPermissions).map(permission => {
+                return (
+                    <option key={permission} value={permission}>
+                        {permission}
+                    </option>);
+            })}
+        </select>;
+    }
+
+    /**
+     * @returns the notification icon. On clicking, it should notify someone of a document been shared with them.
+     */
+    @computed get notifyIcon() {
+        return <Tooltip title={<div className="dash-tooltip">Notify with message</div>}>
+            <div className="notify-button">
+                <FontAwesomeIcon className="notify-button-icon" icon="bell" color="white" size="sm" />
+            </div>
+        </Tooltip>;
+    }
+
+    /**
+     * ... next to the owner that opens the main SharingManager interface on click.
+     */
+    @computed get expansionIcon() {
+        return <Tooltip title={<div className="dash-tooltip">{"Show more permissions"}</div>}>
+            <div className="expansion-button" onPointerDown={() => {
+                if (this.selectedDocumentView || this.selectedDoc) {
+                    SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDocumentView ? this.selectedDocumentView : undefined, this.selectedDoc);
+                }
+            }}>
+                <FontAwesomeIcon className="expansion-button-icon" icon="ellipsis-h" color="black" size="sm" />
+            </div>
+        </Tooltip>;
+    }
+
+    /**
+     * @returns a row of the permissions panel
+     */
+    sharingItem(name: string, effectiveAcl: symbol, permission: string) {
+        return <div className="propertiesView-sharingTable-item" key={name + permission}
+        // style={{ backgroundColor: this.selectedUser === name ? "#bcecfc" : "" }}
+        // onPointerDown={action(() => this.selectedUser = this.selectedUser === name ? "" : name)}
+        >
+            <div className="propertiesView-sharingTable-item-name" style={{ width: name !== "Me" ? "85px" : "80px" }}> {name} </div>
+            {/* {name !== "Me" ? this.notifyIcon : null} */}
+            <div className="propertiesView-sharingTable-item-permission">
+                {effectiveAcl === AclAdmin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission}
+                {permission === "Owner" ? this.expansionIcon : null}
+            </div>
+        </div>;
+    }
+
+    /**
+     * @returns the sharing and permissiosn panel.
+     */
+    @computed get sharingTable() {
+        const AclMap = new Map<symbol, string>([
+            [AclPrivate, SharingPermissions.None],
+            [AclReadonly, SharingPermissions.View],
+            [AclAddonly, SharingPermissions.Add],
+            [AclEdit, SharingPermissions.Edit],
+            [AclAdmin, SharingPermissions.Admin]
+        ]);
+
+        const target = this.layoutDocAcls ? this.selectedDoc! : this.selectedDoc![DataSym];
+
+        const effectiveAcl = GetEffectiveAcl(target);
+        const tableEntries = [];
+
+        // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => {
+        if (target[AclSym]) {
+            for (const [key, value] of Object.entries(target[AclSym])) {
+                const name = key.substring(4).replace("_", ".");
+                if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public"/* && sidebarUsersDisplayed![name] !== false*/) {
+                    tableEntries.push(this.sharingItem(name, effectiveAcl, AclMap.get(value as symbol)!));
+                }
+            }
+        }
+
+        //     if (Doc.UserDoc().sidebarUsersDisplayed) {
+        //         for (const [name, value] of Object.entries(sidebarUsersDisplayed!)) {
+        //             if (value === true && !this.selectedDoc![`ACL-${name.substring(8).replace(".", "_")}`]) tableEntries.push(this.sharingItem(name.substring(8), effectiveAcl, SharingPermissions.None));
+        //         }
+        //     }
+        // })
+
+        // shifts the current user, owner, public to the top of the doc.
+        tableEntries.unshift(this.sharingItem("Public", effectiveAcl, (AclMap.get(target[AclSym]?.["ACL-Public"]) || SharingPermissions.None)));
+        tableEntries.unshift(this.sharingItem("Me", effectiveAcl, Doc.CurrentUserEmail === target.author ? "Owner" : AclMap.get(effectiveAcl)!));
+        if (Doc.CurrentUserEmail !== target.author) tableEntries.unshift(this.sharingItem(StrCast(target.author), effectiveAcl, "Owner"));
+
+        return <div className="propertiesView-sharingTable">
+            {tableEntries}
+        </div>;
+    }
+
+    @computed get fieldsCheckbox() {
+        return <Checkbox
+            color="primary"
+            onChange={this.toggleCheckbox}
+            checked={this.layoutFields}
+        />;
+    }
+
+    @action
+    toggleCheckbox = () => {
+        this.layoutFields = !this.layoutFields;
+    }
+
+    @computed get editableTitle() {
+        const titles = new Set<string>();
+        SelectionManager.SelectedDocuments().forEach(dv => titles.add(StrCast(dv.rootDoc.title)));
+        const title = Array.from(titles.keys()).length > 1 ? "--multiple selected--" : StrCast(this.selectedDoc?.title);
+        return <div className="editable-title"><EditableView
+            key="editableView"
+            contents={title}
+            height={25}
+            fontSize={14}
+            GetValue={() => title}
+            SetValue={this.setTitle} /> </div>;
+    }
+
+    @undoBatch
+    @action
+    setTitle = (value: string) => {
+        if (SelectionManager.SelectedDocuments().length > 1) {
+            SelectionManager.SelectedDocuments().map(dv => Doc.SetInPlace(dv.rootDoc, "title", value, true));
+            return true;
+        } else if (this.dataDoc) {
+            if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, "title", value, true);
+            else KeyValueBox.SetField(this.dataDoc, "title", value, true);
+            return true;
+        }
+        return false;
+    }
+
+
+    @undoBatch
+    @action
+    rotate = (angle: number) => {
+        const _centerPoints: { X: number, Y: number }[] = [];
+        if (this.selectedDoc) {
+            const doc = this.selectedDoc;
+            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+                const ink = Cast(doc.data, InkField)?.inkData;
+                if (ink) {
+                    const xs = ink.map(p => p.X);
+                    const ys = ink.map(p => p.Y);
+                    const left = Math.min(...xs);
+                    const top = Math.min(...ys);
+                    const right = Math.max(...xs);
+                    const bottom = Math.max(...ys);
+                    _centerPoints.push({ X: left, Y: top });
+                }
+            }
+
+            var index = 0;
+            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+                doc.rotation = Number(doc.rotation) + Number(angle);
+                const inks = Cast(doc.data, InkField)?.inkData;
+                if (inks) {
+                    const newPoints: { X: number, Y: number }[] = [];
+                    inks.forEach(ink => {
+                        const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X;
+                        const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y;
+                        newPoints.push({ X: newX, Y: newY });
+                    });
+                    doc.data = new InkField(newPoints);
+                    const xs = newPoints.map(p => p.X);
+                    const ys = newPoints.map(p => p.Y);
+                    const left = Math.min(...xs);
+                    const top = Math.min(...ys);
+                    const right = Math.max(...xs);
+                    const bottom = Math.max(...ys);
+
+                    doc._height = (bottom - top);
+                    doc._width = (right - left);
+                }
+                index++;
+            }
+        }
+    }
+
+
+
+    @computed
+    get controlPointsButton() {
+        return <div className="inking-button">
+            <Tooltip title={<div className="dash-tooltip">{"Edit points"}</div>}>
+                <div className="inking-button-points" onPointerDown={action(() => FormatShapePane.Instance._controlBtn = !FormatShapePane.Instance._controlBtn)} style={{ backgroundColor: FormatShapePane.Instance._controlBtn ? "black" : "" }}>
+                    <FontAwesomeIcon icon="bezier-curve" color="white" size="lg" />
+                </div>
+            </Tooltip>
+            <Tooltip title={<div className="dash-tooltip">{FormatShapePane.Instance._lock ? "Unlock ratio" : "Lock ratio"}</div>}>
+                <div className="inking-button-lock" onPointerDown={action(() => FormatShapePane.Instance._lock = !FormatShapePane.Instance._lock)} >
+                    <FontAwesomeIcon icon={FormatShapePane.Instance._lock ? "lock" : "unlock"} color="white" size="lg" />
+                </div>
+            </Tooltip>
+            <Tooltip title={<div className="dash-tooltip">{"Rotate 90˚"}</div>}>
+                <div className="inking-button-rotate" onPointerDown={action(() => this.rotate(Math.PI / 2))}>
+                    <FontAwesomeIcon icon="undo" color="white" size="lg" />
+                </div>
+            </Tooltip>
+        </div>;
+    }
+
+    inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => {
+        return <div className="inputBox"
+            style={{
+                marginRight: title === "X:" ? "19px" : "",
+                marginLeft: title === "∠:" ? "39px" : ""
+            }}>
+            <div className="inputBox-title"> {title} </div>
+            <input className="inputBox-input"
+                type="text" value={value}
+                onChange={e => {
+                    setter(e.target.value);
+                }}
+                onKeyPress={e => {
+                    e.stopPropagation();
+                }} />
+            <div className="inputBox-button">
+                <div className="inputBox-button-up" key="up2"
+                    onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} >
+                    <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
+                </div>
+                <div className="inputbox-Button-down" key="down2"
+                    onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} >
+                    <FontAwesomeIcon icon="caret-down" color="white" size="sm" />
+                </div>
+            </div>
+        </div>;
+    }
+
+    inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => {
+        return <div className="inputBox-duo">
+            {this.inputBox(key, value, setter, title1)}
+            {title2 === "" ? (null) : this.inputBox(key2, value2, setter2, title2)}
+        </div>;
+    }
+
+    @action
+    upDownButtons = (dirs: string, field: string) => {
+        switch (field) {
+            case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break;
+            // case "rot": this.selectedInk?.forEach(i => i.rootDoc.rotation = NumCast(i.rootDoc.rotation) + (dirs === "up" ? 0.1 : -0.1)); break;
+            case "Xps": this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === "up" ? 10 : -10)); break;
+            case "Yps": this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === "up" ? 10 : -10)); break;
+            case "stk": this.selectedDoc && (this.selectedDoc.strokeWidth = NumCast(this.selectedDoc?.strokeWidth) + (dirs === "up" ? .1 : -.1)); break;
+            case "wid":
+                const oldWidth = NumCast(this.selectedDoc?._width);
+                const oldHeight = NumCast(this.selectedDoc?._height);
+                const oldX = NumCast(this.selectedDoc?.x);
+                const oldY = NumCast(this.selectedDoc?.y);
+                this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === "up" ? 10 : - 10));
+                FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height)));
+                const doc = this.selectedDoc;
+                if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
+                    const ink = Cast(doc.data, InkField)?.inkData;
+                    if (ink) {
+                        const newPoints: { X: number, Y: number }[] = [];
+                        for (var j = 0; j < ink.length; j++) {
+                            // (new x — oldx) + (oldxpoint * newWidt)/oldWidth 
+                            const newX = (NumCast(doc.x) - oldX) + (ink[j].X * NumCast(doc._width)) / oldWidth;
+                            const newY = (NumCast(doc.y) - oldY) + (ink[j].Y * NumCast(doc._height)) / oldHeight;
+                            newPoints.push({ X: newX, Y: newY });
+                        }
+                        doc.data = new InkField(newPoints);
+                    }
+                }
+                break;
+            case "hgt":
+                const oWidth = NumCast(this.selectedDoc?._width);
+                const oHeight = NumCast(this.selectedDoc?._height);
+                const oX = NumCast(this.selectedDoc?.x);
+                const oY = NumCast(this.selectedDoc?.y);
+                this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === "up" ? 10 : - 10));
+                FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width)));
+                const docu = this.selectedDoc;
+                if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) {
+                    const ink = Cast(docu.data, InkField)?.inkData;
+                    if (ink) {
+                        const newPoints: { X: number, Y: number }[] = [];
+                        for (var j = 0; j < ink.length; j++) {
+                            // (new x — oldx) + (oldxpoint * newWidt)/oldWidth 
+                            const newX = (NumCast(docu.x) - oX) + (ink[j].X * NumCast(docu._width)) / oWidth;
+                            const newY = (NumCast(docu.y) - oY) + (ink[j].Y * NumCast(docu._height)) / oHeight;
+                            newPoints.push({ X: newX, Y: newY });
+                        }
+                        docu.data = new InkField(newPoints);
+                    }
+                }
+                break;
+        }
+    }
+
+    getField(key: string) {
+        //if (this.selectedDoc) {
+        return Field.toString(this.selectedDoc?.[key] as Field);
+        // } else {
+        //     return undefined as Opt<string>;
+        // }
+    }
+
+    @computed get shapeXps() { return this.getField("x"); }
+    @computed get shapeYps() { return this.getField("y"); }
+    @computed get shapeRot() { return this.getField("rotation"); }
+    @computed get shapeHgt() { return this.getField("_height"); }
+    @computed get shapeWid() { return this.getField("_width"); }
+    set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Number(value)); }
+    set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Number(value)); }
+    set shapeRot(value) { this.selectedDoc && (this.selectedDoc.rotation = Number(value)); }
+    set shapeWid(value) {
+        const oldWidth = NumCast(this.selectedDoc?._width);
+        this.selectedDoc && (this.selectedDoc._width = Number(value));
+        FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth);
+    }
+    set shapeHgt(value) {
+        const oldHeight = NumCast(this.selectedDoc?._height);
+        this.selectedDoc && (this.selectedDoc._height = Number(value));
+        FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight);
+    }
+
+    @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; }, "H:", "wid", this.shapeWid, (val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; }, "W:"); }
+    @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeXps = val; } return true; }, "X:", "Yps", this.shapeYps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeYps = val; } return true; }, "Y:"); }
+    @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, "∠:", "rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, ""); }
+
+
+    @observable private _fillBtn = false;
+    @observable private _lineBtn = false;
+
+    private _lastFill = "#D0021B";
+    private _lastLine = "#D0021B";
+    private _lastDash: any = "2";
+
+    @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
+    @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
+    set colorFil(value) { value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); }
+    set colorStk(value) { value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); }
+
+    colorButton(value: string, type: string, setter: () => {}) {
+        // return <div className="properties-flyout" onPointerEnter={e => this.changeScrolling(false)}
+        //     onPointerLeave={e => this.changeScrolling(true)}>
+        //     <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+        //         content={type === "fill" ? this.fillPicker : this.linePicker}>
+        return <div className="color-button" key="color" onPointerDown={undoBatch(action(e => setter()))}>
+            <div className="color-button-preview" style={{
+                backgroundColor: value ?? "121212", width: 15, height: 15,
+                display: value === "" || value === "transparent" ? "none" : ""
+            }} />
+            {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -14 }}>☒</p> : ""}
+        </div>;
+        //     </Flyout>
+        // </div>;
+
+    }
+
+    @undoBatch
+    @action
+    switchStk = (color: ColorState) => {
+        const val = String(color.hex);
+        this.colorStk = val;
+        return true;
+    }
+    @undoBatch
+    @action
+    switchFil = (color: ColorState) => {
+        const val = String(color.hex);
+        this.colorFil = val;
+        return true;
+    }
+
+    colorPicker(setter: (color: string) => {}, type: string) {
+        return <SketchPicker onChange={type === "stk" ? this.switchStk : this.switchFil}
+            presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
+                '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
+                '#FFFFFF', '#f1efeb', 'transparent']}
+            color={type === "stk" ? this.colorStk : this.colorFil} />;
+    }
+
+    @computed get fillButton() { return this.colorButton(this.colorFil, "fill", () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); }
+    @computed get lineButton() { return this.colorButton(this.colorStk, "line", () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); }
+
+    @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); }
+    @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); }
+
+    @computed get strokeAndFill() {
+        return <div>
+            <div key="fill" className="strokeAndFill">
+                <div className="fill">
+                    <div className="fill-title">Fill:</div>
+                    <div className="fill-button">{this.fillButton}</div>
+                </div>
+                <div className="stroke">
+                    <div className="stroke-title"> Stroke: </div>
+                    <div className="stroke-button">{this.lineButton}</div>
+                </div>
+            </div>
+            {this._fillBtn ? this.fillPicker : ""}
+            {this._lineBtn ? this.linePicker : ""}
+        </div>;
+    }
+
+    @computed get solidStk() { return this.selectedDoc?.color && (!this.selectedDoc?.strokeDash || this.selectedDoc?.strokeDash === "0") ? true : false; }
+    @computed get dashdStk() { return this.selectedDoc?.strokeDash || ""; }
+    @computed get unStrokd() { return this.selectedDoc?.color ? true : false; }
+    @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
+    @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
+    @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
+    set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
+    set dashdStk(value) {
+        value && (this._lastDash = value) && (this.unStrokd = false);
+        this.selectedDoc && (this.selectedDoc.strokeDash = value ? this._lastDash : undefined);
+    }
+    set widthStk(value) { this.selectedDoc && (this.selectedDoc.strokeWidth = Number(value)); }
+    set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
+    set markHead(value) { this.selectedDoc && (this.selectedDoc.strokeStartMarker = value); }
+    set markTail(value) { this.selectedDoc && (this.selectedDoc.strokeEndMarker = value); }
+
+
+    @computed get stkInput() { return this.regInput("stk", this.widthStk, (val: string) => this.widthStk = val); }
+
+
+    regInput = (key: string, value: any, setter: (val: string) => {}) => {
+        return <div className="inputBox">
+            <input className="inputBox-input"
+                type="text" value={value}
+                onChange={e => setter(e.target.value)} />
+            <div className="inputBox-button">
+                <div className="inputBox-button-up" key="up2"
+                    onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} >
+                    <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
+                </div>
+                <div className="inputbox-Button-down" key="down2"
+                    onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} >
+                    <FontAwesomeIcon icon="caret-down" color="white" size="sm" />
+                </div>
+            </div>
+        </div>;
+    }
+
+    @computed get widthAndDash() {
+        return <div className="widthAndDash">
+            <div className="width">
+                <div className="width-top">
+                    <div className="width-title">Width:</div>
+                    <div className="width-input">{this.stkInput}</div>
+                </div>
+                <input className="width-range" type="range"
+                    defaultValue={Number(this.widthStk)} min={1} max={100}
+                    onChange={(action((e) => this.widthStk = e.target.value))}
+                    onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("width undo"); }}
+                    onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }}
+                />
+            </div>
+
+            <div className="arrows">
+                <div className="arrows-head">
+                    <div className="arrows-head-title" >Arrow Head: </div>
+                    <input key="markHead" className="arrows-head-input" type="checkbox"
+                        checked={this.markHead !== ""}
+                        onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} />
+                </div>
+                <div className="arrows-tail">
+                    <div className="arrows-tail-title" >Arrow End: </div>
+                    <input key="markTail" className="arrows-tail-input" type="checkbox"
+                        checked={this.markTail !== ""}
+                        onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} />
+                </div>
+            </div>
+            <div className="dashed">
+                <div className="dashed-title">Dashed Line:</div>
+                <input key="markHead" className="dashed-input"
+                    type="checkbox" checked={this.dashdStk === "2"}
+                    onChange={this.changeDash} />
+            </div>
+        </div>;
+    }
+
+    @undoBatch @action
+    changeDash = () => {
+        this.dashdStk = this.dashdStk === "2" ? "0" : "2";
+    }
+
+    @computed get appearanceEditor() {
+        return <div className="appearance-editor">
+            {this.widthAndDash}
+            {this.strokeAndFill}
+        </div>;
+    }
+
+    @computed get transformEditor() {
+        return <div className="transform-editor">
+            {this.controlPointsButton}
+            {this.hgtInput}
+            {this.XpsInput}
+            {this.rotInput}
+        </div>;
+    }
+
+    /**
+     * Handles adding and removing members from the sharing panel
+     */
+    // handleUserChange = (selectedUser: string, add: boolean) => {
+    //     if (!Doc.UserDoc().sidebarUsersDisplayed) Doc.UserDoc().sidebarUsersDisplayed = new Doc;
+    //     DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => {
+    //         sidebarUsersDisplayed![`display-${selectedUser}`] = add;
+    //         !add && runInAction(() => this.selectedUser = "");
+    //     });
+    // }
+
+    render() {
+        if (!this.selectedDoc && !this.isPres) {
+            return <div className="propertiesView" style={{ width: this.props.width }}>
+                <div className="propertiesView-title" style={{ width: this.props.width }}>
+                    No Document Selected
+                </div>
+            </div>;
+
+        } else {
+            const novice = Doc.UserDoc().noviceMode;
+
+            if (this.selectedDoc && !this.isPres) {
+                return <div className="propertiesView" style={{
+                    width: this.props.width,
+                    //overflowY: this.scrolling ? "scroll" : "visible"
+                }} >
+                    <div className="propertiesView-title" style={{ width: this.props.width }}>
+                        Properties
+                    {/* <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}>
+                        <FontAwesomeIcon icon="times" color="black" size="sm" />
+                    </div> */}
+                    </div>
+                    <div className="propertiesView-name">
+                        {this.editableTitle}
+                    </div>
+                    <div className="propertiesView-settings" onPointerEnter={action(() => this.inOptions = true)}
+                        onPointerLeave={action(() => this.inOptions = false)}>
+                        <div className="propertiesView-settings-title"
+                            onPointerDown={action(() => this.openOptions = !this.openOptions)}
+                            style={{ backgroundColor: this.openOptions ? "black" : "" }}>
+                            Options
+                        <div className="propertiesView-settings-title-icon">
+                                <FontAwesomeIcon icon={this.openOptions ? "caret-down" : "caret-right"} size="lg" color="white" />
+                            </div>
+                        </div>
+                        {!this.openOptions ? (null) :
+                            <div className="propertiesView-settings-content">
+                                <PropertiesButtons />
+                            </div>}
+                    </div>
+                    <div className="propertiesView-sharing">
+                        <div className="propertiesView-sharing-title"
+                            onPointerDown={action(() => this.openSharing = !this.openSharing)}
+                            style={{ backgroundColor: this.openSharing ? "black" : "" }}>
+                            Sharing {"&"} Permissions
+                        <div className="propertiesView-sharing-title-icon">
+                                <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" />
+                            </div>
+                        </div>
+                        {!this.openSharing ? (null) :
+                            <div className="propertiesView-sharing-content">
+                                <div className="propertiesView-acls-checkbox">
+                                    <Checkbox
+                                        color="primary"
+                                        onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)}
+                                        checked={this.layoutDocAcls}
+                                    />;
+                                    <div className="propertiesView-acls-checkbox-text">Layout</div>
+                                </div>
+                                {this.sharingTable}
+                                {/* <div className="change-buttons">
+                            <button
+                                onPointerDown={action(() => this.addButtonPressed = !this.addButtonPressed)}
+                            >
+                                <FontAwesomeIcon icon={fa.faPlus} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} />
+                            </button>
+                            <button
+                                id="sharingProperties-removeUser"
+                                onPointerDown={() => this.handleUserChange(this.selectedUser, false)}
+                                style={{ backgroundColor: this.selectedUser ? "#121721" : "#777777" }}
+                            ><FontAwesomeIcon icon={fa.faMinus} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} /></button>
+                            <button onClick={() => SharingManager.Instance.open(this.selectedDocumentView!)}><FontAwesomeIcon icon={fa.faCog} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} /></button>
+                            {this.addButtonPressed ?
+                                // <input type="text" onKeyDown={this.handleKeyPress} /> :
+                                <select onChange={e => this.handleUserChange(e.target.value, true)}>
+                                    <option selected disabled hidden>
+                                        Add users
+                                    </option>
+                                    {SharingManager.Instance.users.map(user =>
+                                        (<option value={user.user.email}>
+                                            {user.user.email}
+                                        </option>)
+                                    )}
+                                    {GroupManager.Instance.getAllGroups().map(group =>
+                                        (<option value={StrCast(group.groupName)}>
+                                            {StrCast(group.groupName)}
+                                        </option>))}
+                                </select> :
+                                null}
+                        </div> */}
+                            </div>}
+                    </div>
+
+                    {!this.isInk ? (null) :
+                        <div className="propertiesView-appearance">
+                            <div className="propertiesView-appearance-title"
+                                onPointerDown={action(() => this.openAppearance = !this.openAppearance)}
+                                style={{ backgroundColor: this.openAppearance ? "black" : "" }}>
+                                Appearance
+                            <div className="propertiesView-appearance-title-icon">
+                                    <FontAwesomeIcon icon={this.openAppearance ? "caret-down" : "caret-right"} size="lg" color="white" />
+                                </div>
+                            </div>
+                            {!this.openAppearance ? (null) :
+                                <div className="propertiesView-appearance-content">
+                                    {this.appearanceEditor}
+                                </div>}
+                        </div>}
+
+                    {this.isInk ? <div className="propertiesView-transform">
+                        <div className="propertiesView-transform-title"
+                            onPointerDown={action(() => this.openTransform = !this.openTransform)}
+                            style={{ backgroundColor: this.openTransform ? "black" : "" }}>
+                            Transform
+                        <div className="propertiesView-transform-title-icon">
+                                <FontAwesomeIcon icon={this.openTransform ? "caret-down" : "caret-right"} size="lg" color="white" />
+                            </div>
+                        </div>
+                        {this.openTransform ? <div className="propertiesView-transform-content">
+                            {this.transformEditor}
+                        </div> : null}
+                    </div> : null}
+
+                    <div className="propertiesView-fields">
+                        <div className="propertiesView-fields-title"
+                            onPointerDown={action(() => this.openFields = !this.openFields)}
+                            style={{ backgroundColor: this.openFields ? "black" : "" }}>
+                            Fields {"&"} Tags
+                            <div className="propertiesView-fields-title-icon">
+                                <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" />
+                            </div>
+                        </div>
+                        {!novice && this.openFields ? <div className="propertiesView-fields-checkbox">
+                            {this.fieldsCheckbox}
+                            <div className="propertiesView-fields-checkbox-text">Layout</div>
+                        </div> : null}
+                        {!this.openFields ? (null) :
+                            <div className="propertiesView-fields-content">
+                                {novice ? this.noviceFields : this.expandedField}
+                            </div>}
+                    </div>
+                    <div className="propertiesView-contexts">
+                        <div className="propertiesView-contexts-title"
+                            onPointerDown={action(() => this.openContexts = !this.openContexts)}
+                            style={{ backgroundColor: this.openContexts ? "black" : "" }}>
+                            Contexts
+                        <div className="propertiesView-contexts-title-icon">
+                                <FontAwesomeIcon icon={this.openContexts ? "caret-down" : "caret-right"} size="lg" color="white" />
+                            </div>
+                        </div>
+                        {this.openContexts ? <div className="propertiesView-contexts-content"  >{this.contexts}</div> : null}
+                    </div>
+                    <div className="propertiesView-layout">
+                        <div className="propertiesView-layout-title"
+                            onPointerDown={action(() => this.openLayout = !this.openLayout)}
+                            style={{ backgroundColor: this.openLayout ? "black" : "" }}>
+                            Layout
+                        <div className="propertiesView-layout-title-icon">
+                                <FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" />
+                            </div>
+                        </div>
+                        {this.openLayout ? <div className="propertiesView-layout-content"  >{this.layoutPreview}</div> : null}
+                    </div>
+                </div>;
+            }
+            if (this.isPres) {
+                const selectedItem: boolean = PresBox.Instance?._selectedArray.length > 0;
+                return <div className="propertiesView" style={{ width: this.props.width }}>
+                    <div className="propertiesView-title" style={{ width: this.props.width }}>
+                        Presentation
+                    </div>
+                    <div className="propertiesView-name">
+                        {this.editableTitle}
+                        <div className="propertiesView-presSelected">
+                            {PresBox.Instance?._selectedArray.length} selected
+                            <div className="propertiesView-selectedList">
+                                {PresBox.Instance?.listOfSelected}
+                            </div>
+                        </div>
+                    </div>
+                    {!selectedItem ? (null) : <div className="propertiesView-presTrails">
+                        <div className="propertiesView-presTrails-title"
+                            onPointerDown={action(() => { this.openPresTransitions = !this.openPresTransitions; })}
+                            style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}>
+                            &nbsp; <FontAwesomeIcon icon={"rocket"} /> &nbsp; Transitions
+                        <div className="propertiesView-presTrails-title-icon">
+                                <FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" />
+                            </div>
+                        </div>
+                        {this.openPresTransitions ? <div className="propertiesView-presTrails-content">
+                            {PresBox.Instance.transitionDropdown}
+                        </div> : null}
+                    </div>}
+                    {!selectedItem ? (null) : <div className="propertiesView-presTrails">
+                        <div className="propertiesView-presTrails-title"
+                            onPointerDown={action(() => { this.openPresProgressivize = !this.openPresProgressivize; })}
+                            style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}>
+                            &nbsp; <FontAwesomeIcon icon={"tasks"} /> &nbsp; Progressivize
+                        <div className="propertiesView-presTrails-title-icon">
+                                <FontAwesomeIcon icon={this.openPresProgressivize ? "caret-down" : "caret-right"} size="lg" color="white" />
+                            </div>
+                        </div>
+                        {this.openPresProgressivize ? <div className="propertiesView-presTrails-content">
+                            {PresBox.Instance.progressivizeDropdown}
+                        </div> : null}
+                    </div>}
+                    {!selectedItem ? (null) : <div className="propertiesView-presTrails">
+                        <div className="propertiesView-presTrails-title"
+                            onPointerDown={action(() => { this.openSlideOptions = !this.openSlideOptions; })}
+                            style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}>
+                            &nbsp; <FontAwesomeIcon icon={"cog"} /> &nbsp; {PresBox.Instance.stringType} options
+                        <div className="propertiesView-presTrails-title-icon">
+                                <FontAwesomeIcon icon={this.openSlideOptions ? "caret-down" : "caret-right"} size="lg" color="white" />
+                            </div>
+                        </div>
+                        {this.openSlideOptions ? <div className="propertiesView-presTrails-content">
+                            {PresBox.Instance.optionsDropdown}
+                        </div> : null}
+                    </div>}
+                    <div className="propertiesView-presTrails">
+                        <div className="propertiesView-presTrails-title"
+                            onPointerDown={action(() => { this.openAddSlide = !this.openAddSlide; })}
+                            style={{ backgroundColor: this.openAddSlide ? "black" : "" }}>
+                            &nbsp; <FontAwesomeIcon icon={"plus"} /> &nbsp; Add new slide
+                        <div className="propertiesView-presTrails-title-icon">
+                                <FontAwesomeIcon icon={this.openAddSlide ? "caret-down" : "caret-right"} size="lg" color="white" />
+                            </div>
+                        </div>
+                        {this.openAddSlide ? <div className="propertiesView-presTrails-content">
+                            {PresBox.Instance.newDocumentDropdown}
+                        </div> : null}
+                    </div>
+                    {/* <div className="propertiesView-sharing">
+                        <div className="propertiesView-sharing-title"
+                            onPointerDown={acition(() => { this.openSharing = !this.openSharing; })}
+                            style={{ backgroundColor: this.openSharing ? "black" : "" }}>
+                            Sharing {"&"} Permissions
+                        <div className="propertiesView-sharing-title-icon">
+                                <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" />
+                            </div>
+                        </div>
+                        {this.openSharing ? <div className="propertiesView-sharing-content">
+                            {this.sharingTable}
+                        </div> : null}
+                    </div> */}
+                </div>;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 06cadcacf..8301d3df8 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -31,7 +31,7 @@ import "./CollectionDockingView.scss";
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
 import { CollectionViewType } from './CollectionView';
-import { DockingViewButtonSelector } from './ParentDocumentSelector';
+import { CollectionDockingViewMenu } from './CollectionDockingViewMenu';
 import React = require("react");
 const _global = (window /* browser */ || global /* node */) as any;
 
@@ -80,11 +80,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
 
     @undoBatch
     public CloseFullScreen = () => {
-        const target = this._goldenLayout._maximisedItem;
-        if (target) {
-            target.remove();
-            this.stateChanged();
-        }
+        this._goldenLayout._maximisedItem?.toggleMaximise();
+        this.stateChanged();
     }
 
     @undoBatch
@@ -453,7 +450,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
     }
     tabCreated = (tab: any) => {
-        (tab.contentItem.element[0]?.firstChild?.firstChild as any)?.InitTab(tab);  // have to explicitly initialize tabs that reuse contents from previous abs (ie, when dragging a tab around a new tab is created for the old content)
+        tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab(tab);  // have to explicitly initialize tabs that reuse contents from previous abs (ie, when dragging a tab around a new tab is created for the old content)
     }
 
     stackCreated = (stack: any) => {
@@ -563,7 +560,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 (view) => {
                     if (view) {
                         ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} >
-                            <DockingViewButtonSelector views={() => [view]} Stack={stack} />
+                            <CollectionDockingViewMenu views={() => [view]} Stack={stack} />
                         </span>,
                             gearSpan);
                         tab._disposers.buttonDisposer?.();
diff --git a/src/client/views/collections/CollectionDockingViewMenu.scss b/src/client/views/collections/CollectionDockingViewMenu.scss
new file mode 100644
index 000000000..4157f0f7e
--- /dev/null
+++ b/src/client/views/collections/CollectionDockingViewMenu.scss
@@ -0,0 +1,34 @@
+
+.dockingViewButtonSelector {
+    div {
+        overflow: visible !important;
+    }
+
+    display: inline-block;
+    width:100%;
+    height:100%;
+}
+.dockingViewButtonSelector-flyout {
+    position: relative;
+    z-index: 9999;
+    background-color: #eeeeee;
+    box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
+    color: black;
+
+    padding: 10px;
+    border-radius: 3px;
+    display: inline-block;
+    height: 100%;
+    width: 100%;
+    border-radius: 3px;
+
+    hr {
+        height: 1px;
+        margin: 0px;
+        background-color: gray;
+        border-top: 0px;
+        border-bottom: 0px;
+        border-right: 0px;
+        border-left: 0px;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingViewMenu.tsx b/src/client/views/collections/CollectionDockingViewMenu.tsx
new file mode 100644
index 000000000..4fe97452c
--- /dev/null
+++ b/src/client/views/collections/CollectionDockingViewMenu.tsx
@@ -0,0 +1,51 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from "@material-ui/core";
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import { DocumentButtonBar } from "../DocumentButtonBar";
+import { DocumentView } from "../nodes/DocumentView";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+@observer
+export class CollectionDockingViewMenu extends React.Component<{ views: () => DocumentView[], Stack: any }> {
+    customStylesheet(styles: any) {
+        return {
+            ...styles,
+            panel: {
+                ...styles.panel,
+                minWidth: "100px"
+            },
+        };
+    }
+    _ref = React.createRef<HTMLDivElement>();
+
+    @computed get flyout() {
+        return (
+            <div className="dockingViewButtonSelector-flyout" title=" " ref={this._ref}>
+                <DocumentButtonBar views={this.props.views} stack={this.props.Stack} />
+            </div>
+        );
+    }
+
+    @observable _tooltipOpen: boolean = false;
+    render() {
+        return <Tooltip open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} title={<><div className="dash-tooltip">Tap for toolbar, drag to create alias in another pane</div></>} placement="bottom">
+            <span className="dockingViewButtonSelector"
+                onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}
+                onPointerDown={action(e => {
+                    if (getComputedStyle(this._ref.current!).width !== "100%") {
+                        e.stopPropagation(); e.preventDefault();
+                    }
+                    this.props.views()[0]?.select(false);
+                    this._tooltipOpen = false;
+                })} >
+                <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
+                    <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} />
+                </Flyout>
+            </span>
+        </Tooltip >;
+    }
+}
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss
deleted file mode 100644
index bc9cf4848..000000000
--- a/src/client/views/collections/ParentDocumentSelector.scss
+++ /dev/null
@@ -1,63 +0,0 @@
-.parentDocumentSelector-linkFlyout {
-    div {
-        overflow: visible !important;
-    }
-
-    .metadataEntry-outerDiv {
-        overflow: hidden !important;
-        pointer-events: all;
-    }
-}
-
-.parentDocumentSelector-flyout {
-    position: relative;
-    z-index: 9999;
-    background-color: #eeeeee;
-    box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
-    color: black;
-
-    padding: 10px;
-    border-radius: 3px;
-    display: inline-block;
-    height: 100%;
-    width: 100%;
-    border-radius: 3px;
-
-    hr {
-        height: 1px;
-        margin: 0px;
-        background-color: gray;
-        border-top: 0px;
-        border-bottom: 0px;
-        border-right: 0px;
-        border-left: 0px;
-    }
-}
-
-.parentDocumentSelector-button {
-    pointer-events: all;
-    position: relative;
-    display: inline-block;
-
-    svg {
-        // width:20px !important;
-        //height:20px;
-    }
-}
-
-.parentDocumentSelector-metadata {
-    pointer-events: auto;
-    padding-right: 5px;
-    width: 25px;
-    display: inline-block;
-}
-
-.buttonSelector {
-    div {
-        overflow: visible !important;
-    }
-
-    display: inline-block;
-    width:100%;
-    height:100%;
-}
\ No newline at end of file
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
deleted file mode 100644
index f019ddfe6..000000000
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { computed, IReactionDisposer, observable, reaction, runInAction, action } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Doc } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { NumCast } from "../../../fields/Types";
-import { SearchUtil } from "../../util/SearchUtil";
-import { DocumentButtonBar } from "../DocumentButtonBar";
-import { DocumentView } from "../nodes/DocumentView";
-import { CollectionDockingView } from "./CollectionDockingView";
-import { CollectionViewType } from "./CollectionView";
-import './ParentDocumentSelector.scss';
-const higflyout = require("@hig/flyout");
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
-
-type SelectorProps = {
-    Document: Doc,
-    Stack?: any,
-    hideTitle?: boolean,
-    addDocTab(doc: Doc, location: string): void
-};
-
-@observer
-export class SelectorContextMenu extends React.Component<SelectorProps> {
-    @observable private _docs: { col: Doc, target: Doc }[] = [];
-    @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
-    _reaction: IReactionDisposer | undefined;
-
-    componentDidMount() {
-        this._reaction = reaction(() => this.props.Document, () => this.fetchDocuments(), { fireImmediately: true });
-    }
-    componentWillUnmount() {
-        this._reaction?.();
-    }
-    async fetchDocuments() {
-        const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
-        const containerProtoSets = await Promise.all(aliases.map(async alias => ((await SearchUtil.Search("", true, { fq: `data_l:"${alias[Id]}"` })).docs)));
-        const containerProtos = containerProtoSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
-        const containerSets = await Promise.all(Array.from(containerProtos.keys()).map(async container => SearchUtil.GetAliasesOfDocument(container)));
-        const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
-        const doclayoutSets = await Promise.all(Array.from(containers.keys()).map(async (dp) => SearchUtil.GetAliasesOfDocument(dp)));
-        const doclayouts = Array.from(doclayoutSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>()).keys());
-        runInAction(() => {
-            this._docs = doclayouts.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).filter(doc => !Doc.IsSystem(doc)).map(doc => ({ col: doc, target: this.props.Document }));
-            this._otherDocs = [];
-        });
-    }
-
-    getOnClick({ col, target }: { col: Doc, target: Doc }) {
-        return () => {
-            col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
-            if (col._viewType === 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, "inTab"); // bcz: dataDoc?
-        };
-    }
-
-    render() {
-        return <div >
-            {this.props.hideTitle ? (null) : <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" + doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title?.toString()}</a></p>)}
-        </div>;
-    }
-}
-@observer
-export class DockingViewButtonSelector extends React.Component<{ views: () => DocumentView[], Stack: any }> {
-    customStylesheet(styles: any) {
-        return {
-            ...styles,
-            panel: {
-                ...styles.panel,
-                minWidth: "100px"
-            },
-        };
-    }
-    _ref = React.createRef<HTMLDivElement>();
-
-    @computed get flyout() {
-        return (
-            <div className="ParentDocumentSelector-flyout" title=" " ref={this._ref}>
-                <DocumentButtonBar views={this.props.views} stack={this.props.Stack} />
-            </div>
-        );
-    }
-
-    @observable _tooltipOpen: boolean = false;
-    render() {
-        return <Tooltip open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} title={<><div className="dash-tooltip">Tap for toolbar, drag to create alias in another pane</div></>} placement="bottom">
-            <span className="buttonSelector"
-                onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}
-                onPointerDown={action(e => {
-                    if (getComputedStyle(this._ref.current!).width !== "100%") {
-                        e.stopPropagation(); e.preventDefault();
-                    }
-                    this.props.views()[0]?.select(false);
-                    this._tooltipOpen = false;
-                })} >
-                <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
-                    <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} />
-                </Flyout>
-            </span>
-        </Tooltip >;
-    }
-}
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
deleted file mode 100644
index d49ab27fb..000000000
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
+++ /dev/null
@@ -1,68 +0,0 @@
-.antimodeMenu-button {
-    width: 200px;
-    position: relative;
-    text-align: left;
-
-    .color-previewI {
-        width: 100%;
-        height: 40%;
-    }
-
-    .color-previewII {
-        width: 100%;
-        height: 100%;
-    }
-}
-
-.antimenu-Buttonup {
-    position: absolute;
-    width: 20;
-    height: 10;
-    right: 0;
-    padding: 0;
-}
-
-.formatShapePane-inputBtn {
-    width: inherit;
-    position: absolute;
-}
-
-.btn-group-palette {
-    .sketch-picker {
-        background: #323232;
-        width: 160px !important;
-        height: 80% !important;
-
-        .flexbox-fit {
-            background: #323232;
-        }
-    }
-}
-
-.btn-group {
-    display: grid;
-    grid-template-columns: auto auto auto auto;
-    /* Make the buttons appear below each other */
-}
-
-.btn-group-palette {
-    display: block;
-    /* Make the buttons appear below each other */
-}
-
-.btn-draw {
-    display: inline;
-    /* Make the buttons appear below each other */
-}
-
-.btn2-group {
-    display: block;
-    background: #323232;
-    grid-template-columns: auto;
-
-    /* Make the buttons appear below each other */
-    .antimodeMenu-button {
-        background: #323232;
-        display: block;
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
deleted file mode 100644
index e6ec274f0..000000000
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
+++ /dev/null
@@ -1,545 +0,0 @@
-import React = require("react");
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Field, Opt } from "../../../../fields/Doc";
-import { Document } from "../../../../fields/documentSchemas";
-import { InkField } from "../../../../fields/InkField";
-import { BoolCast, Cast, NumCast } from "../../../../fields/Types";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
-import "./FormatShapePane.scss";
-import { undoBatch } from "../../../util/UndoManager";
-import { ColorState, SketchPicker } from 'react-color';
-
-@observer
-export class FormatShapePane extends AntimodeMenu<AntimodeMenuProps> {
-    static Instance: FormatShapePane;
-
-    private _lastFill = "#D0021B";
-    private _lastLine = "#D0021B";
-    private _lastDash = "2";
-    private _mode = ["fill-drip", "ruler-combined"];
-
-    @observable private _subOpen = [false, false];
-    @observable private _currMode = "fill-drip";
-    @observable _lock = false;
-    @observable private _fillBtn = false;
-    @observable private _lineBtn = false;
-    @observable _controlBtn = false;
-    @observable private _controlPoints: { X: number, Y: number }[] = [];
-    @observable _currPoint = -1;
-
-    getField(key: string) {
-        return this.selectedInk?.reduce((p, i) =>
-            (p === undefined || (p && p === i.rootDoc[key])) && i.rootDoc[key] !== "0" ? Field.toString(i.rootDoc[key] as Field) : "", undefined as Opt<string>);
-    }
-
-    @computed get selectedInk() {
-        const inks = SelectionManager.SelectedDocuments().filter(i => Document(i.rootDoc).type === DocumentType.INK);
-        return inks.length ? inks : undefined;
-    }
-    @computed get unFilled() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; }
-    @computed get unStrokd() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; }
-    @computed get solidFil() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; }
-    @computed get solidStk() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; }
-    @computed get dashdStk() { return !this.unStrokd && this.getField("strokeDash") || ""; }
-    @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
-    @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
-    @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
-    @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
-    @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
-    @computed get shapeHgt() { return this.getField("_height"); }
-    @computed get shapeWid() { return this.getField("_width"); }
-    @computed get shapeXps() { return this.getField("x"); }
-    @computed get shapeYps() { return this.getField("y"); }
-    @computed get shapeRot() { return this.getField("rotation"); }
-    set unFilled(value) { this.colorFil = value ? "" : this._lastFill; }
-    set solidFil(value) { this.unFilled = !value; }
-    set colorFil(value) { value && (this._lastFill = value); this.selectedInk?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); }
-    set colorStk(value) { value && (this._lastLine = value); this.selectedInk?.forEach(i => i.rootDoc.color = value ? value : undefined); }
-    set markHead(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeStartMarker = value); }
-    set markTail(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeEndMarker = value); }
-    set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
-    set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
-    set dashdStk(value) {
-        value && (this._lastDash = value) && (this.unStrokd = false);
-        this.selectedInk?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined);
-    }
-    set shapeXps(value) { this.selectedInk?.forEach(i => i.rootDoc.x = Number(value)); }
-    set shapeYps(value) { this.selectedInk?.forEach(i => i.rootDoc.y = Number(value)); }
-    set shapeRot(value) { this.selectedInk?.forEach(i => i.rootDoc.rotation = Number(value)); }
-    set widthStk(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = Number(value)); }
-    set shapeWid(value) {
-        this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
-            const oldWidth = NumCast(i.rootDoc._width);
-            i.rootDoc._width = Number(value);
-            this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth);
-        });
-    }
-    set shapeHgt(value) {
-        this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
-            const oldHeight = NumCast(i.rootDoc._height);
-            i.rootDoc._height = Number(value);
-            this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight);
-        });
-    }
-
-    constructor(props: Readonly<{}>) {
-        super(props);
-        FormatShapePane.Instance = this;
-        this._canFade = false;
-        runInAction(() => this.Pinned = BoolCast(Doc.UserDoc()["menuFormatShape-pinned"]));
-    }
-
-    @action
-    closePane = () => {
-        this.fadeOut(false);
-        this.Pinned = false;
-    }
-
-    @action
-    upDownButtons = (dirs: string, field: string) => {
-        switch (field) {
-            case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break;
-            // case "rot": this.selectedInk?.forEach(i => i.rootDoc.rotation = NumCast(i.rootDoc.rotation) + (dirs === "up" ? 0.1 : -0.1)); break;
-            case "Xps": this.selectedInk?.forEach(i => i.rootDoc.x = NumCast(i.rootDoc.x) + (dirs === "up" ? 10 : -10)); break;
-            case "Yps": this.selectedInk?.forEach(i => i.rootDoc.y = NumCast(i.rootDoc.y) + (dirs === "up" ? 10 : -10)); break;
-            case "stk": this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = NumCast(i.rootDoc.strokeWidth) + (dirs === "up" ? .1 : -.1)); break;
-            case "wid": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
-                //redraw points
-                const oldWidth = NumCast(i.rootDoc._width);
-                const oldHeight = NumCast(i.rootDoc._height);
-                const oldX = NumCast(i.rootDoc.x);
-                const oldY = NumCast(i.rootDoc.y);
-                i.rootDoc._width = oldWidth + (dirs === "up" ? 10 : - 10);
-                this._lock && (i.rootDoc._height = (i.rootDoc._width / oldWidth * NumCast(i.rootDoc._height)));
-                const doc = Document(i.rootDoc);
-                if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
-                    const ink = Cast(doc.data, InkField)?.inkData;
-                    if (ink) {
-                        const newPoints: { X: number, Y: number }[] = [];
-                        ink.forEach(i => {
-                            // (new x — oldx) + (oldxpoint * newWidt)/oldWidth 
-                            const newX = ((doc.x || 0) - oldX) + (i.X * (doc._width || 0)) / oldWidth;
-                            const newY = ((doc.y || 0) - oldY) + (i.Y * (doc._height || 0)) / oldHeight;
-                            newPoints.push({ X: newX, Y: newY });
-                        });
-                        Doc.GetProto(doc).data = new InkField(newPoints);
-                    }
-                }
-            });
-                break;
-            case "hgt": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
-                const oldWidth = NumCast(i.rootDoc._width);
-                const oldHeight = NumCast(i.rootDoc._height);
-                const oldX = NumCast(i.rootDoc.x);
-                const oldY = NumCast(i.rootDoc.y); i.rootDoc._height = oldHeight + (dirs === "up" ? 10 : - 10);
-                this._lock && (i.rootDoc._width = (i.rootDoc._height / oldHeight * NumCast(i.rootDoc._width)));
-                const doc = Document(i.rootDoc);
-                if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
-                    const ink = Cast(doc.data, InkField)?.inkData;
-                    if (ink) {
-                        const newPoints: { X: number, Y: number }[] = [];
-                        ink.forEach(i => {
-                            // (new x — oldx) + (oldxpoint * newWidt)/oldWidth 
-                            const newX = ((doc.x || 0) - oldX) + (i.X * (doc._width || 0)) / oldWidth;
-                            const newY = ((doc.y || 0) - oldY) + (i.Y * (doc._height || 0)) / oldHeight;
-                            newPoints.push({ X: newX, Y: newY });
-                        });
-                        Doc.GetProto(doc).data = new InkField(newPoints);
-                    }
-                }
-            });
-                break;
-        }
-    }
-
-    @undoBatch
-    @action
-    addPoints = (x: number, y: number, pts: { X: number, Y: number }[], index: number, control: { X: number, Y: number }[]) => {
-        this.selectedInk?.forEach(action(inkView => {
-            if (this.selectedInk?.length === 1) {
-                const doc = Document(inkView.rootDoc);
-                if (doc.type === DocumentType.INK) {
-                    const ink = Cast(doc.data, InkField)?.inkData;
-                    if (ink) {
-                        const newPoints: { X: number, Y: number }[] = [];
-                        var counter = 0;
-                        for (var k = 0; k < index; k++) {
-                            control.forEach(pt => (pts[k].X === pt.X && pts[k].Y === pt.Y) && counter++);
-                        }
-                        //decide where to put the new coordinate
-                        const spNum = Math.floor(counter / 2) * 4 + 2;
-
-                        for (var i = 0; i < spNum; i++) {
-                            ink[i] && newPoints.push({ X: ink[i].X, Y: ink[i].Y });
-                        }
-                        for (var j = 0; j < 4; j++) {
-                            newPoints.push({ X: x, Y: y });
-
-                        }
-                        for (var i = spNum; i < ink.length; i++) {
-                            newPoints.push({ X: ink[i].X, Y: ink[i].Y });
-                        }
-                        this._currPoint = -1;
-                        Doc.GetProto(doc).data = new InkField(newPoints);
-                    }
-                }
-            }
-        }));
-    }
-
-    @undoBatch
-    @action
-    deletePoints = () => {
-        this.selectedInk?.forEach(action(inkView => {
-            if (this.selectedInk?.length === 1 && this._currPoint !== -1) {
-                const doc = Document(inkView.rootDoc);
-                if (doc.type === DocumentType.INK) {
-                    const ink = Cast(doc.data, InkField)?.inkData;
-                    if (ink && ink.length > 4) {
-                        const newPoints: { X: number, Y: number }[] = [];
-                        const toRemove = Math.floor(((this._currPoint + 2) / 4));
-                        for (var i = 0; i < ink.length; i++) {
-                            if (Math.floor((i + 2) / 4) !== toRemove) {
-                                newPoints.push({ X: ink[i].X, Y: ink[i].Y });
-                            }
-                        }
-                        this._currPoint = -1;
-                        Doc.GetProto(doc).data = new InkField(newPoints);
-                        if (newPoints.length === 4) {
-                            const newerPoints: { X: number, Y: number }[] = [];
-                            newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y });
-                            newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y });
-                            newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y });
-                            newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y });
-                            Doc.GetProto(doc).data = new InkField(newerPoints);
-
-                        }
-                    }
-                }
-            }
-        }));
-    }
-
-    @undoBatch
-    @action
-    rotate = (angle: number) => {
-        const _centerPoints: { X: number, Y: number }[] = [];
-        SelectionManager.SelectedDocuments().forEach(action(inkView => {
-            const doc = Document(inkView.rootDoc);
-            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
-                const ink = Cast(doc.data, InkField)?.inkData;
-                if (ink) {
-                    const xs = ink.map(p => p.X);
-                    const ys = ink.map(p => p.Y);
-                    const left = Math.min(...xs);
-                    const top = Math.min(...ys);
-                    const right = Math.max(...xs);
-                    const bottom = Math.max(...ys);
-                    _centerPoints.push({ X: left, Y: top });
-                }
-            }
-        }));
-
-        var index = 0;
-        SelectionManager.SelectedDocuments().forEach(action(inkView => {
-            const doc = Document(inkView.rootDoc);
-            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
-                doc.rotation = Number(doc.rotation) + Number(angle);
-                const ink = Cast(doc.data, InkField)?.inkData;
-                if (ink) {
-
-                    const newPoints: { X: number, Y: number }[] = [];
-                    ink.forEach(i => {
-                        const newX = Math.cos(angle) * (i.X - _centerPoints[index].X) - Math.sin(angle) * (i.Y - _centerPoints[index].Y) + _centerPoints[index].X;
-                        const newY = Math.sin(angle) * (i.X - _centerPoints[index].X) + Math.cos(angle) * (i.Y - _centerPoints[index].Y) + _centerPoints[index].Y;
-                        newPoints.push({ X: newX, Y: newY });
-                    });
-                    Doc.GetProto(doc).data = new InkField(newPoints);
-                    const xs = newPoints.map(p => p.X);
-                    const ys = newPoints.map(p => p.Y);
-                    const left = Math.min(...xs);
-                    const top = Math.min(...ys);
-                    const right = Math.max(...xs);
-                    const bottom = Math.max(...ys);
-
-                    doc._height = (bottom - top);
-                    doc._width = (right - left);
-                }
-                index++;
-            }
-        }));
-    }
-
-    @undoBatch
-    @action
-    control = (xDiff: number, yDiff: number, controlNum: number) => {
-        this.selectedInk?.forEach(action(inkView => {
-            if (this.selectedInk?.length === 1) {
-                const doc = Document(inkView.rootDoc);
-                if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
-                    const ink = Cast(doc.data, InkField)?.inkData;
-                    if (ink) {
-
-                        const newPoints: { X: number, Y: number }[] = [];
-                        const order = controlNum % 4;
-                        for (var i = 0; i < ink.length; i++) {
-                            if (controlNum === i ||
-                                (order === 0 && i === controlNum + 1) ||
-                                (order === 0 && controlNum !== 0 && i === controlNum - 2) ||
-                                (order === 0 && controlNum !== 0 && i === controlNum - 1) ||
-                                (order === 3 && i === controlNum - 1) ||
-                                (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 1) ||
-                                (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 2)
-                                || ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlNum === 0 || controlNum === ink.length - 1))
-                            ) {
-                                newPoints.push({ X: ink[i].X - (xDiff * inkView.props.ScreenToLocalTransform().Scale), Y: ink[i].Y - (yDiff * inkView.props.ScreenToLocalTransform().Scale) });
-                            }
-                            else {
-                                newPoints.push({ X: ink[i].X, Y: ink[i].Y });
-                            }
-                        }
-                        const oldx = doc.x;
-                        const oldy = doc.y;
-                        const xs = ink.map(p => p.X);
-                        const ys = ink.map(p => p.Y);
-                        const left = Math.min(...xs);
-                        const top = Math.min(...ys);
-                        Doc.GetProto(doc).data = new InkField(newPoints);
-                        const xs2 = newPoints.map(p => p.X);
-                        const ys2 = newPoints.map(p => p.Y);
-                        const left2 = Math.min(...xs2);
-                        const top2 = Math.min(...ys2);
-                        const right2 = Math.max(...xs2);
-                        const bottom2 = Math.max(...ys2);
-                        doc._height = (bottom2 - top2);
-                        doc._width = (right2 - left2);
-                        //if points move out of bounds
-
-                        doc.x = oldx - (left - left2);
-                        doc.y = oldy - (top - top2);
-
-                    }
-                }
-            }
-        }));
-    }
-
-    @undoBatch
-    @action
-    switchStk = (color: ColorState) => {
-        const val = String(color.hex);
-        this.colorStk = val;
-        return true;
-    }
-
-    @undoBatch
-    @action
-    switchFil = (color: ColorState) => {
-        const val = String(color.hex);
-        this.colorFil = val;
-        return true;
-    }
-
-
-    colorPicker(setter: (color: string) => {}, type: string) {
-        return <div className="btn-group-palette" key="colorpicker" style={{ width: 160, margin: 10 }}>
-            <SketchPicker onChange={type === "stk" ? this.switchStk : this.switchFil} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
-                color={type === "stk" ? this.colorStk : this.colorFil} />
-        </div>;
-    }
-    inputBox = (key: string, value: any, setter: (val: string) => {}) => {
-        return <>
-            <input style={{ color: "black", width: 40, position: "absolute", right: 20 }}
-                type="text" defaultValue={value}
-                onChange={undoBatch(action((e) => setter(e.target.value)))}
-                autoFocus />
-            <button className="antiMenu-Buttonup" key="up1" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))}>
-                ˄
-            </button>
-            <br />
-            <button className="antiMenu-Buttonup" key="down1" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: -8 }}>
-                ˅
-            </button>
-        </>;
-    }
-
-    inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => {
-        return <>
-            {title1}
-            <p style={{ marginTop: -20, right: 70, position: "absolute" }}>{title2}</p>
-
-            <input style={{ color: "black", width: 40, position: "absolute", right: 130 }}
-                type="text" defaultValue={value}
-                onChange={e => setter(e.target.value)}
-                autoFocus />
-            <button className="antiMenu-Buttonup" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} style={{ right: 110 }}>
-                ˄
-        </button>
-            <button className="antiMenu-Buttonup" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: 12, right: 110 }}>
-                ˅
-        </button>
-            {title2 === "" ? "" : <>
-                <input style={{ color: "black", width: 40, position: "absolute", right: 20 }}
-                    type="text" defaultValue={value2}
-                    onChange={e => setter2(e.target.value)}
-                    autoFocus />
-                <button className="antiMenu-Buttonup" key="up3" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key2)))}>
-                    ˄
-      </button>
-                <br />
-                <button className="antiMenu-Buttonup" key="down3" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key2)))} style={{ marginTop: -8 }}>
-                    ˅
-      </button></>}
-        </>;
-    }
-
-
-    colorButton(value: string, setter: () => {}) {
-        return <>
-            <button className="antimodeMenu-button" key="color" onPointerDown={undoBatch(action(e => setter()))} style={{ position: "relative", marginTop: -5 }}>
-                <div className="color-previewII" style={{ backgroundColor: value ?? "121212" }} />
-                {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -23, position: "fixed" }}>☒</p> : ""}
-            </button>
-        </>;
-    }
-
-    controlPointsButton() {
-        return <>
-            <button className="antimodeMenu-button" title="Edit points" key="bezier" onPointerDown={action(() => this._controlBtn = this._controlBtn ? false : true)} style={{ position: "relative", marginTop: 10, backgroundColor: this._controlBtn ? "black" : "" }}>
-                <FontAwesomeIcon icon="bezier-curve" size="lg" />
-            </button>
-            <button className="antimodeMenu-button" title="Lock ratio" key="ratio" onPointerDown={action(() => this._lock = this._lock ? false : true)} style={{ position: "relative", marginTop: 10, backgroundColor: this._lock ? "black" : "" }}>
-                <FontAwesomeIcon icon="lock" size="lg" />
-
-            </button>
-            <button className="antimodeMenu-button" key="rotate" title="Rotate 90˚" onPointerDown={action(() => this.rotate(Math.PI / 2))} style={{ position: "relative", marginTop: 10, fontSize: 15 }}>
-                ⟲
-            </button>
-            <br /> <br />
-        </>;
-    }
-
-    lockRatioButton() {
-        return <>
-            <button className="antimodeMenu-button" key="lock" onPointerDown={action(() => this._lock = this._lock ? false : true)} style={{ position: "absolute", right: 80, backgroundColor: this._lock ? "black" : "" }}>
-                {/* <FontAwesomeIcon icon="bezier-curve" size="lg" /> */}
-                <FontAwesomeIcon icon="lock" size="lg" />
-
-            </button>
-            <br /> <br />
-        </>;
-    }
-
-    rotate90Button() {
-        return <>
-            <button className="antimodeMenu-button" key="rot" onPointerDown={action(() => this.rotate(Math.PI / 2))} style={{ position: "absolute", right: 80, }}>
-                {/* <FontAwesomeIcon icon="bezier-curve" size="lg" /> */}
-                ⟲
-            </button>
-            <br /> <br />
-        </>;
-    }
-    @computed get fillButton() { return this.colorButton(this.colorFil, () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); }
-    @computed get lineButton() { return this.colorButton(this.colorStk, () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); }
-
-    @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); }
-    @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); }
-
-    @computed get stkInput() { return this.inputBox("stk", this.widthStk, (val: string) => this.widthStk = val); }
-    @computed get dashInput() { return this.inputBox("dsh", this.widthStk, (val: string) => this.widthStk = val); }
-
-    @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => this.shapeHgt = val, "H:", "wid", this.shapeWid, (val: string) => this.shapeWid = val, "W:"); }
-    @computed get widInput() { return this.inputBox("wid", this.shapeWid, (val: string) => this.shapeWid = val); }
-    @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; return true; }, "∠:", "rot", this.shapeRot, (val: string) => this.shapeRot = val, ""); }
-
-    @computed get YpsInput() { return this.inputBox("Yps", this.shapeYps, (val: string) => this.shapeYps = val); }
-
-    @computed get controlPoints() { return this.controlPointsButton(); }
-    @computed get lockRatio() { return this.lockRatioButton(); }
-    @computed get rotate90() { return this.rotate90Button(); }
-    @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => this.shapeXps = val, "X:", "Yps", this.shapeYps, (val: string) => this.shapeYps = val, "Y:"); }
-
-
-    @computed get propertyGroupItems() {
-        const fillCheck = <div key="fill" style={{ display: (this._subOpen[0] && this.selectedInk && this.selectedInk.length >= 1) ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
-            Fill:
-            {this.fillButton}
-            <div style={{ float: "left", width: 100 }} >
-                Stroke:
-            {this.lineButton}
-            </div>
-
-            {this._fillBtn ? this.fillPicker : ""}
-            {this._lineBtn ? this.linePicker : ""}
-            {this._fillBtn || this._lineBtn ? "" : <br />}
-            {(this.solidStk || this.dashdStk) ? "Width" : ""}
-            {(this.solidStk || this.dashdStk) ? this.stkInput : ""}
-            {(this.solidStk || this.dashdStk) ? <input type="range" defaultValue={Number(this.widthStk)} min={1} max={100} onChange={undoBatch(action((e) => this.widthStk = e.target.value))} /> : (null)}
-            <br />
-            {(this.solidStk || this.dashdStk) ? <>
-                <p style={{ position: "absolute", fontSize: 12 }}>Arrow Head</p>
-                <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} style={{ position: "absolute", right: 110, width: 20 }} />
-                <p style={{ position: "absolute", fontSize: 12, right: 30 }}>Arrow End</p>
-                <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} style={{ position: "absolute", right: 0, width: 20 }} />
-                <br />
-            </> : ""}
-            Dash:
-            <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.dashdStk === "2"} onChange={undoBatch(action(() => this.dashdStk = this.dashdStk === "2" ? "0" : "2"))} style={{ position: "absolute", right: 110, width: 20 }} />
-        </div>;
-
-
-
-        const sizeCheck =
-
-            <div key="sizeCheck" style={{ display: (this._subOpen[1] && this.selectedInk && this.selectedInk.length >= 1) ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
-                {this.controlPoints}
-                {this.hgtInput}
-                {this.XpsInput}
-                {this.rotInput}
-
-            </div>;
-
-
-        const subMenus = this._currMode === "fill-drip" ? [`Appearance`, 'Transform'] : [];
-        const menuItems = this._currMode === "fill-drip" ? [fillCheck, sizeCheck] : [];
-        const indexOffset = 0;
-
-        return <div className="antimodeMenu-sub" key="submenu" style={{ position: "absolute", width: "inherit", top: 60 }}>
-            {subMenus.map((subMenu, i) =>
-                <div key={subMenu} style={{ width: "inherit" }}>
-                    <button className="antimodeMenu-button" onPointerDown={action(() => this._subOpen[i + indexOffset] = !this._subOpen[i + indexOffset])}
-                        style={{ backgroundColor: "121212", position: "relative", width: "inherit" }}>
-                        {this._subOpen[i + indexOffset] ? "▼" : "▶︎"}
-                        {subMenu}
-                    </button>
-                    {menuItems[i]}
-                </div>)}
-        </div>;
-    }
-
-    @computed get closeBtn() {
-        return <button className="antimodeMenu-button" key="close" onPointerDown={action(() => this.closePane())} style={{ position: "absolute", right: 0 }}>
-            X
-        </button>;
-    }
-
-    @computed get propertyGroupBtn() {
-        return <div className="antimodeMenu-button-tab" key="modes">
-            {this._mode.map(mode =>
-                <button className="antimodeMenu-button" key={mode} onPointerDown={action(() => this._currMode = mode)}
-                    style={{ backgroundColor: this._currMode === mode ? "121212" : "", position: "relative", top: 30 }}>
-                    <FontAwesomeIcon icon={mode as IconProp} size="lg" />
-                </button>)}
-        </div>;
-    }
-
-    render() {
-        return this.getElementVert([this.closeBtn,
-        this.propertyGroupItems]);
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/collections/collectionFreeForm/PropertiesView.scss
deleted file mode 100644
index 254afeb0a..000000000
--- a/src/client/views/collections/collectionFreeForm/PropertiesView.scss
+++ /dev/null
@@ -1,779 +0,0 @@
-.propertiesView {
-
-    background-color: rgb(205, 205, 205);
-    height: 100%;
-    font-family: "Noto Sans";
-    cursor: auto;
-
-    overflow-x: hidden;
-    overflow-y: scroll;
-
-    .propertiesView-title {
-        background-color: rgb(159, 159, 159);
-        text-align: center;
-        padding-top: 12px;
-        padding-bottom: 12px;
-        display: flex;
-        font-size: 18px;
-        font-weight: bold;
-        justify-content: center;
-
-        .propertiesView-title-icon {
-            width: 20px;
-            height: 20px;
-            padding-left: 38px;
-            margin-top: -5px;
-            align-items: flex-end;
-            margin-left: auto;
-            margin-right: 10px;
-
-            &:hover {
-                color: grey;
-                cursor: pointer;
-            }
-
-        }
-
-    }
-
-    .propertiesView-name {
-        border-bottom: 1px solid black;
-        padding: 8.5px;
-        font-size: 12.5px;
-
-        &:hover {
-            cursor: text;
-        }
-    }
-
-    .propertiesView-settings {
-        border-bottom: 1px solid black;
-        //padding: 8.5px;
-        font-size: 12.5px;
-        font-weight: bold;
-
-        .propertiesView-settings-title {
-            font-weight: bold;
-            font-size: 12.5px;
-            padding: 4px;
-            display: flex;
-            color: white;
-            padding-left: 8px;
-            background-color: rgb(51, 51, 51);
-
-            &:hover {
-                cursor: pointer;
-            }
-
-            .propertiesView-settings-title-icon {
-                float: right;
-                justify-items: right;
-                align-items: flex-end;
-                margin-left: auto;
-                margin-right: 9px;
-
-                &:hover {
-                    cursor: pointer;
-                }
-            }
-        }
-
-        .propertiesView-settings-content {
-            margin-left: 12px;
-            padding-bottom: 10px;
-            padding-top: 8px;
-        }
-
-    }
-
-    .propertiesView-sharing {
-        border-bottom: 1px solid black;
-        //padding: 8.5px;
-
-        .propertiesView-sharing-title {
-            font-weight: bold;
-            font-size: 12.5px;
-            padding: 4px;
-            display: flex;
-            color: white;
-            padding-left: 8px;
-            background-color: rgb(51, 51, 51);
-
-            &:hover {
-                cursor: pointer;
-            }
-
-            .propertiesView-sharing-title-icon {
-                float: right;
-                justify-items: right;
-                align-items: flex-end;
-                margin-left: auto;
-                margin-right: 9px;
-
-                &:hover {
-                    cursor: pointer;
-                }
-            }
-        }
-
-        .propertiesView-sharing-content {
-            font-size: 10px;
-            padding: 10px;
-            margin-left: 5px;
-
-            .propertiesView-acls-checkbox {
-                float: right;
-                height: 20px;
-                margin-top: -20px;
-                margin-right: -15;
-
-                .propertiesView-acls-checkbox-text {
-                    font-size: 7px;
-                    margin-top: -10px;
-                    margin-left: 6px;
-                }
-            }
-
-            .change-buttons {
-                display: flex;
-
-                button {
-                    width: 5;
-                    height: 5;
-                }
-
-                input {
-                    width: 100%;
-                }
-            }
-        }
-    }
-
-    .propertiesView-appearance {
-        border-bottom: 1px solid black;
-        //padding: 8.5px;
-
-        .propertiesView-appearance-title {
-            font-weight: bold;
-            font-size: 12.5px;
-            padding: 4px;
-            display: flex;
-            color: white;
-            padding-left: 8px;
-            background-color: rgb(51, 51, 51);
-
-            &:hover {
-                cursor: pointer;
-            }
-
-            .propertiesView-appearance-title-icon {
-                float: right;
-                justify-items: right;
-                align-items: flex-end;
-                margin-left: auto;
-                margin-right: 9px;
-
-                &:hover {
-                    cursor: pointer;
-                }
-            }
-        }
-
-        .propertiesView-appearance-content {
-            font-size: 10px;
-            padding: 10px;
-            margin-left: 5px;
-        }
-    }
-
-    .propertiesView-transform {
-        border-bottom: 1px solid black;
-        //padding: 8.5px;
-
-        .propertiesView-transform-title {
-            font-weight: bold;
-            font-size: 12.5px;
-            padding: 4px;
-            display: flex;
-            color: white;
-            padding-left: 8px;
-            background-color: rgb(51, 51, 51);
-
-            &:hover {
-                cursor: pointer;
-            }
-
-            .propertiesView-transform-title-icon {
-                float: right;
-                justify-items: right;
-                align-items: flex-end;
-                margin-left: auto;
-                margin-right: 9px;
-
-                &:hover {
-                    cursor: pointer;
-                }
-            }
-        }
-
-        .propertiesView-transform-content {
-            font-size: 10px;
-            padding: 10px;
-            margin-left: 5px;
-        }
-    }
-
-    .notify-button {
-        padding: 2px;
-        width: 12px;
-        height: 12px;
-        background-color: black;
-        border-radius: 10px;
-        padding-left: 2px;
-        padding-right: 2px;
-        margin-top: 2px;
-        margin-left: 3px;
-
-        .notify-button-icon {
-            width: 6px;
-            height: 6.5px;
-            margin-left: .5px;
-        }
-
-        &:hover {
-            background-color: rgb(158, 158, 158);
-            cursor: pointer;
-        }
-    }
-
-    .expansion-button-icon {
-        width: 11px;
-        height: 11px;
-        color: black;
-        margin-left: 27px;
-
-        &:hover {
-            color: rgb(131, 131, 131);
-            cursor: pointer;
-        }
-    }
-
-    .propertiesView-sharingTable {
-
-        // whatever's commented out - add it back in when adding the buttons
-
-        // border: 1.5px solid black;
-        border: 1px solid black;
-        padding: 5px; // remove when adding buttons
-        border-radius: 6px; // remove when adding buttons
-        margin-right: 10px; // remove when adding buttons
-        // width: 100%;
-        // display: inline-table;
-        background-color: #ececec;
-        max-height: 130px;
-        overflow-y: scroll;
-        width: 92%;
-
-        .propertiesView-sharingTable-item {
-
-            display: flex;
-            // padding: 5px;
-            padding: 3px;
-            align-items: center;
-            border-bottom: 0.5px solid grey;
-
-            &:hover .propertiesView-sharingTable-item-name {
-                overflow-x: unset;
-                white-space: unset;
-                overflow-wrap: break-word;
-            }
-
-            .propertiesView-sharingTable-item-name {
-                font-weight: bold;
-                width: 95px;
-                overflow-x: hidden;
-                display: inline-block;
-                text-overflow: ellipsis;
-                white-space: nowrap;
-            }
-
-            .propertiesView-sharingTable-item-permission {
-                display: flex;
-                align-items: flex-end;
-                margin-left: auto;
-
-                .permissions-select {
-                    border: none;
-                    background-color: inherit;
-                    width: 75px;
-                    //text-align: justify; // for Edge
-                    //text-align-last: end;
-
-                    &:hover {
-                        cursor: pointer;
-                    }
-                }
-            }
-
-            &:last-child {
-                border-bottom: none;
-            }
-        }
-    }
-
-    .propertiesView-fields {
-        border-bottom: 1px solid black;
-        //padding: 8.5px;
-
-        .propertiesView-fields-title {
-            font-weight: bold;
-            font-size: 12.5px;
-            padding: 4px;
-            display: flex;
-            color: white;
-            padding-left: 8px;
-            background-color: rgb(51, 51, 51);
-
-            &:hover {
-                cursor: pointer;
-            }
-
-            .propertiesView-fields-title-icon {
-                float: right;
-                justify-items: right;
-                align-items: flex-end;
-                margin-left: auto;
-                margin-right: 9px;
-
-                &:hover {
-                    cursor: pointer;
-                }
-            }
-
-        }
-
-        .propertiesView-fields-checkbox {
-            float: right;
-            height: 20px;
-            margin-top: -9px;
-
-            .propertiesView-fields-checkbox-text {
-                font-size: 7px;
-                margin-top: -10px;
-                margin-left: 6px;
-            }
-        }
-
-        .propertiesView-fields-content {
-            font-size: 10px;
-            margin-left: 2px;
-            padding: 10px;
-
-            &:hover {
-                cursor: pointer;
-            }
-        }
-    }
-
-    .field {
-        display: flex;
-        font-size: 7px;
-        background-color: #e8e8e8;
-        padding-right: 3px;
-        border: 0.5px solid grey;
-        border-radius: 5px;
-        padding-left: 3px;
-    }
-
-    .uneditable-field {
-        display: flex;
-        overflow-y: visible;
-        margin-bottom: 2px;
-
-        &:hover {
-            cursor: auto;
-        }
-    }
-    .propertiesView-contexts {
-
-        .propertiesView-contexts-title {
-            font-weight: bold;
-            font-size: 12.5px;
-            padding: 4px;
-            display: flex;
-            color: white;
-            padding-left: 8px;
-            background-color: rgb(51, 51, 51);
-
-            &:hover {
-                cursor: pointer;
-            }
-
-            .propertiesView-contexts-title-icon {
-                float: right;
-                justify-items: right;
-                align-items: flex-end;
-                margin-left: auto;
-                margin-right: 9px;
-
-                &:hover {
-                    cursor: pointer;
-                }
-            }
-        }
-
-        .propertiesView-contexts-content {
-            overflow: hidden;
-            padding: 10px;
-        }
-
-    }
-
-    .propertiesView-layout {
-
-        .propertiesView-layout-title {
-            font-weight: bold;
-            font-size: 12.5px;
-            padding: 4px;
-            display: flex;
-            color: white;
-            padding-left: 8px;
-            background-color: rgb(51, 51, 51);
-
-            &:hover {
-                cursor: pointer;
-            }
-
-            .propertiesView-layout-title-icon {
-                float: right;
-                justify-items: right;
-                align-items: flex-end;
-                margin-left: auto;
-                margin-right: 9px;
-
-                &:hover {
-                    cursor: pointer;
-                }
-            }
-        }
-
-        .propertiesView-layout-content {
-            overflow: hidden;
-            padding: 10px;
-        }
-
-    }
-
-    .propertiesView-presTrails {
-        border-bottom: 1px solid black;
-        //padding: 8.5px;
-
-        .propertiesView-presTrails-title {
-            font-weight: bold;
-            font-size: 12.5px;
-            padding: 4px;
-            display: flex;
-            color: white;
-            padding-left: 8px;
-            background-color: rgb(51, 51, 51);
-
-            &:hover {
-                cursor: pointer;
-            }
-
-            .propertiesView-presTrails-title-icon {
-                float: right;
-                justify-items: right;
-                align-items: flex-end;
-                margin-left: auto;
-                margin-right: 9px;
-
-                &:hover {
-                    cursor: pointer;
-                }
-            }
-        }
-
-        .propertiesView-presTrails-content {
-            font-size: 10px;
-            padding: 10px;
-            margin-left: 5px;
-        }
-    }
-}
-
-.inking-button {
-
-    display: flex;
-
-    .inking-button-points {
-        background-color: #333333;
-        padding: 7px;
-        border-radius: 7px;
-        margin-right: 32px;
-        width: 32;
-        height: 32;
-        padding-top: 9px;
-        margin-left: 18px;
-
-        &:hover {
-            background: rgb(131, 131, 131);
-            transform: scale(1.05);
-            cursor: pointer;
-        }
-    }
-
-    .inking-button-lock {
-        background-color: #333333;
-        padding: 7px;
-        border-radius: 7px;
-        margin-right: 32px;
-        width: 32;
-        height: 32;
-        padding-top: 9px;
-        padding-left: 10px;
-
-        &:hover {
-            background: rgb(131, 131, 131);
-            transform: scale(1.05);
-            cursor: pointer;
-        }
-    }
-
-    .inking-button-rotate {
-        background-color: #333333;
-        padding: 7px;
-        border-radius: 7px;
-        width: 32;
-        height: 32;
-        padding-top: 9px;
-        padding-left: 10px;
-
-        &:hover {
-            background: rgb(131, 131, 131);
-            transform: scale(1.05);
-            cursor: pointer;
-        }
-    }
-}
-
-.inputBox-duo {
-    display: flex;
-}
-
-.inputBox {
-
-    margin-top: 10px;
-    display: flex;
-    height: 19px;
-    margin-right: 15px;
-
-    .inputBox-title {
-        font-size: 12px;
-        padding-right: 5px;
-    }
-
-    .inputBox-input {
-        font-size: 10px;
-        width: 50px;
-        margin-right: 1px;
-        border-radius: 3px;
-
-        &:hover {
-            cursor: pointer;
-        }
-    }
-
-    .inputBox-button {
-
-        .inputBox-button-up {
-            background-color: #333333;
-            height: 9px;
-            padding-left: 3px;
-            padding-right: 3px;
-            padding-top: 1px;
-            padding-bottom: 1px;
-            border-radius: 1.5px;
-
-            &:hover {
-                background: rgb(131, 131, 131);
-                transform: scale(1.05);
-                cursor: pointer;
-            }
-        }
-
-        .inputBox-button-down {
-            background-color: #333333;
-            height: 9px;
-            padding-left: 3px;
-            padding-right: 3px;
-            padding-top: 1px;
-            padding-bottom: 1px;
-            border-radius: 1.5px;
-
-            &:hover {
-                background: rgb(131, 131, 131);
-                transform: scale(1.05);
-                cursor: pointer;
-            }
-        }
-
-    }
-}
-
-.color-palette {
-    width: 160px;
-    height: 360;
-}
-
-.strokeAndFill {
-    display: flex;
-    margin-top: 10px;
-
-    .fill {
-        margin-right: 40px;
-        display: flex;
-        padding-bottom: 7px;
-        margin-left: 35px;
-
-        .fill-title {
-            font-size: 12px;
-            margin-right: 2px;
-        }
-
-        .fill-button {
-            padding-top: 2px;
-            margin-top: -1px;
-        }
-    }
-
-    .stroke {
-        display: flex;
-
-        .stroke-title {
-            font-size: 12px;
-        }
-
-        .stroke-button {
-            padding-top: 2px;
-            margin-left: 2px;
-            margin-top: -1px;
-        }
-    }
-}
-
-.propertiesView-presSelected {
-    border-top: solid 1px darkgrey;
-    width: 100%;
-    padding-top: 5px;
-    font-family: Roboto;
-    font-weight: 500;
-    display: inline-flex;
-
-    .propertiesView-selectedList {
-        border-left: solid 1px darkgrey;
-        margin-left: 10px;
-        padding-left: 5px;
-
-        .selectedList-items {
-            font-size: 12;
-            font-weight: 300;
-            margin-top: 1;
-        }
-    }
-}
-
-.widthAndDash {
-
-    .width {
-        .width-top {
-            display: flex;
-
-            .width-title {
-                font-size: 12;
-                margin-right: 20px;
-                margin-left: 35px;
-                text-align: center;
-            }
-
-            .width-input {
-                margin-right: 30px;
-                margin-top: -10px;
-            }
-        }
-
-        .width-range {
-            margin-right: 1px;
-            margin-bottom: 6;
-        }
-    }
-
-    .arrows {
-
-        display: flex;
-        margin-bottom: 3px;
-        margin-left: 4px;
-
-        .arrows-head {
-
-            display: flex;
-            margin-right: 35px;
-
-            .arrows-head-title {
-                font-size: 10;
-            }
-
-            .arrows-head-input {
-                margin-left: 6px;
-                margin-top: 2px;
-            }
-        }
-
-        .arrows-tail {
-            display: flex;
-
-            .arrows-tail-title {
-                font-size: 10;
-            }
-
-            .arrows-tail-input {
-                margin-left: 6px;
-                margin-top: 2px;
-            }
-        }
-    }
-
-    .dashed {
-
-        display: flex;
-        margin-left: 64px;
-        margin-bottom: 6px;
-
-        .dashed-title {
-            font-size: 10;
-        }
-
-        .dashed-input {
-            margin-left: 6px;
-            margin-top: 2px;
-        }
-    }
-}
-
-.editable-title {
-    border: none;
-    padding: 6px;
-    padding-bottom: 2px;
-    background: #eeeeee;
-    border-top: 1px solid;
-    border-left: 1px solid;
-
-    &:hover {
-        border: 0.75px solid rgb(122, 28, 28);
-    }
-}
-
-
-.properties-flyout {
-    grid-column: 2/4;
-}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
deleted file mode 100644
index 560f09931..000000000
--- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
+++ /dev/null
@@ -1,1090 +0,0 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Checkbox, Tooltip } from "@material-ui/core";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { ColorState, SketchPicker } from "react-color";
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, Field, HeightSym, WidthSym } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { InkField } from "../../../../fields/InkField";
-import { ComputedField } from "../../../../fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../../fields/Types";
-import { GetEffectiveAcl, SharingPermissions } from "../../../../fields/util";
-import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../../Utils";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { SharingManager } from "../../../util/SharingManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
-import { EditableView } from "../../EditableView";
-import { ContentFittingDocumentView } from "../../nodes/ContentFittingDocumentView";
-import { KeyValueBox } from "../../nodes/KeyValueBox";
-import { PresBox } from "../../nodes/PresBox";
-import { PropertiesButtons } from "../../PropertiesButtons";
-import { CollectionDockingView } from "../CollectionDockingView";
-import { SelectorContextMenu } from "../ParentDocumentSelector";
-import { FormatShapePane } from "./FormatShapePane";
-import "./FormatShapePane.scss";
-import "./PropertiesView.scss";
-const higflyout = require("@hig/flyout");
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
-const _global = (window /* browser */ || global /* node */) as any;
-
-interface PropertiesViewProps {
-    width: number;
-    height: number;
-    renderDepth: number;
-    ScreenToLocalTransform: () => Transform;
-    onDown: (event: any) => void;
-}
-
-@observer
-export class PropertiesView extends React.Component<PropertiesViewProps> {
-    private _widthUndo?: UndoManager.Batch;
-
-    @computed get MAX_EMBED_HEIGHT() { return 200; }
-
-    @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; }
-    @computed get selectedDocumentView() {
-        if (SelectionManager.SelectedDocuments().length) {
-            return SelectionManager.SelectedDocuments()[0];
-        } else if (PresBox.Instance && PresBox.Instance._selectedArray.length) {
-            return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
-        } else { return undefined; }
-    }
-    @computed get isPres(): boolean {
-        if (this.selectedDoc?.type === DocumentType.PRES) return true;
-        return false;
-    }
-    @computed get dataDoc() { return this.selectedDoc?.[DataSym]; }
-
-    @observable layoutFields: boolean = false;
-
-    @observable openOptions: boolean = true;
-    @observable openSharing: boolean = true;
-    @observable openFields: boolean = true;
-    @observable openLayout: boolean = true;
-    @observable openContexts: boolean = true;
-    @observable openAppearance: boolean = true;
-    @observable openTransform: boolean = true;
-    // @observable selectedUser: string = "";
-    // @observable addButtonPressed: boolean = false;
-    @observable layoutDocAcls: boolean = false;
-
-    //Pres Trails booleans:
-    @observable openPresTransitions: boolean = false;
-    @observable openPresProgressivize: boolean = false;
-    @observable openAddSlide: boolean = false;
-    @observable openSlideOptions: boolean = false;
-
-    @observable inOptions: boolean = false;
-    @observable _controlBtn: boolean = false;
-    @observable _lock: boolean = false;
-
-    @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; }
-
-    @action
-    rtfWidth = () => {
-        if (this.selectedDoc) {
-            return Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20);
-        } else {
-            return 0;
-        }
-    }
-    @action
-    rtfHeight = () => {
-        if (this.selectedDoc) {
-            return this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
-        } else {
-            return 0;
-        }
-    }
-
-    @action
-    docWidth = () => {
-        if (this.selectedDoc) {
-            const layoutDoc = this.selectedDoc;
-            const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
-            if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.width - 20));
-            return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.width - 20) : this.props.width - 20;
-        } else {
-            return 0;
-        }
-    }
-
-    @action
-    docHeight = () => {
-        if (this.selectedDoc && this.dataDoc) {
-            const layoutDoc = this.selectedDoc;
-            return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => {
-                const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
-                if (aspect) return this.docWidth() * aspect;
-                return layoutDoc._fitWidth ? (!this.dataDoc._nativeHeight ? NumCast(this.props.height) :
-                    Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth,
-                        NumCast(this.props.height)))) :
-                    NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50;
-            })()));
-        } else {
-            return 0;
-        }
-    }
-
-    @computed get expandedField() {
-        if (this.dataDoc && this.selectedDoc) {
-            const ids: { [key: string]: string } = {};
-            const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] :
-                SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? Doc.Layout(dv.layoutDoc) : dv.dataDoc);
-            docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)));
-            const rows: JSX.Element[] = [];
-            for (const key of Object.keys(ids).slice().sort()) {
-                const docvals = new Set<any>();
-                docs.forEach(doc => docvals.add(doc[key]));
-                const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key];
-                if (key[0] === "#") {
-                    rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "2px" }} key={key}>
-                        <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key}</span>
-                    &nbsp;
-                </div>);
-                } else {
-                    const contentElement = <EditableView key="editableView"
-                        contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
-                        height={13}
-                        fontSize={10}
-                        GetValue={() => contents !== undefined ? Field.toString(contents as Field) : "null"}
-                        SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }}
-                    />;
-                    rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "-1px" }} key={key}>
-                        <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ":"}</span>
-                        &nbsp;
-                        {contentElement}
-                    </div>);
-                }
-            }
-            rows.push(<div className="field" key={"newKeyValue"} style={{ marginTop: "3px" }}>
-                <EditableView
-                    key="editableView"
-                    contents={"add key:value or #tags"}
-                    height={13}
-                    fontSize={10}
-                    GetValue={() => ""}
-                    SetValue={this.setKeyValue} />
-            </div>);
-            return rows;
-        }
-    }
-
-    @computed get noviceFields() {
-        if (this.dataDoc) {
-            const ids: { [key: string]: string } = {};
-            const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => dv.dataDoc);
-            docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)));
-            const rows: JSX.Element[] = [];
-            const noviceReqFields = ["author", "creationDate"];
-            const noviceLayoutFields = ["_curPage"];
-            const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("ACL") && key !== "UseCors")),
-            ...noviceReqFields, ...noviceLayoutFields];
-            for (const key of noviceKeys.sort()) {
-                const docvals = new Set<any>();
-                docs.forEach(doc => docvals.add(doc[key]));
-                const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key];
-                if (key[0] === "#") {
-                    rows.push(<div className="uneditable-field" key={key}>
-                        <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key}</span>
-                        &nbsp;
-                    </div>);
-                } else if (contents !== undefined) {
-                    const value = Field.toString(contents as Field);
-                    if (noviceReqFields.includes(key) || key.indexOf("lastModified") !== -1) {
-                        rows.push(<div className="uneditable-field" key={key}>
-                            <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ": "}</span>
-                            <div style={{ whiteSpace: "nowrap", overflowX: "hidden" }}>{value}</div>
-                        </div>);
-                    } else {
-                        const contentElement = <EditableView key="editableView"
-                            contents={value}
-                            height={13}
-                            fontSize={10}
-                            GetValue={() => contents !== undefined ? Field.toString(contents as Field) : "null"}
-                            SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }}
-                        />;
-
-                        rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "-1px" }} key={key}>
-                            <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ":"}</span>
-                        &nbsp;
-                        {contentElement}
-                        </div>);
-                    }
-                }
-            }
-            rows.push(<div className="field" key={"newKeyValue"} style={{ marginTop: "3px" }}>
-                <EditableView
-                    key="editableView"
-                    contents={"add key:value or #tags"}
-                    height={13}
-                    fontSize={10}
-                    GetValue={() => ""}
-                    SetValue={this.setKeyValue} />
-            </div>);
-            return rows;
-        }
-    }
-
-    @undoBatch
-    setKeyValue = (value: string) => {
-        const docs = SelectionManager.SelectedDocuments().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? dv.layoutDoc : dv.dataDoc);
-        docs.forEach(doc => {
-            if (value.indexOf(":") !== -1) {
-                const newVal = value[0].toUpperCase() + value.substring(1, value.length);
-                KeyValueBox.SetField(doc, newVal.substring(0, newVal.indexOf(":")), newVal.substring(newVal.indexOf(":") + 1, newVal.length), true);
-                return true;
-            } else if (value[0] === "#") {
-                const newVal = value + `:'${value}'`;
-                KeyValueBox.SetField(doc, newVal.substring(0, newVal.indexOf(":")), newVal.substring(newVal.indexOf(":") + 1, newVal.length), true);
-                return true;
-            }
-        });
-        return false;
-    }
-
-    @observable transform: Transform = Transform.Identity();
-    getTransform = () => this.transform;
-    propertiesDocViewRef = (ref: HTMLDivElement) => {
-        const observer = new _global.ResizeObserver(action((entries: any) => {
-            const cliRect = ref.getBoundingClientRect();
-            this.transform = new Transform(-cliRect.x, -cliRect.y, 1);
-        }));
-        ref && observer.observe(ref);
-    }
-
-    @computed get contexts() {
-        return !this.selectedDoc ? (null) : <SelectorContextMenu Document={this.selectedDoc} hideTitle={true} addDocTab={(doc, where) => CollectionDockingView.AddRightSplit(doc)} />;
-    }
-
-    previewBackground = () => "lightgrey";
-    @computed get layoutPreview() {
-        if (SelectionManager.SelectedDocuments().length > 1) {
-            return "-- multiple selected --";
-        }
-        if (this.selectedDoc) {
-            const layoutDoc = Doc.Layout(this.selectedDoc);
-            const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : this.docHeight;
-            const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : this.docWidth;
-            return <div ref={this.propertiesDocViewRef} style={{ pointerEvents: "none", display: "inline-block", height: panelHeight() }} key={this.selectedDoc[Id]}>
-                <ContentFittingDocumentView
-                    Document={layoutDoc}
-                    DataDoc={this.dataDoc}
-                    LibraryPath={emptyPath}
-                    renderDepth={this.props.renderDepth + 1}
-                    rootSelected={returnFalse}
-                    treeViewDoc={undefined}
-                    backgroundColor={this.previewBackground}
-                    fitToBox={true}
-                    FreezeDimensions={true}
-                    NativeWidth={layoutDoc.type ===
-                        StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : returnZero}
-                    NativeHeight={layoutDoc.type ===
-                        StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : returnZero}
-                    PanelWidth={panelWidth}
-                    PanelHeight={panelHeight}
-                    focus={returnFalse}
-                    ScreenToLocalTransform={this.getTransform}
-                    docFilters={returnEmptyFilter}
-                    searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionDoc={undefined}
-                    ContainingCollectionView={undefined}
-                    addDocument={returnFalse}
-                    moveDocument={undefined}
-                    removeDocument={returnFalse}
-                    parentActive={() => false}
-                    whenActiveChanged={emptyFunction}
-                    addDocTab={returnFalse}
-                    pinToPres={emptyFunction}
-                    bringToFront={returnFalse}
-                    ContentScaling={returnOne}
-                    dontRegisterView={true}
-                    dropAction={undefined}
-                />
-            </div>;
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Handles the changing of a user's permissions from the permissions panel.
-     */
-    @undoBatch
-    changePermissions = (e: any, user: string) => {
-        SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, this.selectedDoc!);
-    }
-
-    /**
-     * @returns the options for the permissions dropdown.
-     */
-    getPermissionsSelect(user: string, permission: string) {
-        return <select className="permissions-select"
-            value={permission}
-            onChange={e => this.changePermissions(e, user)}>
-            {Object.values(SharingPermissions).map(permission => {
-                return (
-                    <option key={permission} value={permission}>
-                        {permission}
-                    </option>);
-            })}
-        </select>;
-    }
-
-    /**
-     * @returns the notification icon. On clicking, it should notify someone of a document been shared with them.
-     */
-    @computed get notifyIcon() {
-        return <Tooltip title={<div className="dash-tooltip">Notify with message</div>}>
-            <div className="notify-button">
-                <FontAwesomeIcon className="notify-button-icon" icon="bell" color="white" size="sm" />
-            </div>
-        </Tooltip>;
-    }
-
-    /**
-     * ... next to the owner that opens the main SharingManager interface on click.
-     */
-    @computed get expansionIcon() {
-        return <Tooltip title={<div className="dash-tooltip">{"Show more permissions"}</div>}>
-            <div className="expansion-button" onPointerDown={() => {
-                if (this.selectedDocumentView || this.selectedDoc) {
-                    SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDocumentView ? this.selectedDocumentView : undefined, this.selectedDoc);
-                }
-            }}>
-                <FontAwesomeIcon className="expansion-button-icon" icon="ellipsis-h" color="black" size="sm" />
-            </div>
-        </Tooltip>;
-    }
-
-    /**
-     * @returns a row of the permissions panel
-     */
-    sharingItem(name: string, effectiveAcl: symbol, permission: string) {
-        return <div className="propertiesView-sharingTable-item" key={name + permission}
-        // style={{ backgroundColor: this.selectedUser === name ? "#bcecfc" : "" }}
-        // onPointerDown={action(() => this.selectedUser = this.selectedUser === name ? "" : name)}
-        >
-            <div className="propertiesView-sharingTable-item-name" style={{ width: name !== "Me" ? "85px" : "80px" }}> {name} </div>
-            {/* {name !== "Me" ? this.notifyIcon : null} */}
-            <div className="propertiesView-sharingTable-item-permission">
-                {effectiveAcl === AclAdmin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission}
-                {permission === "Owner" ? this.expansionIcon : null}
-            </div>
-        </div>;
-    }
-
-    /**
-     * @returns the sharing and permissiosn panel.
-     */
-    @computed get sharingTable() {
-        const AclMap = new Map<symbol, string>([
-            [AclPrivate, SharingPermissions.None],
-            [AclReadonly, SharingPermissions.View],
-            [AclAddonly, SharingPermissions.Add],
-            [AclEdit, SharingPermissions.Edit],
-            [AclAdmin, SharingPermissions.Admin]
-        ]);
-
-        const target = this.layoutDocAcls ? this.selectedDoc! : this.selectedDoc![DataSym];
-
-        const effectiveAcl = GetEffectiveAcl(target);
-        const tableEntries = [];
-
-        // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => {
-        if (target[AclSym]) {
-            for (const [key, value] of Object.entries(target[AclSym])) {
-                const name = key.substring(4).replace("_", ".");
-                if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public"/* && sidebarUsersDisplayed![name] !== false*/) {
-                    tableEntries.push(this.sharingItem(name, effectiveAcl, AclMap.get(value as symbol)!));
-                }
-            }
-        }
-
-        //     if (Doc.UserDoc().sidebarUsersDisplayed) {
-        //         for (const [name, value] of Object.entries(sidebarUsersDisplayed!)) {
-        //             if (value === true && !this.selectedDoc![`ACL-${name.substring(8).replace(".", "_")}`]) tableEntries.push(this.sharingItem(name.substring(8), effectiveAcl, SharingPermissions.None));
-        //         }
-        //     }
-        // })
-
-        // shifts the current user, owner, public to the top of the doc.
-        tableEntries.unshift(this.sharingItem("Public", effectiveAcl, (AclMap.get(target[AclSym]?.["ACL-Public"]) || SharingPermissions.None)));
-        tableEntries.unshift(this.sharingItem("Me", effectiveAcl, Doc.CurrentUserEmail === target.author ? "Owner" : AclMap.get(effectiveAcl)!));
-        if (Doc.CurrentUserEmail !== target.author) tableEntries.unshift(this.sharingItem(StrCast(target.author), effectiveAcl, "Owner"));
-
-        return <div className="propertiesView-sharingTable">
-            {tableEntries}
-        </div>;
-    }
-
-    @computed get fieldsCheckbox() {
-        return <Checkbox
-            color="primary"
-            onChange={this.toggleCheckbox}
-            checked={this.layoutFields}
-        />;
-    }
-
-    @action
-    toggleCheckbox = () => {
-        this.layoutFields = !this.layoutFields;
-    }
-
-    @computed get editableTitle() {
-        const titles = new Set<string>();
-        SelectionManager.SelectedDocuments().forEach(dv => titles.add(StrCast(dv.rootDoc.title)));
-        const title = Array.from(titles.keys()).length > 1 ? "--multiple selected--" : StrCast(this.selectedDoc?.title);
-        return <div className="editable-title"><EditableView
-            key="editableView"
-            contents={title}
-            height={25}
-            fontSize={14}
-            GetValue={() => title}
-            SetValue={this.setTitle} /> </div>;
-    }
-
-    @undoBatch
-    @action
-    setTitle = (value: string) => {
-        if (SelectionManager.SelectedDocuments().length > 1) {
-            SelectionManager.SelectedDocuments().map(dv => Doc.SetInPlace(dv.rootDoc, "title", value, true));
-            return true;
-        } else if (this.dataDoc) {
-            if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, "title", value, true);
-            else KeyValueBox.SetField(this.dataDoc, "title", value, true);
-            return true;
-        }
-        return false;
-    }
-
-
-    @undoBatch
-    @action
-    rotate = (angle: number) => {
-        const _centerPoints: { X: number, Y: number }[] = [];
-        if (this.selectedDoc) {
-            const doc = this.selectedDoc;
-            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
-                const ink = Cast(doc.data, InkField)?.inkData;
-                if (ink) {
-                    const xs = ink.map(p => p.X);
-                    const ys = ink.map(p => p.Y);
-                    const left = Math.min(...xs);
-                    const top = Math.min(...ys);
-                    const right = Math.max(...xs);
-                    const bottom = Math.max(...ys);
-                    _centerPoints.push({ X: left, Y: top });
-                }
-            }
-
-            var index = 0;
-            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
-                doc.rotation = Number(doc.rotation) + Number(angle);
-                const inks = Cast(doc.data, InkField)?.inkData;
-                if (inks) {
-                    const newPoints: { X: number, Y: number }[] = [];
-                    inks.forEach(ink => {
-                        const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X;
-                        const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y;
-                        newPoints.push({ X: newX, Y: newY });
-                    });
-                    doc.data = new InkField(newPoints);
-                    const xs = newPoints.map(p => p.X);
-                    const ys = newPoints.map(p => p.Y);
-                    const left = Math.min(...xs);
-                    const top = Math.min(...ys);
-                    const right = Math.max(...xs);
-                    const bottom = Math.max(...ys);
-
-                    doc._height = (bottom - top);
-                    doc._width = (right - left);
-                }
-                index++;
-            }
-        }
-    }
-
-
-
-    @computed
-    get controlPointsButton() {
-        return <div className="inking-button">
-            <Tooltip title={<div className="dash-tooltip">{"Edit points"}</div>}>
-                <div className="inking-button-points" onPointerDown={action(() => FormatShapePane.Instance._controlBtn = !FormatShapePane.Instance._controlBtn)} style={{ backgroundColor: FormatShapePane.Instance._controlBtn ? "black" : "" }}>
-                    <FontAwesomeIcon icon="bezier-curve" color="white" size="lg" />
-                </div>
-            </Tooltip>
-            <Tooltip title={<div className="dash-tooltip">{FormatShapePane.Instance._lock ? "Unlock ratio" : "Lock ratio"}</div>}>
-                <div className="inking-button-lock" onPointerDown={action(() => FormatShapePane.Instance._lock = !FormatShapePane.Instance._lock)} >
-                    <FontAwesomeIcon icon={FormatShapePane.Instance._lock ? "lock" : "unlock"} color="white" size="lg" />
-                </div>
-            </Tooltip>
-            <Tooltip title={<div className="dash-tooltip">{"Rotate 90˚"}</div>}>
-                <div className="inking-button-rotate" onPointerDown={action(() => this.rotate(Math.PI / 2))}>
-                    <FontAwesomeIcon icon="undo" color="white" size="lg" />
-                </div>
-            </Tooltip>
-        </div>;
-    }
-
-    inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => {
-        return <div className="inputBox"
-            style={{
-                marginRight: title === "X:" ? "19px" : "",
-                marginLeft: title === "∠:" ? "39px" : ""
-            }}>
-            <div className="inputBox-title"> {title} </div>
-            <input className="inputBox-input"
-                type="text" value={value}
-                onChange={e => {
-                    setter(e.target.value);
-                }}
-                onKeyPress={e => {
-                    e.stopPropagation();
-                }} />
-            <div className="inputBox-button">
-                <div className="inputBox-button-up" key="up2"
-                    onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} >
-                    <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
-                </div>
-                <div className="inputbox-Button-down" key="down2"
-                    onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} >
-                    <FontAwesomeIcon icon="caret-down" color="white" size="sm" />
-                </div>
-            </div>
-        </div>;
-    }
-
-    inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => {
-        return <div className="inputBox-duo">
-            {this.inputBox(key, value, setter, title1)}
-            {title2 === "" ? (null) : this.inputBox(key2, value2, setter2, title2)}
-        </div>;
-    }
-
-    @action
-    upDownButtons = (dirs: string, field: string) => {
-        switch (field) {
-            case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break;
-            // case "rot": this.selectedInk?.forEach(i => i.rootDoc.rotation = NumCast(i.rootDoc.rotation) + (dirs === "up" ? 0.1 : -0.1)); break;
-            case "Xps": this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === "up" ? 10 : -10)); break;
-            case "Yps": this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === "up" ? 10 : -10)); break;
-            case "stk": this.selectedDoc && (this.selectedDoc.strokeWidth = NumCast(this.selectedDoc?.strokeWidth) + (dirs === "up" ? .1 : -.1)); break;
-            case "wid":
-                const oldWidth = NumCast(this.selectedDoc?._width);
-                const oldHeight = NumCast(this.selectedDoc?._height);
-                const oldX = NumCast(this.selectedDoc?.x);
-                const oldY = NumCast(this.selectedDoc?.y);
-                this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === "up" ? 10 : - 10));
-                FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height)));
-                const doc = this.selectedDoc;
-                if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
-                    const ink = Cast(doc.data, InkField)?.inkData;
-                    if (ink) {
-                        const newPoints: { X: number, Y: number }[] = [];
-                        for (var j = 0; j < ink.length; j++) {
-                            // (new x — oldx) + (oldxpoint * newWidt)/oldWidth 
-                            const newX = (NumCast(doc.x) - oldX) + (ink[j].X * NumCast(doc._width)) / oldWidth;
-                            const newY = (NumCast(doc.y) - oldY) + (ink[j].Y * NumCast(doc._height)) / oldHeight;
-                            newPoints.push({ X: newX, Y: newY });
-                        }
-                        doc.data = new InkField(newPoints);
-                    }
-                }
-                break;
-            case "hgt":
-                const oWidth = NumCast(this.selectedDoc?._width);
-                const oHeight = NumCast(this.selectedDoc?._height);
-                const oX = NumCast(this.selectedDoc?.x);
-                const oY = NumCast(this.selectedDoc?.y);
-                this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === "up" ? 10 : - 10));
-                FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width)));
-                const docu = this.selectedDoc;
-                if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) {
-                    const ink = Cast(docu.data, InkField)?.inkData;
-                    if (ink) {
-                        const newPoints: { X: number, Y: number }[] = [];
-                        for (var j = 0; j < ink.length; j++) {
-                            // (new x — oldx) + (oldxpoint * newWidt)/oldWidth 
-                            const newX = (NumCast(docu.x) - oX) + (ink[j].X * NumCast(docu._width)) / oWidth;
-                            const newY = (NumCast(docu.y) - oY) + (ink[j].Y * NumCast(docu._height)) / oHeight;
-                            newPoints.push({ X: newX, Y: newY });
-                        }
-                        docu.data = new InkField(newPoints);
-                    }
-                }
-                break;
-        }
-    }
-
-    getField(key: string) {
-        //if (this.selectedDoc) {
-        return Field.toString(this.selectedDoc?.[key] as Field);
-        // } else {
-        //     return undefined as Opt<string>;
-        // }
-    }
-
-    @computed get shapeXps() { return this.getField("x"); }
-    @computed get shapeYps() { return this.getField("y"); }
-    @computed get shapeRot() { return this.getField("rotation"); }
-    @computed get shapeHgt() { return this.getField("_height"); }
-    @computed get shapeWid() { return this.getField("_width"); }
-    set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Number(value)); }
-    set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Number(value)); }
-    set shapeRot(value) { this.selectedDoc && (this.selectedDoc.rotation = Number(value)); }
-    set shapeWid(value) {
-        const oldWidth = NumCast(this.selectedDoc?._width);
-        this.selectedDoc && (this.selectedDoc._width = Number(value));
-        FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth);
-    }
-    set shapeHgt(value) {
-        const oldHeight = NumCast(this.selectedDoc?._height);
-        this.selectedDoc && (this.selectedDoc._height = Number(value));
-        FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight);
-    }
-
-    @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; }, "H:", "wid", this.shapeWid, (val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; }, "W:"); }
-    @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeXps = val; } return true; }, "X:", "Yps", this.shapeYps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeYps = val; } return true; }, "Y:"); }
-    @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, "∠:", "rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, ""); }
-
-
-    @observable private _fillBtn = false;
-    @observable private _lineBtn = false;
-
-    private _lastFill = "#D0021B";
-    private _lastLine = "#D0021B";
-    private _lastDash: any = "2";
-
-    @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
-    @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
-    set colorFil(value) { value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); }
-    set colorStk(value) { value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); }
-
-    colorButton(value: string, type: string, setter: () => {}) {
-        // return <div className="properties-flyout" onPointerEnter={e => this.changeScrolling(false)}
-        //     onPointerLeave={e => this.changeScrolling(true)}>
-        //     <Flyout anchorPoint={anchorPoints.LEFT_TOP}
-        //         content={type === "fill" ? this.fillPicker : this.linePicker}>
-        return <div className="color-button" key="color" onPointerDown={undoBatch(action(e => setter()))}>
-            <div className="color-button-preview" style={{
-                backgroundColor: value ?? "121212", width: 15, height: 15,
-                display: value === "" || value === "transparent" ? "none" : ""
-            }} />
-            {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -14 }}>☒</p> : ""}
-        </div>;
-        //     </Flyout>
-        // </div>;
-
-    }
-
-    @undoBatch
-    @action
-    switchStk = (color: ColorState) => {
-        const val = String(color.hex);
-        this.colorStk = val;
-        return true;
-    }
-    @undoBatch
-    @action
-    switchFil = (color: ColorState) => {
-        const val = String(color.hex);
-        this.colorFil = val;
-        return true;
-    }
-
-    colorPicker(setter: (color: string) => {}, type: string) {
-        return <SketchPicker onChange={type === "stk" ? this.switchStk : this.switchFil}
-            presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
-                '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
-                '#FFFFFF', '#f1efeb', 'transparent']}
-            color={type === "stk" ? this.colorStk : this.colorFil} />;
-    }
-
-    @computed get fillButton() { return this.colorButton(this.colorFil, "fill", () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); }
-    @computed get lineButton() { return this.colorButton(this.colorStk, "line", () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); }
-
-    @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); }
-    @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); }
-
-    @computed get strokeAndFill() {
-        return <div>
-            <div key="fill" className="strokeAndFill">
-                <div className="fill">
-                    <div className="fill-title">Fill:</div>
-                    <div className="fill-button">{this.fillButton}</div>
-                </div>
-                <div className="stroke">
-                    <div className="stroke-title"> Stroke: </div>
-                    <div className="stroke-button">{this.lineButton}</div>
-                </div>
-            </div>
-            {this._fillBtn ? this.fillPicker : ""}
-            {this._lineBtn ? this.linePicker : ""}
-        </div>;
-    }
-
-    @computed get solidStk() { return this.selectedDoc?.color && (!this.selectedDoc?.strokeDash || this.selectedDoc?.strokeDash === "0") ? true : false; }
-    @computed get dashdStk() { return this.selectedDoc?.strokeDash || ""; }
-    @computed get unStrokd() { return this.selectedDoc?.color ? true : false; }
-    @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
-    @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
-    @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
-    set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
-    set dashdStk(value) {
-        value && (this._lastDash = value) && (this.unStrokd = false);
-        this.selectedDoc && (this.selectedDoc.strokeDash = value ? this._lastDash : undefined);
-    }
-    set widthStk(value) { this.selectedDoc && (this.selectedDoc.strokeWidth = Number(value)); }
-    set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
-    set markHead(value) { this.selectedDoc && (this.selectedDoc.strokeStartMarker = value); }
-    set markTail(value) { this.selectedDoc && (this.selectedDoc.strokeEndMarker = value); }
-
-
-    @computed get stkInput() { return this.regInput("stk", this.widthStk, (val: string) => this.widthStk = val); }
-
-
-    regInput = (key: string, value: any, setter: (val: string) => {}) => {
-        return <div className="inputBox">
-            <input className="inputBox-input"
-                type="text" value={value}
-                onChange={e => setter(e.target.value)} />
-            <div className="inputBox-button">
-                <div className="inputBox-button-up" key="up2"
-                    onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} >
-                    <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
-                </div>
-                <div className="inputbox-Button-down" key="down2"
-                    onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} >
-                    <FontAwesomeIcon icon="caret-down" color="white" size="sm" />
-                </div>
-            </div>
-        </div>;
-    }
-
-    @computed get widthAndDash() {
-        return <div className="widthAndDash">
-            <div className="width">
-                <div className="width-top">
-                    <div className="width-title">Width:</div>
-                    <div className="width-input">{this.stkInput}</div>
-                </div>
-                <input className="width-range" type="range"
-                    defaultValue={Number(this.widthStk)} min={1} max={100}
-                    onChange={(action((e) => this.widthStk = e.target.value))}
-                    onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("width undo"); }}
-                    onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }}
-                />
-            </div>
-
-            <div className="arrows">
-                <div className="arrows-head">
-                    <div className="arrows-head-title" >Arrow Head: </div>
-                    <input key="markHead" className="arrows-head-input" type="checkbox"
-                        checked={this.markHead !== ""}
-                        onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} />
-                </div>
-                <div className="arrows-tail">
-                    <div className="arrows-tail-title" >Arrow End: </div>
-                    <input key="markTail" className="arrows-tail-input" type="checkbox"
-                        checked={this.markTail !== ""}
-                        onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} />
-                </div>
-            </div>
-            <div className="dashed">
-                <div className="dashed-title">Dashed Line:</div>
-                <input key="markHead" className="dashed-input"
-                    type="checkbox" checked={this.dashdStk === "2"}
-                    onChange={this.changeDash} />
-            </div>
-        </div>;
-    }
-
-    @undoBatch @action
-    changeDash = () => {
-        this.dashdStk = this.dashdStk === "2" ? "0" : "2";
-    }
-
-    @computed get appearanceEditor() {
-        return <div className="appearance-editor">
-            {this.widthAndDash}
-            {this.strokeAndFill}
-        </div>;
-    }
-
-    @computed get transformEditor() {
-        return <div className="transform-editor">
-            {this.controlPointsButton}
-            {this.hgtInput}
-            {this.XpsInput}
-            {this.rotInput}
-        </div>;
-    }
-
-    /**
-     * Handles adding and removing members from the sharing panel
-     */
-    // handleUserChange = (selectedUser: string, add: boolean) => {
-    //     if (!Doc.UserDoc().sidebarUsersDisplayed) Doc.UserDoc().sidebarUsersDisplayed = new Doc;
-    //     DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => {
-    //         sidebarUsersDisplayed![`display-${selectedUser}`] = add;
-    //         !add && runInAction(() => this.selectedUser = "");
-    //     });
-    // }
-
-    render() {
-        if (!this.selectedDoc && !this.isPres) {
-            return <div className="propertiesView" style={{ width: this.props.width }}>
-                <div className="propertiesView-title" style={{ width: this.props.width }}>
-                    No Document Selected
-                </div>
-            </div>;
-
-        } else {
-            const novice = Doc.UserDoc().noviceMode;
-
-            if (this.selectedDoc && !this.isPres) {
-                return <div className="propertiesView" style={{
-                    width: this.props.width,
-                    //overflowY: this.scrolling ? "scroll" : "visible"
-                }} >
-                    <div className="propertiesView-title" style={{ width: this.props.width }}>
-                        Properties
-                    {/* <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}>
-                        <FontAwesomeIcon icon="times" color="black" size="sm" />
-                    </div> */}
-                    </div>
-                    <div className="propertiesView-name">
-                        {this.editableTitle}
-                    </div>
-                    <div className="propertiesView-settings" onPointerEnter={action(() => this.inOptions = true)}
-                        onPointerLeave={action(() => this.inOptions = false)}>
-                        <div className="propertiesView-settings-title"
-                            onPointerDown={action(() => this.openOptions = !this.openOptions)}
-                            style={{ backgroundColor: this.openOptions ? "black" : "" }}>
-                            Options
-                        <div className="propertiesView-settings-title-icon">
-                                <FontAwesomeIcon icon={this.openOptions ? "caret-down" : "caret-right"} size="lg" color="white" />
-                            </div>
-                        </div>
-                        {!this.openOptions ? (null) :
-                            <div className="propertiesView-settings-content">
-                                <PropertiesButtons />
-                            </div>}
-                    </div>
-                    <div className="propertiesView-sharing">
-                        <div className="propertiesView-sharing-title"
-                            onPointerDown={action(() => this.openSharing = !this.openSharing)}
-                            style={{ backgroundColor: this.openSharing ? "black" : "" }}>
-                            Sharing {"&"} Permissions
-                        <div className="propertiesView-sharing-title-icon">
-                                <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" />
-                            </div>
-                        </div>
-                        {!this.openSharing ? (null) :
-                            <div className="propertiesView-sharing-content">
-                                <div className="propertiesView-acls-checkbox">
-                                    <Checkbox
-                                        color="primary"
-                                        onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)}
-                                        checked={this.layoutDocAcls}
-                                    />;
-                                    <div className="propertiesView-acls-checkbox-text">Layout</div>
-                                </div>
-                                {this.sharingTable}
-                                {/* <div className="change-buttons">
-                            <button
-                                onPointerDown={action(() => this.addButtonPressed = !this.addButtonPressed)}
-                            >
-                                <FontAwesomeIcon icon={fa.faPlus} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} />
-                            </button>
-                            <button
-                                id="sharingProperties-removeUser"
-                                onPointerDown={() => this.handleUserChange(this.selectedUser, false)}
-                                style={{ backgroundColor: this.selectedUser ? "#121721" : "#777777" }}
-                            ><FontAwesomeIcon icon={fa.faMinus} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} /></button>
-                            <button onClick={() => SharingManager.Instance.open(this.selectedDocumentView!)}><FontAwesomeIcon icon={fa.faCog} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} /></button>
-                            {this.addButtonPressed ?
-                                // <input type="text" onKeyDown={this.handleKeyPress} /> :
-                                <select onChange={e => this.handleUserChange(e.target.value, true)}>
-                                    <option selected disabled hidden>
-                                        Add users
-                                    </option>
-                                    {SharingManager.Instance.users.map(user =>
-                                        (<option value={user.user.email}>
-                                            {user.user.email}
-                                        </option>)
-                                    )}
-                                    {GroupManager.Instance.getAllGroups().map(group =>
-                                        (<option value={StrCast(group.groupName)}>
-                                            {StrCast(group.groupName)}
-                                        </option>))}
-                                </select> :
-                                null}
-                        </div> */}
-                            </div>}
-                    </div>
-
-                    {!this.isInk ? (null) :
-                        <div className="propertiesView-appearance">
-                            <div className="propertiesView-appearance-title"
-                                onPointerDown={action(() => this.openAppearance = !this.openAppearance)}
-                                style={{ backgroundColor: this.openAppearance ? "black" : "" }}>
-                                Appearance
-                            <div className="propertiesView-appearance-title-icon">
-                                    <FontAwesomeIcon icon={this.openAppearance ? "caret-down" : "caret-right"} size="lg" color="white" />
-                                </div>
-                            </div>
-                            {!this.openAppearance ? (null) :
-                                <div className="propertiesView-appearance-content">
-                                    {this.appearanceEditor}
-                                </div>}
-                        </div>}
-
-                    {this.isInk ? <div className="propertiesView-transform">
-                        <div className="propertiesView-transform-title"
-                            onPointerDown={action(() => this.openTransform = !this.openTransform)}
-                            style={{ backgroundColor: this.openTransform ? "black" : "" }}>
-                            Transform
-                        <div className="propertiesView-transform-title-icon">
-                                <FontAwesomeIcon icon={this.openTransform ? "caret-down" : "caret-right"} size="lg" color="white" />
-                            </div>
-                        </div>
-                        {this.openTransform ? <div className="propertiesView-transform-content">
-                            {this.transformEditor}
-                        </div> : null}
-                    </div> : null}
-
-                    <div className="propertiesView-fields">
-                        <div className="propertiesView-fields-title"
-                            onPointerDown={action(() => this.openFields = !this.openFields)}
-                            style={{ backgroundColor: this.openFields ? "black" : "" }}>
-                            Fields {"&"} Tags
-                            <div className="propertiesView-fields-title-icon">
-                                <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" />
-                            </div>
-                        </div>
-                        {!novice && this.openFields ? <div className="propertiesView-fields-checkbox">
-                            {this.fieldsCheckbox}
-                            <div className="propertiesView-fields-checkbox-text">Layout</div>
-                        </div> : null}
-                        {!this.openFields ? (null) :
-                            <div className="propertiesView-fields-content">
-                                {novice ? this.noviceFields : this.expandedField}
-                            </div>}
-                    </div>
-                    <div className="propertiesView-contexts">
-                        <div className="propertiesView-contexts-title"
-                            onPointerDown={action(() => this.openContexts = !this.openContexts)}
-                            style={{ backgroundColor: this.openContexts ? "black" : "" }}>
-                            Contexts
-                        <div className="propertiesView-contexts-title-icon">
-                                <FontAwesomeIcon icon={this.openContexts ? "caret-down" : "caret-right"} size="lg" color="white" />
-                            </div>
-                        </div>
-                        {this.openContexts ? <div className="propertiesView-contexts-content"  >{this.contexts}</div> : null}
-                    </div>
-                    <div className="propertiesView-layout">
-                        <div className="propertiesView-layout-title"
-                            onPointerDown={action(() => this.openLayout = !this.openLayout)}
-                            style={{ backgroundColor: this.openLayout ? "black" : "" }}>
-                            Layout
-                        <div className="propertiesView-layout-title-icon">
-                                <FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" />
-                            </div>
-                        </div>
-                        {this.openLayout ? <div className="propertiesView-layout-content"  >{this.layoutPreview}</div> : null}
-                    </div>
-                </div>;
-            }
-            if (this.isPres) {
-                const selectedItem: boolean = PresBox.Instance?._selectedArray.length > 0;
-                return <div className="propertiesView" style={{ width: this.props.width }}>
-                    <div className="propertiesView-title" style={{ width: this.props.width }}>
-                        Presentation
-                    </div>
-                    <div className="propertiesView-name">
-                        {this.editableTitle}
-                        <div className="propertiesView-presSelected">
-                            {PresBox.Instance?._selectedArray.length} selected
-                            <div className="propertiesView-selectedList">
-                                {PresBox.Instance?.listOfSelected}
-                            </div>
-                        </div>
-                    </div>
-                    {!selectedItem ? (null) : <div className="propertiesView-presTrails">
-                        <div className="propertiesView-presTrails-title"
-                            onPointerDown={action(() => { this.openPresTransitions = !this.openPresTransitions; })}
-                            style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}>
-                            &nbsp; <FontAwesomeIcon icon={"rocket"} /> &nbsp; Transitions
-                        <div className="propertiesView-presTrails-title-icon">
-                                <FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" />
-                            </div>
-                        </div>
-                        {this.openPresTransitions ? <div className="propertiesView-presTrails-content">
-                            {PresBox.Instance.transitionDropdown}
-                        </div> : null}
-                    </div>}
-                    {!selectedItem ? (null) : <div className="propertiesView-presTrails">
-                        <div className="propertiesView-presTrails-title"
-                            onPointerDown={action(() => { this.openPresProgressivize = !this.openPresProgressivize; })}
-                            style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}>
-                            &nbsp; <FontAwesomeIcon icon={"tasks"} /> &nbsp; Progressivize
-                        <div className="propertiesView-presTrails-title-icon">
-                                <FontAwesomeIcon icon={this.openPresProgressivize ? "caret-down" : "caret-right"} size="lg" color="white" />
-                            </div>
-                        </div>
-                        {this.openPresProgressivize ? <div className="propertiesView-presTrails-content">
-                            {PresBox.Instance.progressivizeDropdown}
-                        </div> : null}
-                    </div>}
-                    {!selectedItem ? (null) : <div className="propertiesView-presTrails">
-                        <div className="propertiesView-presTrails-title"
-                            onPointerDown={action(() => { this.openSlideOptions = !this.openSlideOptions; })}
-                            style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}>
-                            &nbsp; <FontAwesomeIcon icon={"cog"} /> &nbsp; {PresBox.Instance.stringType} options
-                        <div className="propertiesView-presTrails-title-icon">
-                                <FontAwesomeIcon icon={this.openSlideOptions ? "caret-down" : "caret-right"} size="lg" color="white" />
-                            </div>
-                        </div>
-                        {this.openSlideOptions ? <div className="propertiesView-presTrails-content">
-                            {PresBox.Instance.optionsDropdown}
-                        </div> : null}
-                    </div>}
-                    <div className="propertiesView-presTrails">
-                        <div className="propertiesView-presTrails-title"
-                            onPointerDown={action(() => { this.openAddSlide = !this.openAddSlide; })}
-                            style={{ backgroundColor: this.openAddSlide ? "black" : "" }}>
-                            &nbsp; <FontAwesomeIcon icon={"plus"} /> &nbsp; Add new slide
-                        <div className="propertiesView-presTrails-title-icon">
-                                <FontAwesomeIcon icon={this.openAddSlide ? "caret-down" : "caret-right"} size="lg" color="white" />
-                            </div>
-                        </div>
-                        {this.openAddSlide ? <div className="propertiesView-presTrails-content">
-                            {PresBox.Instance.newDocumentDropdown}
-                        </div> : null}
-                    </div>
-                    {/* <div className="propertiesView-sharing">
-                        <div className="propertiesView-sharing-title"
-                            onPointerDown={acition(() => { this.openSharing = !this.openSharing; })}
-                            style={{ backgroundColor: this.openSharing ? "black" : "" }}>
-                            Sharing {"&"} Permissions
-                        <div className="propertiesView-sharing-title-icon">
-                                <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" />
-                            </div>
-                        </div>
-                        {this.openSharing ? <div className="propertiesView-sharing-content">
-                            {this.sharingTable}
-                        </div> : null}
-                    </div> */}
-                </div>;
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6e1357b41..2d03c5154 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -30,6 +30,7 @@ import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from "../DocComponent";
 import { EditableView } from '../EditableView';
+import { FormatShapePane } from '../FormatShapePane';
 import { DocumentContentsView } from "./DocumentContentsView";
 import { DocumentLinksButton } from './DocumentLinksButton';
 import "./DocumentView.scss";
@@ -38,7 +39,6 @@ import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { RadialMenu } from './RadialMenu';
 import { TaskCompletionBox } from './TaskCompletedBox';
 import React = require("react");
-import { FormatShapePane } from '../collections/collectionFreeForm/FormatShapePane';
 
 export type DocFocusFunc = () => boolean;
 
diff --git a/src/client/views/nodes/LinkAnchorBox.scss b/src/client/views/nodes/LinkAnchorBox.scss
index 42ef2958e..62ee9513c 100644
--- a/src/client/views/nodes/LinkAnchorBox.scss
+++ b/src/client/views/nodes/LinkAnchorBox.scss
@@ -22,6 +22,11 @@
         padding-left: 2px;
         padding-top: 1px;
     }
+    .linkAnchorBox-button {
+        pointer-events: all;
+        position: relative;
+        display: inline-block;
+    }
 }
 
 .linkAnchorBox-cont-small {
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 1ef25c80d..8e5ee09ff 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -148,7 +148,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
             }} >
             {!this._editing && !this._forceOpen ? (null) :
                 <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout} open={this._forceOpen ? true : undefined} onOpen={() => this._isOpen = true} onClose={action(() => this._isOpen = this._forceOpen = this._editing = false)}>
-                    <span className="parentDocumentSelector-button" >
+                    <span className="linkAnchorBox-button" >
                         <FontAwesomeIcon icon={"eye"} size={"lg"} />
                     </span>
                 </Flyout>}
-- 
cgit v1.2.3-70-g09d2


From 4f3c8cfab12f1c5fff27faa0998a286d210148d2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 29 Aug 2020 16:39:17 -0400
Subject: major restructure of how addDocTabs works.  now you can add/replace
 with options for left/right/top/bottom and specific panel names

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/util/DictationManager.ts                |   4 +-
 src/client/util/DocumentManager.ts                 |   4 +-
 src/client/views/DocumentButtonBar.tsx             |   2 +-
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/GlobalKeyHandler.ts               |   4 +-
 src/client/views/MainView.tsx                      |   8 +-
 src/client/views/PropertiesButtons.tsx             |   4 +-
 src/client/views/PropertiesDocContextSelector.tsx  |   2 +-
 src/client/views/PropertiesView.tsx                |   2 +-
 src/client/views/animationtimeline/Keyframe.tsx    |   2 +-
 .../views/collections/CollectionDockingView.tsx    | 298 ++++++++-------------
 .../views/collections/CollectionSchemaCells.tsx    |   2 +-
 .../CollectionSchemaMovableTableHOC.tsx            |   2 +-
 .../views/collections/CollectionTreeView.tsx       |   2 +-
 src/client/views/collections/CollectionView.tsx    |   8 +-
 src/client/views/collections/SchemaTable.tsx       |   2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   4 +-
 src/client/views/linking/LinkEditor.tsx            |  20 +-
 src/client/views/linking/LinkMenuItem.tsx          |   2 +-
 src/client/views/nodes/DocumentView.tsx            |  16 +-
 src/client/views/nodes/FontIconBox.tsx             |   2 +-
 src/client/views/nodes/KeyValueBox.tsx             |   2 +-
 src/client/views/nodes/KeyValuePair.tsx            |   2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |   6 +-
 src/client/views/nodes/LinkDocPreview.tsx          |   6 +-
 src/client/views/nodes/PresBox.tsx                 |   6 +-
 src/client/views/nodes/VideoBox.tsx                |   2 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |   4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  12 +-
 .../formattedText/FormattedTextBoxComment.tsx      |  16 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    |   2 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |   4 +-
 .../views/nodes/formattedText/RichTextRules.ts     |   2 +-
 src/client/views/nodes/formattedText/nodes_rts.ts  |   4 +-
 src/client/views/pdf/Annotation.tsx                |   2 +-
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 src/fields/RichTextUtils.ts                        |   2 +-
 39 files changed, 202 insertions(+), 268 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2ca1b95d7..262086735 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -847,7 +847,7 @@ export namespace Docs {
                     {
                         type: type,
                         content: [
-                            ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, false, config.initialWidth))
+                            ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, undefined, config.initialWidth))
                         ]
                     }
                 ]
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 7937072da..20f8fe9c1 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1063,7 +1063,7 @@ export class CurrentUserUtils {
 Scripting.addGlobal(function openDragFactory(dragFactory: Doc) {
     const copy = Doc.copyDragFactory(dragFactory);
     if (copy) {
-        CollectionDockingView.AddRightSplit(copy);
+        CollectionDockingView.AddSplit(copy, "right");
         const view = DocumentManager.Instance.getFirstDocumentView(copy);
         view && SelectionManager.SelectDoc(view, false);
     }
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index e66c15fcb..231e1fa8d 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -323,7 +323,7 @@ export namespace DictationManager {
             ["open fields", {
                 action: (target: DocumentView) => {
                     const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 });
-                    target.props.addDocTab(kvp, "onRight");
+                    target.props.addDocTab(kvp, "add:right");
                 }
             }],
 
@@ -337,7 +337,7 @@ export namespace DictationManager {
                     const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
                     proto.data = new RichTextField(proseMirrorState);
                     proto.backgroundColor = "#eeffff";
-                    target.props.addDocTab(newBox, "onRight");
+                    target.props.addDocTab(newBox, "add:right");
                 }
             }]
 
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index fb54fbefc..93c37dbd1 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -124,7 +124,7 @@ export class DocumentManager {
     }
 
     static addRightSplit = (doc: Doc, finished?: () => void) => {
-        CollectionDockingView.AddRightSplit(doc);
+        CollectionDockingView.AddSplit(doc, "right");
         finished?.();
     }
     public jumpToDocument = async (
@@ -231,7 +231,7 @@ export class DocumentManager {
                     containerDoc._currentTimecode = targetTimecode;
                     const targetContext = await target?.context as Doc;
                     const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
-                    DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+                    DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished);
                 } else {
                     finished?.();
                 }
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index dd223390a..764a2547c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -156,7 +156,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                             googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
                             dataDoc.googleDoc = googleDoc;
                         }
-                        CollectionDockingView.AddRightSplit(googleDoc);
+                        CollectionDockingView.AddSplit(googleDoc, "right");
                     } else if (e.altKey) {
                         e.preventDefault();
                         window.open(googleDocUrl);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6c64a3714..c7a24133c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -188,7 +188,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
                 alias.context = undefined;
                 //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0]);
-                CollectionDockingView.AddRightSplit(alias);
+                CollectionDockingView.AddSplit(alias, "right");
             }
         }
         SelectionManager.DeselectAll();
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 2794a9f4e..db3e3d0dd 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -210,7 +210,7 @@ export class KeyManager {
                         return { stopPropagation: false, preventDefault: false };
                     }
                 }
-                MainView.Instance.mainFreeform && CollectionDockingView.AddRightSplit(MainView.Instance.mainFreeform);
+                MainView.Instance.mainFreeform && CollectionDockingView.AddSplit(MainView.Instance.mainFreeform, "right");
                 break;
             case "arrowleft":
                 if (document.activeElement) {
@@ -218,7 +218,7 @@ export class KeyManager {
                         return { stopPropagation: false, preventDefault: false };
                     }
                 }
-                MainView.Instance.mainFreeform && CollectionDockingView.CloseRightSplit(MainView.Instance.mainFreeform);
+                MainView.Instance.mainFreeform && CollectionDockingView.CloseSplit(MainView.Instance.mainFreeform);
                 break;
             case "backspace":
                 if (document.activeElement) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 6a960f4d5..7b0c6fa9f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -236,7 +236,7 @@ export class MainView extends React.Component {
                 reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized,
                     initialized => initialized && received && DocServer.GetRefField(received).then(docField => {
                         if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) {
-                            CollectionDockingView.AddRightSplit(docField);
+                            CollectionDockingView.AddSplit(docField, "right");
                         }
                     }),
                 );
@@ -260,7 +260,7 @@ export class MainView extends React.Component {
         }
         const pres = Docs.Create.PresDocument(new List<Doc>(),
             { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeStatus: "replaced", boxShadow: "0 0", system: true });
-        CollectionDockingView.AddRightSplit(pres);
+        CollectionDockingView.AddSplit(pres, "right");
         Doc.UserDoc().activePresentation = pres;
         const myPresentations = Doc.UserDoc().myPresentations as Doc;
         Doc.AddDocToList(myPresentations, "data", pres);
@@ -382,9 +382,9 @@ export class MainView extends React.Component {
     @computed get topOffset() { return (CollectionMenu.Instance?.Pinned ? 35 : 0) + Number(SEARCH_PANEL_HEIGHT.replace("px", "")); }
     flyoutWidthFunc = () => this.flyoutWidth;
     addDocTabFunc = (doc: Doc, where: string, libraryPath?: Doc[]): boolean => {
-        return where === "close" ? CollectionDockingView.CloseRightSplit(doc) :
+        return where === "close" ? CollectionDockingView.CloseSplit(doc) :
             doc.dockingConfig ? CurrentUserUtils.openDashboard(Doc.UserDoc(), doc) :
-                CollectionDockingView.AddRightSplit(doc);
+                CollectionDockingView.AddSplit(doc, "right");
     }
     sidebarScreenToLocal = () => new Transform(0, (CollectionMenu.Instance.Pinned ? -35 : 0) - Number(SEARCH_PANEL_HEIGHT.replace("px", "")), 1);
     mainContainerXf = () => this.sidebarScreenToLocal().translate(-58, 0);
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index e95b28d6c..6ac6571d3 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -160,7 +160,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                                 googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
                                 dataDoc.googleDoc = googleDoc;
                             }
-                            CollectionDockingView.AddRightSplit(googleDoc);
+                            CollectionDockingView.AddRight(googleDoc, "right");
                         } else if (e.altKey) {
                             e.preventDefault();
                             window.open(googleDocUrl);
@@ -343,7 +343,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 dv.toggleFollowLink("inPlace", true, false);
             } else if (value === "linkOnRight") {
                 dv.noOnClick();
-                dv.toggleFollowLink("onRight", false, false);
+                dv.toggleFollowLink("add:right", false, false);
             }
         });
     }
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index b382298f3..427748fe7 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -44,7 +44,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
             col._panX = NumCast(target.x) + NumCast(target._width) / 2;
             col._panY = NumCast(target.y) + NumCast(target._height) / 2;
         }
-        this.props.addDocTab(col, "inTab");
+        this.props.addDocTab(col, "add:right");
     }
 
     render() {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index fef0a3e00..d9ab5d029 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -258,7 +258,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     @computed get contexts() {
-        return !this.selectedDoc ? (null) : <PropertiesDocContextSelector Document={this.selectedDoc} hideTitle={true} addDocTab={(doc, where) => CollectionDockingView.AddRightSplit(doc)} />;
+        return !this.selectedDoc ? (null) : <PropertiesDocContextSelector Document={this.selectedDoc} hideTitle={true} addDocTab={(doc, where) => CollectionDockingView.AddSplit(doc, "right")} />;
     }
 
     previewBackground = () => "lightgrey";
diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx
index 4fb362ab1..e84022366 100644
--- a/src/client/views/animationtimeline/Keyframe.tsx
+++ b/src/client/views/animationtimeline/Keyframe.tsx
@@ -331,7 +331,7 @@ export class Keyframe extends React.Component<IProps> {
         }),
             TimelineMenu.Instance.addItem("button", "Show Data", action(() => {
                 const kvp = Docs.Create.KVPDocument(kf, { _width: 300, _height: 300 });
-                CollectionDockingView.AddRightSplit(kvp);
+                CollectionDockingView.AddSplit(kvp, "right");
             })),
             TimelineMenu.Instance.addItem("button", "Delete", action(() => {
                 (this.regiondata.keyframes as List<Doc>).splice(this.keyframes.indexOf(kf), 1);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 8301d3df8..62c865436 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,6 +1,6 @@
 import 'golden-layout/src/css/goldenlayout-base.css';
 import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { clamp } from 'lodash';
+import { clamp, pull } from 'lodash';
 import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
@@ -38,7 +38,7 @@ const _global = (window /* browser */ || global /* node */) as any;
 @observer
 export class CollectionDockingView extends CollectionSubView(doc => doc) {
     @observable public static Instance: CollectionDockingView;
-    public static makeDocumentConfig(document: Doc, isDisplayPanel?: boolean, width?: number) {
+    public static makeDocumentConfig(document: Doc, panelName?: string, width?: number) {
         return {
             type: 'react-component',
             component: 'DocumentFrameRenderer',
@@ -46,7 +46,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             width: width,
             props: {
                 documentId: document[Id],
-                isDisplayPanel   // flag for whether a tab should be considered a placeholder that has its contents replaced with new content
+                panelName   // name of tab that can be used to close or replace its contents
             }
         };
     }
@@ -85,26 +85,20 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     }
 
     @undoBatch
-    @action
-    public static CloseRightSplit(document: Opt<Doc>): boolean {
-        const tryClose = (childItem: any) => {
-            if (childItem.config?.component === "DocumentFrameRenderer") {
-                if ((!document && childItem.config.props.isDisplayPanel) || (document && childItem.config.props.documentId === document[Id])) {
-                    childItem.remove();
-                    return true;
-                }
+    public static CloseSplit(document: Opt<Doc>, panelName?: string): boolean {
+        const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find((tab) => panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document);
+        if (tab) {
+            const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
+            if (j !== -1) {
+                tab.header.parent.contentItems[j].remove();
+                return CollectionDockingView.Instance.layoutChanged();
             }
-            return false;
-        };
-        const retVal = !CollectionDockingView.Instance?._goldenLayout.root.contentItems[0].isRow ? false :
-            Array.from(CollectionDockingView.Instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => Array.from(child.contentItems).some(tryClose));
+        }
 
-        retVal && document && CollectionDockingView.Instance.layoutChanged();
-        return retVal;
+        return false;
     }
 
     @undoBatch
-    @action
     public static OpenFullScreen(doc: Doc, libraryPath?: Doc[]) {
         const instance = CollectionDockingView.Instance;
         if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") {
@@ -124,176 +118,94 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         return true;
     }
 
-    public static ReplaceRightSplit(document: Doc, libraryPath?: Doc[], addToSplit?: boolean): boolean {
-        if (!CollectionDockingView.Instance) return false;
-        const instance = CollectionDockingView.Instance;
-        const retVal = !instance._goldenLayout.root.contentItems[0].isRow ? false :
-            Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
-                if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
-                    child.contentItems[0].config.isDisplayPanel) {
-                    const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, true);
-                    child.addChild(newItemStackConfig, undefined);
-                    !addToSplit && child.contentItems[0].remove();
-                    return true;
-                }
-                return Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
-                    if (tab.config.props.isDisplayPanel) {
-                        const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, true);
-                        child.addChild(newItemStackConfig, undefined);
-                        !addToSplit && child.contentItems[j].remove();
-                        return true;
-                    }
-                    return false;
-                });
-            });
-        retVal && instance.layoutChanged();
-        return retVal;
-    }
-
-    //
-    //  Creates a vertical split on the right side of the docking view, and then adds the Document to the right of that split
-    //
     @undoBatch
-    @action
-    public static AddRightSplit(document: Doc, isDisplayPanel: Opt<boolean> = undefined) {
-        if (!CollectionDockingView.Instance) return false;
-
-        const ind = Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === document);
-        if (ind !== -1) {
-            const tab = Array.from(CollectionDockingView.Instance.tabMap.keys())[ind];
-            const activeContentItem = tab.header.parent.getActiveContentItem();
-            if (tab.contentItem !== activeContentItem) {
-                tab.header.parent.setActiveContentItem(tab.contentItem);
-            }
-            tab.setActive(true);
-        } else {
-            if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document);
-            const instance = CollectionDockingView.Instance;
-            const newItemStackConfig = {
-                type: 'stack',
-                content: [CollectionDockingView.makeDocumentConfig(document, isDisplayPanel)]
-            };
-
-            const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
-
-            if (instance._goldenLayout.root.contentItems.length === 0) {
-                instance._goldenLayout.root.addChild(newContentItem);
-            } else if (instance._goldenLayout.root.contentItems[0].isRow) {
-                instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
-            } else {
-                const collayout = instance._goldenLayout.root.contentItems[0];
-                const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
-                collayout.parent.replaceChild(collayout, newRow);
-
-                newRow.addChild(newContentItem, undefined, true);
-                newRow.addChild(collayout, 0, true);
-
-                collayout.config.width = 50;
-                newContentItem.config.width = 50;
-            }
-            newContentItem.callDownwards('_$init');
-            instance.layoutChanged();
+    public static ReplaceTab(document: Doc, panelName: string, stack: any, addToSplit?: boolean): boolean {
+        const instance = CollectionDockingView.Instance;
+        if (!instance) return false;
+        const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
+        if (!panelName && stack) {
+            const activeContentItemIndex = stack.contentItems.findIndex((item: any) => item.config === stack._activeContentItem.config);
+            const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout);
+            stack.addChild(newContentItem.contentItems[0], undefined);
+            stack.contentItems[activeContentItemIndex].remove();
+            return CollectionDockingView.Instance.layoutChanged();
         }
-        return true;
+        const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find((tab) => tab.contentItem.config.props.panelName === panelName);
+        if (tab) {
+            tab.header.parent.addChild(newConfig, undefined);
+            const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
+            !addToSplit && j !== -1 && tab.header.parent.contentItems[j].remove();
+            return CollectionDockingView.Instance.layoutChanged();
+        }
+        return CollectionDockingView.AddSplit(document, panelName, stack, panelName);
     }
 
-
     //
     //  Creates a split on any side of the docking view based on the passed input pullSide and then adds the Document to the requested side
     //
-    @action
-    public static AddSplit(document: Doc, pullSide: string, libraryPath?: Doc[]) {
-        if (!CollectionDockingView.Instance) return false;
+    @undoBatch
+    public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) {
         const instance = CollectionDockingView.Instance;
-        const newItemStackConfig = {
-            type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(document, undefined)]
-        };
+        if (!instance) return false;
+        const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
 
-        const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
-
-        if (instance._goldenLayout.root.contentItems.length === 0) { // if no rows / columns
-            instance._goldenLayout.root.addChild(newContentItem);
-        } else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row
-            if (pullSide === "left") {
-                instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0);
-            } else if (pullSide === "right") {
-                instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
-            } else if (pullSide === "top" || pullSide === "bottom") {
-                // if not going in a row layout, must add already existing content into column
-                const rowlayout = instance._goldenLayout.root.contentItems[0];
-                const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout);
-                rowlayout.parent.replaceChild(rowlayout, newColumn);
-                if (pullSide === "top") {
-                    newColumn.addChild(rowlayout, undefined, true);
-                    newColumn.addChild(newContentItem, 0, true);
-                } else if (pullSide === "bottom") {
-                    newColumn.addChild(newContentItem, undefined, true);
-                    newColumn.addChild(rowlayout, 0, true);
+        if (!pullSide && stack) {
+            stack.addChild(docContentConfig, undefined);
+        } else {
+            const newItemStackConfig = { type: 'stack', content: [docContentConfig] };
+            const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+            if (instance._goldenLayout.root.contentItems.length === 0) { // if no rows / columns
+                instance._goldenLayout.root.addChild(newContentItem);
+            } else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row
+                switch (pullSide) {
+                    default:
+                    case "right": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break;
+                    case "left": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break;
+                    case "top":
+                    case "bottom":
+                        // if not going in a row layout, must add already existing content into column
+                        const rowlayout = instance._goldenLayout.root.contentItems[0];
+                        const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout);
+                        rowlayout.parent.replaceChild(rowlayout, newColumn);
+                        if (pullSide === "top") {
+                            newColumn.addChild(rowlayout, undefined, true);
+                            newColumn.addChild(newContentItem, 0, true);
+                        } else if (pullSide === "bottom") {
+                            newColumn.addChild(newContentItem, undefined, true);
+                            newColumn.addChild(rowlayout, 0, true);
+                        }
+
+                        rowlayout.config.height = 50;
+                        newContentItem.config.height = 50;
                 }
-
-                rowlayout.config.height = 50;
-                newContentItem.config.height = 50;
-            }
-        } else if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
-            if (pullSide === "top") {
-                instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0);
-            } else if (pullSide === "bottom") {
-                instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
-            } else if (pullSide === "left" || pullSide === "right") {
-                // if not going in a row layout, must add already existing content into column
-                const collayout = instance._goldenLayout.root.contentItems[0];
-                const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
-                collayout.parent.replaceChild(collayout, newRow);
-
-                if (pullSide === "left") {
-                    newRow.addChild(collayout, undefined, true);
-                    newRow.addChild(newContentItem, 0, true);
-                } else if (pullSide === "right") {
-                    newRow.addChild(newContentItem, undefined, true);
-                    newRow.addChild(collayout, 0, true);
+            } else if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
+                switch (pullSide) {
+                    case "top": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break;
+                    case "bottom": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break;
+                    case "left":
+                    case "right":
+                    default:
+                        // if not going in a row layout, must add already existing content into column
+                        const collayout = instance._goldenLayout.root.contentItems[0];
+                        const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
+                        collayout.parent.replaceChild(collayout, newRow);
+
+                        if (pullSide === "left") {
+                            newRow.addChild(collayout, undefined, true);
+                            newRow.addChild(newContentItem, 0, true);
+                        } else {
+                            newRow.addChild(newContentItem, undefined, true);
+                            newRow.addChild(collayout, 0, true);
+                        }
+
+                        collayout.config.width = 50;
+                        newContentItem.config.width = 50;
                 }
-
-                collayout.config.width = 50;
-                newContentItem.config.width = 50;
             }
+            newContentItem.callDownwards('_$init');
         }
 
-        newContentItem.callDownwards('_$init');
-        instance.layoutChanged();
-        return true;
-    }
-
-    //
-    //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
-    //
-    @undoBatch
-    public static UseRightSplit(document: Doc, libraryPath?: Doc[], shiftKey?: boolean) {
-        if (shiftKey || !CollectionDockingView.ReplaceRightSplit(document, libraryPath, shiftKey)) {
-            return CollectionDockingView.AddRightSplit(document, true);
-        }
-        return false;
-    }
-
-    @undoBatch
-    public static AddTab(stack: any, document: Doc, libraryPath?: Doc[]) {
-        const instance = CollectionDockingView.Instance;
-        const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined);
-        if (stack === undefined) {
-            stack = instance._goldenLayout.root;
-            while (!stack.isStack) {
-                if (stack.contentItems.length) {
-                    stack = stack.contentItems[0];
-                } else {
-                    stack.addChild({ type: 'stack', content: [docContentConfig] });
-                    stack = undefined;
-                    break;
-                }
-            }
-        }
-        stack?.addChild(docContentConfig, undefined);
-        instance.layoutChanged();
-        return true;
+        return instance.layoutChanged();
     }
 
     @undoBatch
@@ -303,6 +215,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         this._goldenLayout.emit('stateChanged');
         this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         this.stateChanged();
+        return true;
     }
 
     async setupGoldenLayout() {
@@ -450,7 +363,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
     }
     tabCreated = (tab: any) => {
-        tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab(tab);  // have to explicitly initialize tabs that reuse contents from previous abs (ie, when dragging a tab around a new tab is created for the old content)
+        tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab);  // have to explicitly initialize tabs that reuse contents from previous abs (ie, when dragging a tab around a new tab is created for the old content)
     }
 
     stackCreated = (stack: any) => {
@@ -458,9 +371,9 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             if (e.target === stack.header.element[0] && e.button === 2) {
                 const emptyPane = CurrentUserUtils.EmptyPane;
                 emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
-                CollectionDockingView.AddTab(stack, Docs.Create.FreeformDocument([], {
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], {
                     _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
-                }));
+                }), "", stack);
             }
         });
 
@@ -477,9 +390,9 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                 // stack.config.fixed = !stack.config.fixed;  // force the stack to have a fixed size
                 const emptyPane = CurrentUserUtils.EmptyPane;
                 emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
-                CollectionDockingView.AddTab(stack, Docs.Create.FreeformDocument([], {
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], {
                     _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
-                }));
+                }), "", stack);
             }));
     }
 
@@ -601,7 +514,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 Doc.AddDocToList(curPres, "data", pinDoc);
                 if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
                 if (!DocumentManager.Instance.getDocumentView(curPres)) {
-                    CollectionDockingView.AddRightSplit(curPres);
+                    CollectionDockingView.AddSplit(curPres, "right");
                 }
                 DocumentManager.Instance.jumpToDocument(doc, false, undefined, Cast(doc.context, Doc, null));
             }
@@ -688,17 +601,26 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     get previewPanelCenteringOffset() { return this.nativeWidth() ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
     get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this._panelWidth * 100}% ` : undefined; }
 
+    // adds a tab to the layout based on the locaiton parameter which can be:
+    //  close[:{left,right,top,bottom}]  - e.g., "close" will close the tab, "close:left" will close the left tab, 
+    //  add[:{left,right,top,bottom}] - e.g., "add" will add a tab to the current stack, "add:right" will add a tab on the right
+    //  replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents, 
+    //                                  "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name, 
+    //                                   "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
+    //  inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack
     addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => {
         SelectionManager.DeselectAll();
         if (doc._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
-        switch (location) {
-            case "onRight": return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === doc) !== -1 ?
-                CollectionDockingView.CloseRightSplit(doc) : CollectionDockingView.AddRightSplit(doc);
-            case "close": return CollectionDockingView.CloseRightSplit(doc);
-            case "replace": return CollectionDockingView.UseRightSplit(doc);
+        const locationFields = location.split(":");
+        const locationParams = locationFields.length > 1 ? locationFields[1] : "";
+        switch (locationFields[0]) {
+            case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
             case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
+            case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
             case "inPlace":
-            default: return CollectionDockingView.AddTab(this.stack, doc);
+            case "add":
+            default: return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === doc) !== -1 ?
+                CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, locationParams, this.stack);
         }
     }
 
@@ -830,6 +752,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             </div >);
     }
 }
-Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc); },
+Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddSplit(doc, "right"); },
     "opens up the inputted document on the right side of the screen", "(doc: any)");
-Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.UseRightSplit(doc, undefined, shiftKey); });
+Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); });
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 18ca29aaa..0d3a951cf 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -370,7 +370,7 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
                         })}
                     />
                 </div >
-                <div onClick={() => this._doc && this.props.addDocTab(this._doc, "onRight")} className="collectionSchemaView-cellContents-docButton">
+                <div onClick={() => this._doc && this.props.addDocTab(this._doc, "add:right")} className="collectionSchemaView-cellContents-docButton">
                     <FontAwesomeIcon icon="external-link-alt" size="lg" />
                 </div>
             </div>;
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index a3475b481..383a9312f 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -246,7 +246,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
                         <div className="row-dragger">
                             <div className="row-option" style={{ left: 5 }} onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div>
                             <div className="row-option" style={{ cursor: "grab", left: 25 }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div>
-                            <div className="row-option" style={{ left: 40 }} onClick={() => this.props.addDocTab(this.props.rowInfo.original, "onRight")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
+                            <div className="row-option" style={{ left: 40 }} onClick={() => this.props.addDocTab(this.props.rowInfo.original, "add:right")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
                         </div>
                         {children}
                     </ReactTableDefaults.TrComponent>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index fffbe65a3..80a6afba7 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -117,7 +117,7 @@ class TreeView extends React.Component<TreeViewProps> {
             Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey]));
     }
 
-    @undoBatch openRight = () => this.props.addDocTab(this.doc, "onRight");
+    @undoBatch openRight = () => this.props.addDocTab(this.doc, "add:right");
     @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
         return this.doc !== target && this.props.removeDoc?.(doc) === true && addDoc(doc);
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 1da0e601a..64ad3abbf 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -305,7 +305,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             this.setupViewTypes("UI Controls...", vtype => {
                 const newRendition = Doc.MakeAlias(this.props.Document);
                 newRendition._viewType = vtype;
-                this.props.addDocTab(newRendition, "onRight");
+                this.props.addDocTab(newRendition, "add:right");
                 return newRendition;
             }, false);
 
@@ -313,10 +313,10 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             const optionItems = options && "subitems" in options ? options.subitems : [];
             !Doc.UserDoc().noviceMode ? optionItems.splice(0, 0, { description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }) : null;
             if (this.props.Document.childLayout instanceof Doc) {
-                optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
+                optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "add:right"), icon: "project-diagram" });
             }
             if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
-                optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
+                optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" });
             }
             !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
@@ -331,7 +331,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => {
                     const alias = Doc.MakeAlias(this.props.Document);
                     DocUtils.makeCustomViewClicked(alias, undefined, func.key);
-                    this.props.addDocTab(alias, "onRight");
+                    this.props.addDocTab(alias, "add:right");
                 }
             }));
             DocListCast(Cast(Doc.UserDoc()["clickFuncs-child"], Doc, null).data).forEach(childClick =>
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index 2fe00a245..1fb7aa04a 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -545,7 +545,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     }
 
     onOpenClick = () => {
-        this._showDoc && this.props.addDocTab(this._showDoc, "onRight");
+        this._showDoc && this.props.addDocTab(this._showDoc, "add:right");
     }
 
     getPreviewTransform = (): Transform => {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 759d142ea..1aa30fc02 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -76,7 +76,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY);
         if (e.key === "?") {
             cm.setDefaultItem("?", (str: string) => this.props.addDocTab(
-                Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 850, isAnnotating: false, title: "bing", UseCors: true }), "onRight"));
+                Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 850, isAnnotating: false, title: "bing", UseCors: true }), "add:right"));
 
             cm.displayMenu(this._downX, this._downY);
             e.stopPropagation();
@@ -400,7 +400,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             Doc.AddDocToList(curPres, "data", pinDoc);
             if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
             if (!DocumentManager.Instance.getDocumentView(curPres)) {
-                CollectionDockingView.AddRightSplit(curPres);
+                CollectionDockingView.AddSplit(curPres, "right");
             }
             if (e instanceof KeyboardEvent ? e.key === "c" : true) {
                 const x = this.Bounds.left + this.Bounds.width / 2;
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 8e391f995..c9c2e657b 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -376,21 +376,33 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
                         Default
                         </div>
                     <div className="linkEditor-followingDropdown-option"
-                        onPointerDown={() => this.changeFollowBehavior("onRight")}>
-                        Always open in a new pane
+                        onPointerDown={() => this.changeFollowBehavior("add:left")}>
+                        Always open in new left pane
                         </div>
                     <div className="linkEditor-followingDropdown-option"
-                        onPointerDown={() => this.changeFollowBehavior("replace")}>
+                        onPointerDown={() => this.changeFollowBehavior("add:right")}>
+                        Always open in new right pane
+                        </div>
+                    <div className="linkEditor-followingDropdown-option"
+                        onPointerDown={() => this.changeFollowBehavior("replace:right")}>
                         Always replace right tab
                         </div>
+                    <div className="linkEditor-followingDropdown-option"
+                        onPointerDown={() => this.changeFollowBehavior("replace:left")}>
+                        Always replace left tab
+                        </div>
                     <div className="linkEditor-followingDropdown-option"
                         onPointerDown={() => this.changeFollowBehavior("fullScreen")}>
                         Always open full screen
                         </div>
                     <div className="linkEditor-followingDropdown-option"
-                        onPointerDown={() => this.changeFollowBehavior("inTab")}>
+                        onPointerDown={() => this.changeFollowBehavior("add")}>
                         Always open in a new tab
                         </div>
+                    <div className="linkEditor-followingDropdown-option"
+                        onPointerDown={() => this.changeFollowBehavior("replace")}>
+                        Replace Tab
+                        </div>
                     {this.props.linkDoc.linksToAnnotation ?
                         <div className="linkEditor-followingDropdown-option"
                             onPointerDown={() => this.changeFollowBehavior("openExternal")}>
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index a77122456..05b6b584d 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -167,7 +167,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                 });
             }
         } else {
-            DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
+            DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "add:right"), false);
         }
 
         linkDoc.linksToAnnotation && Hypothesis.scrollToAnnotation(StrCast(this.props.linkDoc.annotationId), this.props.destinationDoc);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2d03c5154..48a1688a6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -180,10 +180,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
         RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15);
 
-        // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 });
+        // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "map-pin", selected: -1 });
         const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
         (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 });
-        // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 });
+        // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 });
         RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
         RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 });
 
@@ -323,7 +323,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                                     fullScreenDoc = Doc.MakeAlias(this.props.Document);
                                     fullScreenDoc.layoutKey = "layout_fullScreen";
                                 }
-                                this.props.addDocTab(fullScreenDoc, "inTab");
+                                this.props.addDocTab(fullScreenDoc, "add");
                             }, "double tap");
                             SelectionManager.DeselectAll();
                         }
@@ -351,7 +351,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 500);
                 } else clickFunc();
             } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
-                this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "onRight");
+                this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "add:right");
             } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
                 this.allLinks.length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
             } else {
@@ -748,7 +748,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
         const appearance = cm.findByDescription("UI Controls...");
         const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
-        templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
+        templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
         //DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
         !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
 
@@ -766,7 +766,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "concierge-bell" });
             onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
             onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" });
-            !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("onRight", false, false), icon: "link" });
+            !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("add:right", false, false), icon: "link" });
             onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" });
             onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" });
             onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" });
@@ -810,8 +810,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const help = cm.findByDescription("Help...");
         const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
-        !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
-        helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
+        !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
+        helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
         helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
         cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
 
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index bc5abb0a4..87142babd 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -39,7 +39,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
 
     showTemplate = (): void => {
         const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
-        dragFactory && this.props.addDocTab(dragFactory, "onRight");
+        dragFactory && this.props.addDocTab(dragFactory, "add:right");
     }
     dragAsTemplate = (): void => {
         this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index b732f5f83..c5ff42a1a 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -230,7 +230,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         openItems.push({
             description: "Default Perspective", event: () => {
                 this.props.addDocTab(this.props.Document, "close");
-                this.props.addDocTab(this.fieldDocToLayout, "onRight");
+                this.props.addDocTab(this.fieldDocToLayout, "add:right");
             }, icon: "image"
         });
         !open && cm.addItem({ description: "Change Perspective...", subitems: openItems, icon: "external-link-alt" });
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 6dd85b7f5..74d10d087 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 }), "onRight"), icon: "layer-group" });
+            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
             ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
         }
     }
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 8e5ee09ff..10b6aa02e 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -73,7 +73,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
             anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
             if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
                 this._timeout = setTimeout(action(() => {
-                    DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, e.altKey ? "inTab" : "onRight")), false);
+                    DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "add:right")), false);
                     this._editing = false;
                 }), 300 - (Date.now() - this._lastTap));
             }
@@ -87,14 +87,14 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
     }
 
     openLinkDocOnRight = (e: React.MouseEvent) => {
-        this.props.addDocTab(this.rootDoc, "onRight");
+        this.props.addDocTab(this.rootDoc, "add:right");
     }
     openLinkTargetOnRight = (e: React.MouseEvent) => {
         const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null));
         alias.isLinkButton = undefined;
         alias._isBackground = undefined;
         alias.layoutKey = "layout";
-        this.props.addDocTab(alias, "onRight");
+        this.props.addDocTab(alias, "add:right");
     }
     @action
     openLinkEditor = action((e: React.MouseEvent) => {
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index f154c5f56..e174a95af 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -40,7 +40,7 @@ export class LinkDocPreview extends React.Component<Props> {
     async followDefault() {
         DocumentLinksButton.EditLink = undefined;
         LinkDocPreview.LinkInfo = undefined;
-        this._targetDoc ? DocumentManager.Instance.FollowLink(this.props.linkDoc, this._targetDoc, doc => this.props.addDocTab(doc, "onRight"), false) : null;
+        this._targetDoc ? DocumentManager.Instance.FollowLink(this.props.linkDoc, this._targetDoc, doc => this.props.addDocTab(doc, "add:right"), false) : null;
     }
 
     componentDidUpdate() { this.updatePreview(); }
@@ -69,9 +69,9 @@ export class LinkDocPreview extends React.Component<Props> {
     pointerDown = (e: React.PointerEvent) => {
         if (this.props.linkDoc && this.props.linkSrc) {
             DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.linkSrc,
-                (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+                (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation));
         } else if (this.props.href) {
-            this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, UseCors: true }), "onRight");
+            this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, UseCors: true }), "add:right");
         }
     }
     width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225));
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index f60c521ad..a03c45a53 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -238,7 +238,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
 
         // If openDocument is selected then it should open the document for the user
         if (activeItem.openDocument) {
-            this.props.addDocTab(activeItem, "replace");
+            this.props.addDocTab(activeItem, "replace:right");
         } else
             //docToJump stayed same meaning, it was not in the group or was the last element in the group
             if (activeItem.zoomProgressivize && this.rootDoc.presStatus !== 'edit') {
@@ -443,7 +443,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         if (this.layoutDoc.inOverlay) {
             this.layoutDoc.presStatus = 'edit';
             Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
-            CollectionDockingView.AddRightSplit(this.rootDoc);
+            CollectionDockingView.AddSplit(this.rootDoc, "right");
             this.layoutDoc.inOverlay = false;
         } else if (this.layoutDoc.context && docView) {
             this.layoutDoc.presStatus = 'manual';
@@ -1115,7 +1115,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                 DockedFrameRenderer.PinDoc(doc, false);
                 this.gotoDocument(this.childDocs.length, this.itemIndex);
             } else {
-                this.props.addDocTab(doc, "onRight");
+                this.props.addDocTab(doc, "add:right");
             }
         }
     }
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 578061344..d50a10bdd 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -82,7 +82,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     @action public FullScreen() {
         this._fullScreen = true;
         this.player && this.player.requestFullscreen();
-        this._youtubePlayer && this.props.addDocTab(this.rootDoc, "inTab");
+        this._youtubePlayer && this.props.addDocTab(this.rootDoc, "add");
     }
 
     choosePath(url: string) {
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 924079096..f2658e77e 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -169,7 +169,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     onPointerDownEnumerables = async (e: any) => {
         e.stopPropagation();
         const collview = await DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]);
-        collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight");
+        collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "add:right");
     }
 
 
@@ -191,7 +191,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
             list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb"));
             list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
             alias._pivotField = this._fieldKey.startsWith("#") ? "#" : this._fieldKey;
-            this.props.tbox.props.addDocTab(alias, "onRight");
+            this.props.tbox.props.addDocTab(alias, "add:right");
         }
     }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 356e36b76..c49e38f72 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -222,7 +222,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
                     const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
                     const allLinks = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }];
-                    const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "onRight", title: value });
+                    const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "add:right", title: value });
                     const mval = this._editorView.state.schema.marks.metadataVal.create();
                     const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
                     tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -465,7 +465,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const linkDoc = data.linkDocument!;
         const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-";
         const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : "";
-        this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id);
+        this.makeLinkToSelection(linkDoc[Id], anchor1Title, "add:right", anchor1Id);
     }
 
     getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
@@ -811,7 +811,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 if (linkDoc) {
                     const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
                     const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : "";
-                    this.makeLinkToSelection(linkDoc[Id], anchor2Title, "onRight", anchor2Id);
+                    this.makeLinkToSelection(linkDoc[Id], anchor2Title, "add:right", anchor2Id);
                 }
             },
             { fireImmediately: true }
@@ -1096,7 +1096,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const marks = [...node.marks];
             const linkIndex = marks.findIndex(mark => mark.type.name === "link");
             const allLinks = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }];
-            const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "onRight", title, docref: true });
+            const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true });
             marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
             return node.mark(marks);
         }
@@ -1243,10 +1243,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         FormattedTextBoxComment.Hide();
         if (FormattedTextBoxComment.linkDoc) {
             if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+                this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
             } else {
                 DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document,
-                    (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+                    (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation));
             }
         }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 55d225adc..f41f03620 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -103,7 +103,7 @@ export class FormattedTextBoxComment {
                             this.deleteLink();
                         } else if (FormattedTextBoxComment._followRef && FormattedTextBoxComment._followRef.contains(e.target as any)) {
                             if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                                textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+                                textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
                             } else {
                                 const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc)), textBox.dataDoc) ?
                                     Cast(FormattedTextBoxComment.linkDoc.anchor2, Doc) : (Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc))
@@ -112,28 +112,28 @@ export class FormattedTextBoxComment {
 
                                 if (FormattedTextBoxComment.linkDoc.follow) {
                                     if (FormattedTextBoxComment.linkDoc.follow === "Default") {
-                                        DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false);
+                                        DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "add:right"), false);
                                     } else if (FormattedTextBoxComment.linkDoc.follow === "Always open in right tab") {
-                                        if (target) { textBox.props.addDocTab(target, "onRight"); }
+                                        if (target) { textBox.props.addDocTab(target, "add:right"); }
                                     } else if (FormattedTextBoxComment.linkDoc.follow === "Always open in new tab") {
-                                        if (target) { textBox.props.addDocTab(target, "inTab"); }
+                                        if (target) { textBox.props.addDocTab(target, "add"); }
                                     }
                                 } else {
-                                    DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false);
+                                    DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "add:right"), false);
                                 }
                             }
                         } else {
                             if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                                textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+                                textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
                             } else {
                                 DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
-                                    (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+                                    (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation));
                             }
                         }
 
                     }
                 } 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, UseCors: true }), "onRight");
+                    textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, UseCors: true }), "add:right");
                 }
                 keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
                     FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index a65eea50f..c6bacc1a8 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -104,7 +104,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     //Command to create a new Tab with a PDF of all the command shortcuts
     bind("Mod-/", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _fitWidth: true, _width: 300, _height: 300 });
-        props.addDocTab(newDoc, "onRight");
+        props.addDocTab(newDoc, "add:right");
     });
 
     //Commands to modify BlockType
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 399f72608..307238ea1 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -810,7 +810,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
             <div className="dropdown link-menu">
                 <p>Linked to:</p>
                 <input value={link} placeholder="Enter URL" onChange={onLinkChange} />
-                <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button>
+                <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "add:right")}>Apply hyperlink</button>
                 <div className="divider"></div>
                 <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
             </div>;
@@ -857,7 +857,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
     // TODO: should check for valid URL
     @undoBatch
     makeLinkToURL = (target: string, lcoation: string) => {
-        ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRight", "", target);
+        ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRadd:rightight", "", target);
     }
 
     @undoBatch
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 7e632a0ee..3fadfe842 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -279,7 +279,7 @@ export class RichTextRules {
                                 DocUtils.Publish(target, docid, returnFalse, returnFalse);
                                 DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to");
                             });
-                            const link = state.schema.marks.linkAnchor.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
+                            const link = state.schema.marks.linkAnchor.create({ href: Utils.prepend("/doc/" + docid), location: "add:right", title: docid, targetId: docid });
                             return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link);
                         }
                         return state.tr;
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 1616500f6..64f7d27e5 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -148,7 +148,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             alt: { default: null },
             title: { default: null },
             float: { default: "left" },
-            location: { default: "onRight" },
+            location: { default: "add:right" },
             docid: { default: "" }
         },
         group: "inline",
@@ -177,7 +177,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             height: { default: 100 },
             title: { default: null },
             float: { default: "right" },
-            location: { default: "onRight" },
+            location: { default: "add:right" },
             hidden: { default: false },
             fieldKey: { default: "" },
             docid: { default: "" },
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 98638ecc2..a33068a18 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -99,7 +99,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
         else if (e.button === 0) {
             const annoGroup = await Cast(this.props.document.group, Doc);
             if (annoGroup) {
-                DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation), false, undefined);
+                DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation), false, undefined);
                 e.stopPropagation();
             }
         }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index ed33ac822..d89e66ec5 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -604,7 +604,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
                     if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
                         const link = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
                         annotationDoc.isLinkButton = true;
-                        if (link) link.followLinkLocation = "onRight";
+                        if (link) link.followLinkLocation = "add:right";
                     }
                 }
             });
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index a590c88c4..0b5f14d74 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -278,7 +278,7 @@ export namespace RichTextUtils {
             } else {
                 docid = backingDocId;
             }
-            return schema.node("image", { src, agnostic, width, docid, float: null, location: "onRight" });
+            return schema.node("image", { src, agnostic, width, docid, float: null, location: "add:right" });
         };
 
         const textNode = (schema: any, run: docs_v1.Schema$TextRun) => {
-- 
cgit v1.2.3-70-g09d2


From 442572a66aaa6b50d8d01d46d79b7d6cf2f247a8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 29 Aug 2020 17:42:48 -0400
Subject: fixed maximize docdecoration to toggle target w/o making an alias.
 use ctrlKey to make an alias.  removed dashboardBrush stuff.  cleaned up
 DocDecoartions css slightly

---
 src/client/util/CurrentUserUtils.ts                |  9 ++-
 src/client/views/DocumentDecorations.scss          | 84 ++++++++++++----------
 src/client/views/DocumentDecorations.tsx           | 82 +++++++++------------
 src/client/views/MainView.tsx                      | 17 +++--
 .../views/collections/CollectionDockingView.tsx    | 13 ++--
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../CollectionFreeFormLayoutEngines.tsx            |  3 +-
 .../collectionGrid/CollectionGridView.scss         | 11 ---
 src/client/views/nodes/DocumentView.tsx            |  7 +-
 .../views/nodes/formattedText/DashDocView.tsx      |  5 +-
 .../views/nodes/formattedText/RichTextSchema.tsx   |  5 +-
 src/client/views/search/SearchBox.tsx              |  2 +-
 13 files changed, 117 insertions(+), 125 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 20f8fe9c1..2ca395164 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1020,7 +1020,7 @@ export class CurrentUserUtils {
     }
 
     public static snapshotDashboard = (userDoc: Doc) => {
-        const copy = CollectionDockingView.Copy(Cast(userDoc.activeDashboard, Doc, null));
+        const copy = CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard);
         Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy);
         CurrentUserUtils.openDashboard(userDoc, copy);
     }
@@ -1031,7 +1031,11 @@ export class CurrentUserUtils {
         const dashboards = Cast(userDoc.myDashboards, Doc) as Doc;
         const dashboardCount = DocListCast(dashboards.data).length + 1;
         const emptyPane = Cast(userDoc.emptyPane, Doc, null);
-        emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
+        try {
+            emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
+        } catch (e) {
+            console.log(e)
+        }
         const freeformOptions: DocumentOptions = {
             x: 0,
             y: 400,
@@ -1054,6 +1058,7 @@ export class CurrentUserUtils {
         CurrentUserUtils.openDashboard(userDoc, dashboardDoc);
     }
 
+    public static get ActiveDashboard() { return Cast(Doc.UserDoc().activeDashboard, Doc, null); }
     public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); }
     public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); }
     public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); }
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index b5c32ca1d..8291d7212 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -16,21 +16,11 @@ $linkGap : 3px;
     grid-template-columns: 8px 16px 1fr 8px 8px;
     pointer-events: none;
 
-    #documentDecorations-centerCont {
+    .documentDecorations-centerCont {
         grid-column: 3;
         background: none;
     }
 
-    .documentDecorations-resizer {
-        pointer-events: auto;
-        background: $alt-accent;
-        opacity: 0.1;
-    }
-
-    .documentDecorations-resizer:hover {
-        opacity: 1;
-    }
-
     .documentDecorations-selector {
         pointer-events: auto;
         height: 15px;
@@ -49,27 +39,43 @@ $linkGap : 3px;
         grid-row: 4;
     }
 
-    #documentDecorations-topLeftResizer,
-    #documentDecorations-leftResizer,
-    #documentDecorations-bottomLeftResizer {
+    .documentDecorations-topLeftResizer,
+    .documentDecorations-topRightResizer,
+    .documentDecorations-bottomLeftResizer,
+    .documentDecorations-bottomRightResizer,
+    .documentDecorations-leftResizer,
+    .documentDecorations-topResizer,
+    .documentDecorations-bottomResizer,
+    .documentDecorations-rightResizer {
+        pointer-events: auto;
+        background: $alt-accent;
+        opacity: 0.1;
+        &:hover {
+            opacity: 1;
+        }
+    }
+
+    .documentDecorations-topLeftResizer,
+    .documentDecorations-leftResizer,
+    .documentDecorations-bottomLeftResizer {
         grid-column: 1
     }
 
-    #documentDecorations-topResizer,
-    #documentDecorations-bottomResizer {
+    .documentDecorations-topResizer,
+    .documentDecorations-bottomResizer {
         grid-column-start: 2;
         grid-column-end: 5;
     }
 
-    #documentDecorations-bottomRightResizer,
-    #documentDecorations-topRightResizer,
-    #documentDecorations-rightResizer {
+    .documentDecorations-bottomRightResizer,
+    .documentDecorations-topRightResizer,
+    .documentDecorations-rightResizer {
         grid-column-start: 5;
         grid-column-end: 7;
     }
 
-    #documentDecorations-rotation,
-    #documentDecorations-borderRadius {
+    .documentDecorations-rotation,
+    .documentDecorations-borderRadius {
         grid-column: 5;
         grid-row: 4;
         border-radius: 100%;
@@ -87,69 +93,69 @@ $linkGap : 3px;
             position: absolute;
         }
     }
-    #documentDecorations-rotation {
+    .documentDecorations-rotation {
         background: transparent;
         right: -15;
     }
 
 
-    #documentDecorations-topLeftResizer,
-    #documentDecorations-bottomRightResizer {
+    .documentDecorations-topLeftResizer,
+    .documentDecorations-bottomRightResizer {
         cursor: nwse-resize;
         background: unset;
         opacity: 1;
     }
 
-    #documentDecorations-topLeftResizer {
+    .documentDecorations-topLeftResizer {
         border-left: 2px solid;
         border-top: solid 2px;
     }
 
-    #documentDecorations-bottomRightResizer {
+    .documentDecorations-bottomRightResizer {
         border-right: 2px solid;
         border-bottom: solid 2px;
     }
 
-    #documentDecorations-topLeftResizer:hover,
-    #documentDecorations-bottomRightResizer:hover {
+    .documentDecorations-topLeftResizer:hover,
+    .documentDecorations-bottomRightResizer:hover {
         opacity: 1;
     }
 
-    #documentDecorations-bottomRightResizer {
+    .documentDecorations-bottomRightResizer {
         grid-row: 4;
     }
 
-    #documentDecorations-topRightResizer,
-    #documentDecorations-bottomLeftResizer {
+    .documentDecorations-topRightResizer,
+    .documentDecorations-bottomLeftResizer {
         cursor: nesw-resize;
         background: unset;
         opacity: 1;
     }
 
-    #documentDecorations-topRightResizer {
+    .documentDecorations-topRightResizer {
         border-right: 2px solid;
         border-top: 2px solid;
     }
 
-    #documentDecorations-bottomLeftResizer {
+    .documentDecorations-bottomLeftResizer {
         border-left: 2px solid;
         border-bottom: 2px solid;
     }
 
-    #documentDecorations-topRightResizer:hover,
-    #documentDecorations-bottomLeftResizer:hover {
+    .documentDecorations-topRightResizer:hover,
+    .documentDecorations-bottomLeftResizer:hover {
         cursor: nesw-resize;
         background: dimGray;
         opacity: 1;
     }
 
-    #documentDecorations-topResizer,
-    #documentDecorations-bottomResizer {
+    .documentDecorations-topResizer,
+    .documentDecorations-bottomResizer {
         cursor: ns-resize;
     }
 
-    #documentDecorations-leftResizer,
-    #documentDecorations-rightResizer {
+    .documentDecorations-leftResizer,
+    .documentDecorations-rightResizer {
         cursor: ew-resize;
     }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c7a24133c..6db5186ba 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -24,6 +24,7 @@ import { FormatShapePane } from './FormatShapePane';
 import { DocumentView } from "./nodes/DocumentView";
 import React = require("react");
 import e = require('express');
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
 
 @observer
 export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -177,7 +178,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
     @action
     onMaximizeDown = (e: React.PointerEvent): void => {
-        setupMoveUpEvents(this, e, (e, d) => false, (e) => { }, this.onMaximizeClick);
+        if (e.ctrlKey) {
+            const selectedDocs = SelectionManager.SelectedDocuments();
+            const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
+            alias.context = undefined;
+            //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0]);
+            CollectionDockingView.AddSplit(alias, "right");
+            e.stopPropagation();
+            e.preventDefault();
+        } else {
+            setupMoveUpEvents(this, e, (e, d) => false, (e) => { }, this.onMaximizeClick);
+        }
     }
     @undoBatch
     @action
@@ -185,10 +196,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (e.button === 0) {
             const selectedDocs = SelectionManager.SelectedDocuments();
             if (selectedDocs.length) {
-                const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
-                alias.context = undefined;
-                //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0]);
-                CollectionDockingView.AddSplit(alias, "right");
+                CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
             }
         }
         SelectionManager.DeselectAll();
@@ -333,7 +341,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
         setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, (e) => { });
         if (e.button === 0) {
-            this._resizeHdlId = e.currentTarget.id;
+            this._resizeHdlId = e.currentTarget.className;
             const bounds = e.currentTarget.getBoundingClientRect();
             this._offX = this._resizeHdlId.toLowerCase().includes("left") ? bounds.right - e.clientX : bounds.left - e.clientX;
             this._offY = this._resizeHdlId.toLowerCase().includes("top") ? bounds.bottom - e.clientY : bounds.top - e.clientY;
@@ -575,7 +583,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
     }
     render() {
-        const darkScheme = Cast(Doc.UserDoc().activeDashboard, Doc, null)?.darkScheme ? "dimgray" : undefined;
+        const darkScheme = CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimgray" : undefined;
         const bounds = this.Bounds;
         const seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
         if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
@@ -587,35 +595,21 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         });
         const minimal = bounds.r - bounds.x < 100 ? true : false;
         const maximizeIcon = minimal ? (
-            <Tooltip title={<><div className="dash-tooltip">Show context menu</div></>} placement="top">
+            <Tooltip title={<div className="dash-tooltip">Show context menu</div>} placement="top">
                 <div className="documentDecorations-contextMenu" onPointerDown={this.onSettingsDown}>
                     <FontAwesomeIcon size="lg" icon="bars" />
                 </div></Tooltip>) : canDelete ? (
-                    <Tooltip title={<><div className="dash-tooltip">Close</div></>} placement="top">
+                    <Tooltip title={<div className="dash-tooltip">Close</div>} placement="top">
                         <div className="documentDecorations-closeButton" onClick={this.onCloseClick}>
                             {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
                             <FontAwesomeIcon className="documentdecorations-times" icon={"times"} size="lg" />
                         </div></Tooltip>) : (null);
 
         const titleArea = this._edtingTitle ?
+            <input ref={this._keyinput} className="documentDecorations-title" type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle}
+                onBlur={e => this.titleBlur(true)} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} /> :
             <>
-                <input ref={this._keyinput} className="documentDecorations-title" type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle}
-                    onBlur={e => this.titleBlur(true)} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} />
-                {minimal ? (null) : <div className="publishBox" // title="make document referenceable by its title"
-                // onPointerDown={action(e => {
-                //     if (!seldoc.props.Document["title-custom"]) {
-                //         seldoc.props.Document["title-custom"] = true;
-                //         StrCast(Doc.GetProto(seldoc.props.Document).title).startsWith("-") && (Doc.GetProto(seldoc.props.Document).title = StrCast(seldoc.props.Document.title).substring(1));
-                //         this._accumulatedTitle = StrCast(seldoc.props.Document.title);
-                //     }
-                //     DocUtils.Publish(seldoc.props.Document, this._accumulatedTitle, seldoc.props.addDocument, seldoc.props.removeDocument);
-                // })}
-                >
-                    {/* <FontAwesomeIcon size="lg" color={SelectionManager.SelectedDocuments()[0].props.Document.title === SelectionManager.SelectedDocuments()[0].props.Document[Id] ? "green" : undefined} icon="sticky-note"></FontAwesomeIcon> */}
-                </div>}
-            </> :
-            <>
-                {minimal ? (null) : <Tooltip title={<><div className="dash-tooltip">Show context menu</div></>} placement="top"><div className="documentDecorations-contextMenu" key="menu" onPointerDown={this.onSettingsDown}>
+                {minimal ? (null) : <Tooltip title={<div className="dash-tooltip">Show context menu</div>} placement="top"><div className="documentDecorations-contextMenu" key="menu" onPointerDown={this.onSettingsDown}>
                     <FontAwesomeIcon size="lg" icon="bars" />
                 </div></Tooltip>}
                 <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
@@ -656,37 +650,29 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     {maximizeIcon}
                     {titleArea}
                     {SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
-                        <Tooltip title={<><div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div></>} placement="top">
+                        <Tooltip title={<div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div>} placement="top">
                             <div className="documentDecorations-iconifyButton" onPointerDown={this.onIconifyDown}>
                                 <FontAwesomeIcon icon={seldoc.finalLayoutKey.includes("icon") ? "window-restore" : "window-minimize"} className="documentView-minimizedIcon" />
                             </div></Tooltip>}
-                    <Tooltip title={<><div className="dash-tooltip">Open In a New Pane</div></>} placement="top"><div className="documentDecorations-openInTab" onPointerDown={this.onMaximizeDown}>
+                    <Tooltip title={<div className="dash-tooltip">Open In a New Pane</div>} placement="top"><div className="documentDecorations-openInTab" onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} onPointerDown={this.onMaximizeDown}>
                         {SelectionManager.SelectedDocuments().length === 1 ? <FontAwesomeIcon icon="external-link-alt" className="documentView-minimizedIcon" /> : "..."}
                     </div></Tooltip>
-                    <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer"
-                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                    <div id="documentDecorations-topResizer" className="documentDecorations-resizer"
-                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                    <div id="documentDecorations-topRightResizer" className="documentDecorations-resizer"
-                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                    <div id="documentDecorations-leftResizer" className="documentDecorations-resizer"
-                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                    <div id="documentDecorations-centerCont"></div>
-                    <div id="documentDecorations-rightResizer" className="documentDecorations-resizer"
-                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                    <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer"
-                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                    <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer"
-                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                    <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer"
-                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                    <div className="documentDecorations-topLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                    <div className="documentDecorations-topResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                    <div className="documentDecorations-topRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                    <div className="documentDecorations-leftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                    <div className="documentDecorations-centerCont"></div>
+                    <div className="documentDecorations-rightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                    <div className="documentDecorations-bottomLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                    <div className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                    <div className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
                     {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
-                        <Tooltip title={<><div className="dash-tooltip">tap to select containing document</div></>} placement="top">
-                            <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
+                        <Tooltip title={<div className="dash-tooltip">tap to select containing document</div>} placement="top">
+                            <div className="documentDecorations-levelSelector"
                                 onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
                                 <FontAwesomeIcon className="documentdecorations-times" icon={"arrow-alt-circle-up"} size="lg" />
                             </div></Tooltip>}
-                    <div id={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`}
+                    <div className={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`}
                         onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}>{useRotation && "⟲"}</div>
 
                 </div >
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7b0c6fa9f..83d9a3498 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -13,7 +13,7 @@ import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
 import { listSpec } from '../../fields/Schema';
-import { BoolCast, Cast, FieldValue, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, FieldValue, StrCast, PromiseValue } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
 import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
@@ -75,10 +75,10 @@ export class MainView extends React.Component {
     @observable private _panelHeight: number = 0;
     @observable private _flyoutTranslate: boolean = false;
     @observable public flyoutWidth: number = 0;
-    private get darkScheme() { return BoolCast(Cast(this.userDoc?.activeDashboard, Doc, null)?.darkScheme); }
+    private get darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
 
     @computed private get userDoc() { return Doc.UserDoc(); }
-    @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeDashboard, Doc)) : CurrentUserUtils.GuestDashboard; }
+    @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; }
     @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
     @computed public get searchDoc() { return Cast(this.userDoc.mySearchPanelDoc, Doc) as Doc; }
 
@@ -226,8 +226,7 @@ export class MainView extends React.Component {
         // Load the user's active dashboard, or create a new one if initial session after signup
         const received = CurrentUserUtils.MainDocId;
         if (received && !this.userDoc) {
-            reaction(
-                () => CurrentUserUtils.GuestTarget,
+            reaction(() => CurrentUserUtils.GuestTarget,
                 target => target && CurrentUserUtils.createNewDashboard(Doc.UserDoc()),
                 { fireImmediately: true }
             );
@@ -241,11 +240,11 @@ export class MainView extends React.Component {
                     }),
                 );
             }
-            const doc = this.userDoc && await Cast(this.userDoc.activeDashboard, Doc);
-            if (doc) {
-                CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
+            const activeDash = PromiseValue(this.userDoc.activeDashboard);
+            if (activeDash) {
+                activeDash.then(dash => dash instanceof Doc && CurrentUserUtils.openDashboard(this.userDoc, dash));
             } else {
-                CurrentUserUtils.createNewDashboard(Doc.UserDoc());
+                CurrentUserUtils.createNewDashboard(this.userDoc);
             }
         }
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 62c865436..93e77e0a2 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -140,6 +140,13 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         return CollectionDockingView.AddSplit(document, panelName, stack, panelName);
     }
 
+
+    @undoBatch
+    public static ToggleSplit(doc: Doc, location: string, stack?: any, panelName?: string) {
+        return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === doc) !== -1 ?
+            CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, location, stack, panelName);
+    }
+
     //
     //  Creates a split on any side of the docking view based on the passed input pullSide and then adds the Document to the requested side
     //
@@ -262,8 +269,6 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                 config => {
                     if (!this._goldenLayout || this._ignoreStateChange !== config) {
                         this.setupGoldenLayout();
-                        DocListCast(CurrentUserUtils.MyDashboards.data).map(d => d.dashboardBrush = false);
-                        this.props.Document.dashboardBrush = true;
                     }
                     this._ignoreStateChange = "";
                 });
@@ -274,7 +279,6 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
 
     componentWillUnmount: () => void = () => {
         try {
-            this.props.Document.dashboardBrush = false;
             this._goldenLayout.unbind('stackCreated', this.stackCreated);
             this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
         } catch (e) { }
@@ -619,8 +623,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
             case "inPlace":
             case "add":
-            default: return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === doc) !== -1 ?
-                CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, locationParams, this.stack);
+            default: return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack);
         }
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 80a6afba7..84fd4cbe8 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -483,7 +483,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 style={{
                     fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? "bold" : undefined,
                     textDecoration: Doc.GetT(this.doc, "title", "string", true) ? "underline" : undefined,
-                    outline: BoolCast(this.doc.dashboardBrush) ? "dashed 1px #06123232" : undefined,
+                    outline: this.doc === CurrentUserUtils.ActiveDashboard ? "dashed 1px #06123232" : undefined,
                     pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined
                 }} >
                 {view}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 64ad3abbf..81403de46 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -583,7 +583,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             ChildLayoutString: this.childLayoutString,
         };
         const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document._isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
-            `${Cast(Doc.UserDoc().activeDashboard, Doc, null)?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
+            `${CurrentUserUtils.ActiveDashboard?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
         return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
             style={{ pointerEvents: this.props.Document._isBackground ? "none" : undefined, boxShadow }}>
             {this.showIsTagged()}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 646ae94fb..bc2cb2d20 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -10,6 +10,7 @@ import { Id, ToString } from "../../../../fields/FieldSymbols";
 import { ObjectField } from "../../../../fields/ObjectField";
 import { RefField } from "../../../../fields/RefField";
 import { listSpec } from "../../../../fields/Schema";
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
 
 export interface ViewDefBounds {
     type: string;
@@ -359,7 +360,7 @@ export function computeTimelineLayout(
         groupNames.push({ type: "text", text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
     }
 
-    const divider = { type: "div", color: Cast(Doc.UserDoc().activeDashboard, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
+    const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
     return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
 
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss
index 4d8473be9..60ec02f47 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.scss
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss
@@ -134,17 +134,6 @@
 
 }
 
-// .documentDecorations-container .documentDecorations-resizer {
-//     pointer-events: none;
-// }
-
-// #documentDecorations-bottomRightResizer,
-// #documentDecorations-bottomLeftResizer,
-// #documentDecorations-topRightResizer,
-// #documentDecorations-topLeftResizer {
-//     visibility: collapse;
-// }
-
 
 /* Chrome, Safari, Edge, Opera */
 input::-webkit-outer-spin-button,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 48a1688a6..f4eb71145 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -39,6 +39,7 @@ import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { RadialMenu } from './RadialMenu';
 import { TaskCompletionBox } from './TaskCompletedBox';
 import React = require("react");
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 
 export type DocFocusFunc = () => boolean;
 
@@ -588,7 +589,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch @action
     deleteClicked = (): void => {
-        if (Doc.UserDoc().activeDashboard === this.props.Document) {
+        if (CurrentUserUtils.ActiveDashboard === this.props.Document) {
             alert("Can't delete the active dashboard");
         } else {
             const selected = SelectionManager.SelectedDocuments().slice();
@@ -625,7 +626,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
-        if (this.props.Document === Doc.UserDoc().activeDashboard) {
+        if (this.props.Document === CurrentUserUtils.ActiveDashboard) {
             alert("linking to document tabs not yet supported.  Drop link on document content.");
             return;
         }
@@ -1041,7 +1042,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
         const borderRounding = this.layoutDoc.borderRounding;
         const localScale = fullDegree;
-        const highlightColors = Cast(Doc.UserDoc().activeDashboard, Doc, null)?.darkScheme ?
+        const highlightColors = CurrentUserUtils.ActiveDashboard?.darkScheme ?
             ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
             ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
         const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 08bab8d12..3b77735a7 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -12,6 +12,7 @@ import { DocumentView } from "../DocumentView";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { Transform } from "../../../util/Transform";
 import React = require("react");
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
 
 interface IDashDocView {
     node: any;
@@ -175,7 +176,7 @@ export class DashDocView extends React.Component<IDashDocView> {
         const outerStyle = {
             position: "relative" as "relative",
             textIndent: "0",
-            border: "1px solid " + StrCast(this._textBox.Document.color, (Cast(Doc.UserDoc().activeDashboard, Doc, null).darkScheme ? "dimGray" : "lightGray")),
+            border: "1px solid " + StrCast(this._textBox.Document.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray")),
             width: this.props.node.props.width,
             height: this.props.node.props.height,
             display: this.props.node.props.hidden ? "none" : "inline-block",
@@ -202,7 +203,7 @@ export class DashDocView extends React.Component<IDashDocView> {
                 ({ dim, color }) => {
                     spanStyle.width = outerStyle.width = Math.max(20, dim[0]) + "px";
                     spanStyle.height = outerStyle.height = Math.max(20, dim[1]) + "px";
-                    outerStyle.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeDashboard, Doc, null).darkScheme ? "dimGray" : "lightGray"));
+                    outerStyle.border = "1px solid " + StrCast(finalLayout.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray"));
                 }, { fireImmediately: true });
 
             if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
index b76e520b7..f0bacb735 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -13,6 +13,7 @@ import { Transform } from "../../../util/Transform";
 import { DocumentView } from "../DocumentView";
 import { FormattedTextBox } from "./FormattedTextBox";
 import React = require("react");
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
 
 
 export class DashDocView {
@@ -43,7 +44,7 @@ export class DashDocView {
         this._outer = document.createElement("span");
         this._outer.style.position = "relative";
         this._outer.style.textIndent = "0";
-        this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (Cast(Doc.UserDoc().activeDashboard, Doc, null).darkScheme ? "dimGray" : "lightGray"));
+        this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray"));
         this._outer.style.width = node.attrs.width;
         this._outer.style.height = node.attrs.height;
         this._outer.style.display = node.attrs.hidden ? "none" : "inline-block";
@@ -126,7 +127,7 @@ export class DashDocView {
             this._reactionDisposer = reaction(() => ({ dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], color: finalLayout.color }), ({ dim, color }) => {
                 this._dashSpan.style.width = this._outer.style.width = Math.max(20, dim[0]) + "px";
                 this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px";
-                this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeDashboard, Doc, null).darkScheme ? "dimGray" : "lightGray"));
+                this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray"));
             }, { fireImmediately: true });
 
             const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index d7a8ed404..3926208ae 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -507,7 +507,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                     </div>
                     <div className="searchBox-lozenge-dashboard"  >
                         <select className="searchBox-dashSelect" onChange={e => CurrentUserUtils.openDashboard(Doc.UserDoc(), myDashboards[Number(e.target.value)])}
-                            value={myDashboards.indexOf(Cast(Doc.UserDoc().activeDashboard, Doc, null))}>
+                            value={myDashboards.indexOf(CurrentUserUtils.ActiveDashboard)}>
                             {myDashboards.map((dash, i) => <option key={dash[Id]} value={i}> {StrCast(dash.title)} </option>)}
                         </select>
                         <div className="searchBox-dashboards" onClick={undoBatch(() => CurrentUserUtils.createNewDashboard(Doc.UserDoc()))}>
-- 
cgit v1.2.3-70-g09d2


From 51f9da2bb6529c102c300a1ceb4ccd23065a76fc Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 29 Aug 2020 17:58:32 -0400
Subject: split out TabDocView from CollectionDockingView (where it had been
 called DockedFrameRenderer)

---
 src/client/util/CurrentUserUtils.ts                |   6 +-
 src/client/views/DocumentButtonBar.tsx             |   5 +-
 src/client/views/PropertiesButtons.tsx             |   2 +-
 .../views/collections/CollectionDockingView.scss   |  31 --
 .../views/collections/CollectionDockingView.tsx    | 370 +-------------------
 src/client/views/collections/CollectionMenu.tsx    |  10 +-
 src/client/views/collections/TabDocView.scss       |  20 ++
 src/client/views/collections/TabDocView.tsx        | 379 +++++++++++++++++++++
 src/client/views/nodes/PresBox.tsx                 |  43 ++-
 src/mobile/MobileInterface.tsx                     |   4 +-
 10 files changed, 437 insertions(+), 433 deletions(-)
 create mode 100644 src/client/views/collections/TabDocView.scss
 create mode 100644 src/client/views/collections/TabDocView.tsx

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 2ca395164..260552879 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1031,11 +1031,7 @@ export class CurrentUserUtils {
         const dashboards = Cast(userDoc.myDashboards, Doc) as Doc;
         const dashboardCount = DocListCast(dashboards.data).length + 1;
         const emptyPane = Cast(userDoc.emptyPane, Doc, null);
-        try {
-            emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
-        } catch (e) {
-            console.log(e)
-        }
+        emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
         const freeformOptions: DocumentOptions = {
             x: 0,
             y: 400,
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 764a2547c..45336129a 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -15,7 +15,8 @@ import { CurrentUserUtils } from '../util/CurrentUserUtils';
 import { DragManager } from '../util/DragManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { SharingManager } from '../util/SharingManager';
-import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { TabDocView } from './collections/TabDocView';
 import './DocumentButtonBar.scss';
 import { MetadataEntryMenu } from './MetadataEntryMenu';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
@@ -194,7 +195,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div></>}>
             <div className="documentButtonBar-linker"
                 style={{ backgroundColor: isPinned ? "white" : "", color: isPinned ? "black" : "white", border: isPinned ? "black 1px solid " : "" }}
-                onClick={e => this.props.views().map(view => view && DockedFrameRenderer.PinDoc(view.props.Document, isPinned))}>
+                onClick={e => this.props.views().map(view => view && TabDocView.PinDoc(view.props.Document, isPinned))}>
                 <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin"
                 />
             </div></Tooltip>;
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 6ac6571d3..dcd3d47cf 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -160,7 +160,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                                 googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
                                 dataDoc.googleDoc = googleDoc;
                             }
-                            CollectionDockingView.AddRight(googleDoc, "right");
+                            CollectionDockingView.AddSplit(googleDoc, "right");
                         } else if (e.altKey) {
                             e.preventDefault();
                             window.open(googleDocUrl);
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index f1a29bd13..d3be1636d 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,24 +1,5 @@
 @import "../../views/globalCssVariables.scss";
 
-.miniMap {
-    position: absolute;
-    overflow: hidden;
-    right: 10;
-    bottom: 10;
-    border: solid 1px;
-    box-shadow: black 0.4vw 0.4vw 0.8vw;
-
-    .miniOverlay {
-        width: 100%;
-        height: 100%;
-        position: absolute;
-
-        .miniThumb {
-            background: #25252525;
-            position: absolute;
-        }
-    }
-}
 
 .lm_title {
     margin-top: 3px;
@@ -72,18 +53,6 @@
     display: inline;
 }
 
-.messageCounter {
-    width: 18px;
-    height: 20px;
-    text-align: center;
-    border-radius: 20px;
-    margin-left: 5px;
-    transform: translate(0px, -8px);
-    display: inline-block;
-    background: transparent;
-    border: 1px #999999 solid;
-}
-
 .collectiondockingview-container {
     width: 100%;
     height: 100%;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 93e77e0a2..d8e95bcdc 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,37 +1,25 @@
 import 'golden-layout/src/css/goldenlayout-base.css';
 import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { clamp, pull } from 'lodash';
-import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
 import * as GoldenLayout from "../../../client/goldenLayout";
-import { DataSym, Doc, DocListCast, Opt } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../fields/Doc";
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
-import { FieldId } from "../../../fields/RefField";
-import { listSpec } from '../../../fields/Schema';
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs } from '../../documents/Documents';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
-import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from "../../util/DragManager";
+import { DragManager } from "../../util/DragManager";
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { Scripting } from '../../util/Scripting';
-import { SelectionManager } from '../../util/SelectionManager';
-import { SnappingManager } from '../../util/SnappingManager';
-import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DocumentView } from "../nodes/DocumentView";
-import { PresBox } from '../nodes/PresBox';
 import "./CollectionDockingView.scss";
-import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
 import { CollectionViewType } from './CollectionView';
-import { CollectionDockingViewMenu } from './CollectionDockingViewMenu';
+import { TabDocView } from './TabDocView';
 import React = require("react");
 const _global = (window /* browser */ || global /* node */) as any;
 
@@ -249,7 +237,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             this._goldenLayout.on('tabCreated', this.tabCreated);
             this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
             this._goldenLayout.on('stackCreated', this.stackCreated);
-            this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
+            this._goldenLayout.registerComponent('DocumentFrameRenderer', TabDocView);
             this._goldenLayout.container = this._containerRef.current;
             if (this._goldenLayout.config.maximisedItemId === '__glMaximised') {
                 try {
@@ -407,354 +395,6 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     }
 }
 
-interface DockedFrameProps {
-    documentId: FieldId;
-    glContainer: any;
-}
-@observer
-export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
-    _mainCont: HTMLDivElement | null = null;
-    _tabReaction: IReactionDisposer | undefined;
-    @observable private _panelWidth = 0;
-    @observable private _panelHeight = 0;
-    @observable private _isActive: boolean = false;
-    @observable private _document: Doc | undefined;
-    @observable private _view: DocumentView | undefined;
-
-    get stack(): any { return (this.props as any).glContainer.parent.parent; }
-    get tab() { return (this.props as any).glContainer.tab; }
-    get view() { return this._view; }
-
-    @action
-    init = (tab: any, doc: Opt<Doc>) => {
-        if (tab.DashDoc !== doc && doc && tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
-            tab._disposers = {} as { [name: string]: IReactionDisposer };
-            tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
-            tab.DashDoc = doc;
-
-            CollectionDockingView.Instance.tabMap.add(tab);
-            tab.titleElement[0].onclick = (e: any) => {
-                if (Date.now() - tab.titleElement[0].lastClick < 1000) tab.titleElement[0].select();
-                tab.titleElement[0].lastClick = Date.now();
-                tab.titleElement[0].focus();
-            };
-            tab.titleElement[0].onchange = (e: any) => {
-                tab.titleElement[0].size = e.currentTarget.value.length + 1;
-                Doc.GetProto(doc).title = e.currentTarget.value;
-            };
-            tab.titleElement[0].size = StrCast(doc.title).length + 1;
-            tab.titleElement[0].value = doc.title;
-            tab.titleElement[0].style["max-width"] = "100px";
-            const gearSpan = document.createElement("span");
-            gearSpan.className = "collectionDockingView-gear";
-            gearSpan.style.position = "relative";
-            gearSpan.style.paddingLeft = "0px";
-            gearSpan.style.paddingRight = "12px";
-            const stack = tab.contentItem.parent;
-            tab.element[0].onpointerdown = (e: any) => {
-                e.target.className !== "lm_close_tab" && this.view && SelectionManager.SelectDoc(this.view, false);
-            };
-            // shifts the focus to this tab when another tab is dragged over it
-            tab.element[0].onmouseenter = (e: MouseEvent) => {
-                if (SnappingManager.GetIsDragging() && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
-                    tab.header.parent.setActiveContentItem(tab.contentItem);
-                }
-                tab.setActive(true);
-            };
-            const onDown = (e: React.PointerEvent) => {
-                setupMoveUpEvents(this, e, (e) => {
-                    !e.defaultPrevented && DragManager.StartDocumentDrag([gearSpan], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY);
-                    return !e.defaultPrevented;
-                }, returnFalse, emptyFunction);
-            };
-
-            tab._disposers.selectionDisposer = reaction(() => SelectionManager.SelectedDocuments().some(v => v.props.Document === doc),
-                (selected) => {
-                    selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && tab.header.parent.setActiveContentItem(tab.contentItem);
-                }
-            );
-            tab._disposers.buttonDisposer = reaction(() => this.view,
-                (view) => {
-                    if (view) {
-                        ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} >
-                            <CollectionDockingViewMenu views={() => [view]} Stack={stack} />
-                        </span>,
-                            gearSpan);
-                        tab._disposers.buttonDisposer?.();
-                    }
-                }, { fireImmediately: true });
-
-            tab.reactComponents = [gearSpan];
-            tab.element.append(gearSpan);
-            tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
-                tab.titleElement[0].value = title;
-                tab.titleElement[0].style.padding = degree ? 0 : 2;
-                tab.titleElement[0].style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
-            }, { fireImmediately: true });
-            tab.closeElement.off('click') //unbind the current click handler
-                .click(function () {
-                    Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
-                    Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true);
-                    SelectionManager.DeselectAll();
-                    tab.contentItem.remove();
-                });
-        }
-    }
-    /**
-     * Adds a document to the presentation view
-     **/
-    @undoBatch
-    @action
-    public static PinDoc(doc: Doc, unpin = false) {
-        if (unpin) DockedFrameRenderer.UnpinDoc(doc);
-        else {
-            //add this new doc to props.Document
-            const curPres = CurrentUserUtils.ActivePresentation;
-            if (curPres) {
-                const pinDoc = Doc.MakeAlias(doc);
-                pinDoc.presentationTargetDoc = doc;
-                pinDoc.presZoomButton = true;
-                pinDoc.context = curPres;
-                Doc.AddDocToList(curPres, "data", pinDoc);
-                if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
-                if (!DocumentManager.Instance.getDocumentView(curPres)) {
-                    CollectionDockingView.AddSplit(curPres, "right");
-                }
-                DocumentManager.Instance.jumpToDocument(doc, false, undefined, Cast(doc.context, Doc, null));
-            }
-        }
-    }
-    /**
-     * Adds a document to the presentation view
-     **/
-    @undoBatch
-    @action
-    public static UnpinDoc(doc: Doc) {
-        //add this new doc to props.Document
-        const curPres = CurrentUserUtils.ActivePresentation;
-        if (curPres) {
-            const ind = DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc));
-            ind !== -1 && Doc.RemoveDocFromList(curPres, "data", DocListCast(curPres.data)[ind]);
-        }
-    }
-
-    componentDidMount() {
-        const color = () => StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white");
-        const selected = () => SelectionManager.SelectedDocuments().some(v => v.props.Document === this._document);
-        const updateTabColor = () => this.tab?.titleElement[0] && (this.tab.titleElement[0].style.backgroundColor = selected() ? color() : "");
-        const observer = new _global.ResizeObserver(action((entries: any) => {
-            for (const entry of entries) {
-                this._panelWidth = entry.contentRect.width;
-                this._panelHeight = entry.contentRect.height;
-            }
-            updateTabColor();
-        }));
-        observer.observe(this.props.glContainer._element[0]);
-        this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
-        this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged();
-        this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: color() }), updateTabColor, { fireImmediately: true });
-    }
-
-    componentWillUnmount() {
-        this._tabReaction?.();
-        this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged);
-    }
-
-    @action.bound
-    private onActiveContentItemChanged() {
-        if (this.props.glContainer.tab && this._isActive !== this.props.glContainer.tab.isActive) {
-            this._isActive = this.props.glContainer.tab.isActive;
-            this._isActive && setTimeout(() => this.view && SelectionManager.SelectDoc(this.view, false), 0);
-            (CollectionDockingView.Instance as any)._goldenLayout.isInitialised && CollectionDockingView.Instance.stateChanged();
-            !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
-        }
-    }
-
-    get layoutDoc() { return this._document && Doc.Layout(this._document); }
-    nativeAspect = () => this.nativeWidth() ? this.nativeWidth() / this.nativeHeight() : 0;
-    panelWidth = () => this.layoutDoc?.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) :
-        (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth)
-    panelHeight = () => this.nativeAspect() && this.nativeAspect() > this._panelWidth / this._panelHeight ? this._panelWidth / this.nativeAspect() : this._panelHeight;
-    nativeWidth = () => !this.layoutDoc?._fitWidth ? NumCast(this.layoutDoc?._nativeWidth) || this._panelWidth : 0;
-    nativeHeight = () => !this.layoutDoc?._fitWidth ? NumCast(this.layoutDoc?._nativeHeight) || this._panelHeight : 0;
-
-    contentScaling = () => {
-        const nativeH = this.nativeHeight();
-        const nativeW = this.nativeWidth();
-        let scaling = 1;
-        if (!this.layoutDoc?._fitWidth && (!nativeW || !nativeH)) {
-            scaling = 1;
-        } else if (NumCast(this.layoutDoc?._nativeWidth) && ((this.layoutDoc?._fitWidth) ||
-            this._panelHeight / NumCast(this.layoutDoc?._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc?._nativeWidth))) {
-            scaling = this._panelWidth / NumCast(this.layoutDoc?._nativeWidth);
-        } else if (nativeW && nativeH) {
-            const wscale = this.panelWidth() / nativeW;
-            scaling = wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
-        }
-        return scaling;
-    }
-
-    ScreenToLocalTransform = () => {
-        if (this._mainCont && this._mainCont.children) {
-            const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0].firstChild as HTMLElement);
-            const scale = Utils.GetScreenTransform(this._mainCont).scale;
-            return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
-        }
-        return Transform.Identity();
-    }
-    get previewPanelCenteringOffset() { return this.nativeWidth() ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
-    get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this._panelWidth * 100}% ` : undefined; }
-
-    // adds a tab to the layout based on the locaiton parameter which can be:
-    //  close[:{left,right,top,bottom}]  - e.g., "close" will close the tab, "close:left" will close the left tab, 
-    //  add[:{left,right,top,bottom}] - e.g., "add" will add a tab to the current stack, "add:right" will add a tab on the right
-    //  replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents, 
-    //                                  "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name, 
-    //                                   "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
-    //  inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack
-    addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => {
-        SelectionManager.DeselectAll();
-        if (doc._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
-        const locationFields = location.split(":");
-        const locationParams = locationFields.length > 1 ? locationFields[1] : "";
-        switch (locationFields[0]) {
-            case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
-            case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
-            case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
-            case "inPlace":
-            case "add":
-            default: return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack);
-        }
-    }
-
-    @computed get renderContentBounds() {
-        const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
-        const xbounds = bounds[2] - bounds[0];
-        const ybounds = bounds[3] - bounds[1];
-        const dim = Math.max(xbounds, ybounds);
-        return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
-    }
-    @computed get miniLeft() { return 50 + (NumCast(this._document?._panX) - this.renderContentBounds.cx) / this.renderContentBounds.dim * 100 - this.miniWidth / 2; }
-    @computed get miniTop() { return 50 + (NumCast(this._document?._panY) - this.renderContentBounds.cy) / this.renderContentBounds.dim * 100 - this.miniHeight / 2; }
-    @computed get miniWidth() { return this.panelWidth() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
-    @computed get miniHeight() { return this.panelHeight() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
-    childLayoutTemplate = () => Cast(this._document?.childLayoutTemplate, Doc, null);
-    returnMiniSize = () => NumCast(this._document?._miniMapSize, 150);
-    miniDown = (e: React.PointerEvent) => {
-        this._document && setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
-            this._document!._panX = clamp(NumCast(this._document!._panX) + delta[0] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.l, this.renderContentBounds.l + this.renderContentBounds.dim);
-            this._document!._panY = clamp(NumCast(this._document!._panY) + delta[1] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.t, this.renderContentBounds.t + this.renderContentBounds.dim);
-            return false;
-        }), emptyFunction, emptyFunction);
-    }
-    getCurrentFrame = (): number => {
-        const presTargetDoc = Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null);
-        return Cast(presTargetDoc._currentFrame, "number", null);
-    }
-
-    renderMiniMap() {
-        return <div className="miniMap" style={{
-            width: this.returnMiniSize(), height: this.returnMiniSize(), background: StrCast(this._document!._backgroundColor,
-                StrCast(this._document!.backgroundColor, CollectionDockingView.Instance.props.backgroundColor?.(this._document!, 0))),
-        }}>
-            <CollectionFreeFormView
-                Document={this._document!}
-                LibraryPath={emptyPath}
-                CollectionView={undefined}
-                ContainingCollectionView={undefined}
-                ContainingCollectionDoc={undefined}
-                ChildLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid havin to set stuff like this.
-                noOverlay={true} // don't render overlay Docs since they won't scale
-                active={returnTrue}
-                select={emptyFunction}
-                dropAction={undefined}
-                isSelected={returnFalse}
-                dontRegisterView={true}
-                annotationsKey={""}
-                fieldKey={Doc.LayoutFieldKey(this._document!)}
-                bringToFront={emptyFunction}
-                rootSelected={returnTrue}
-                addDocument={returnFalse}
-                moveDocument={returnFalse}
-                removeDocument={returnFalse}
-                ContentScaling={returnOne}
-                PanelWidth={this.returnMiniSize}
-                PanelHeight={this.returnMiniSize}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
-                ScreenToLocalTransform={this.ScreenToLocalTransform}
-                renderDepth={0}
-                whenActiveChanged={emptyFunction}
-                focus={emptyFunction}
-                backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
-                addDocTab={this.addDocTab}
-                pinToPres={DockedFrameRenderer.PinDoc}
-                docFilters={CollectionDockingView.Instance.docFilters}
-                searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
-                fitToBox={true}
-            />
-            <div className="miniOverlay" onPointerDown={this.miniDown} >
-                <div className="miniThumb" style={{
-                    width: `${this.miniWidth}% `,
-                    height: `${this.miniHeight}% `,
-                    left: `${this.miniLeft}% `,
-                    top: `${this.miniTop}% `,
-                }}
-                />
-            </div>
-        </div>;
-    }
-    setView = action((view: DocumentView) => this._view = view);
-    @computed get docView() {
-        TraceMobx();
-        return !this._document ? (null) :
-            <><DocumentView key={this._document[Id]}
-                LibraryPath={emptyPath}
-                Document={this._document}
-                getView={this.setView}
-                DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
-                bringToFront={emptyFunction}
-                rootSelected={returnTrue}
-                addDocument={undefined}
-                removeDocument={undefined}
-                ContentScaling={this.contentScaling}
-                PanelWidth={this.panelWidth}
-                PanelHeight={this.panelHeight}
-                NativeHeight={this.nativeHeight}
-                NativeWidth={this.nativeWidth}
-                ScreenToLocalTransform={this.ScreenToLocalTransform}
-                renderDepth={0}
-                parentActive={returnTrue}
-                whenActiveChanged={emptyFunction}
-                focus={emptyFunction}
-                backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
-                addDocTab={this.addDocTab}
-                pinToPres={DockedFrameRenderer.PinDoc}
-                docFilters={CollectionDockingView.Instance.docFilters}
-                searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
-                ContainingCollectionView={undefined}
-                ContainingCollectionDoc={undefined} />
-                {this._document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
-            </>;
-    }
-
-    render() {
-        return !this._isActive ? (null) :
-            (<div className="collectionDockingView-content" ref={ref => {
-                if (this._mainCont = ref) {
-                    (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
-                    DocServer.GetRefField(this.tab.contentItem.config.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.init(this.tab, this._document)));
-                }
-            }}
-                style={{
-                    transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`,
-                    height: this.layoutDoc?._fitWidth ? undefined : "100%",
-                    width: this.widthpercent
-                }}>
-                {this.docView}
-            </div >);
-    }
-}
 Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddSplit(doc, "right"); },
     "opens up the inputted document on the right side of the screen", "(doc: any)");
 Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); });
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index a41b5eaca..eaada32b0 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -5,7 +5,7 @@ import { Tooltip } from "@material-ui/core";
 import { action, computed, Lambda, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { ColorState } from "react-color";
-import { Doc, DocListCast, Opt, Field } from "../../../fields/Doc";
+import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
 import { Document } from "../../../fields/documentSchemas";
 import { Id } from "../../../fields/FieldSymbols";
 import { InkTool } from "../../../fields/InkField";
@@ -29,10 +29,10 @@ import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
 import { DocumentView } from "../nodes/DocumentView";
 import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
+import { PresBox } from "../nodes/PresBox";
 import "./CollectionMenu.scss";
 import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView";
-import { DockedFrameRenderer } from "./CollectionDockingView";
-import { PresBox } from "../nodes/PresBox";
+import { TabDocView } from "./TabDocView";
 
 @observer
 export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -379,7 +379,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
         const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
         return !targetDoc ? (null) : <Tooltip key="pin" title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div>} placement="top">
             <button className="antimodeMenu-button" style={{ backgroundColor: isPinned ? "121212" : undefined, borderRight: "1px solid gray" }}
-                onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
+                onClick={e => TabDocView.PinDoc(targetDoc, isPinned)}>
                 <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" />
             </button>
         </Tooltip>;
@@ -443,7 +443,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
             <button className="antidmodeMenu-button" style={{ borderRight: "1px solid gray" }}
                 onClick={e => {
                     if (targetDoc) {
-                        DockedFrameRenderer.PinDoc(targetDoc, false);
+                        TabDocView.PinDoc(targetDoc, false);
                         const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1];
                         const x = targetDoc._panX;
                         const y = targetDoc._panY;
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
new file mode 100644
index 000000000..9a4b5cbd1
--- /dev/null
+++ b/src/client/views/collections/TabDocView.scss
@@ -0,0 +1,20 @@
+
+.miniMap {
+    position: absolute;
+    overflow: hidden;
+    right: 10;
+    bottom: 10;
+    border: solid 1px;
+    box-shadow: black 0.4vw 0.4vw 0.8vw;
+
+    .miniOverlay {
+        width: 100%;
+        height: 100%;
+        position: absolute;
+
+        .miniThumb {
+            background: #25252525;
+            position: absolute;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
new file mode 100644
index 000000000..36959f254
--- /dev/null
+++ b/src/client/views/collections/TabDocView.tsx
@@ -0,0 +1,379 @@
+import 'golden-layout/src/css/goldenlayout-base.css';
+import 'golden-layout/src/css/goldenlayout-dark-theme.css';
+import { clamp } from 'lodash';
+import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
+import * as ReactDOM from 'react-dom';
+import { DataSym, Doc, DocListCast, Opt } from "../../../fields/Doc";
+import { Id } from '../../../fields/FieldSymbols';
+import { FieldId } from "../../../fields/RefField";
+import { listSpec } from '../../../fields/Schema';
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
+import { TraceMobx } from '../../../fields/util';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils } from "../../../Utils";
+import { DocServer } from "../../DocServer";
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { DocumentManager } from '../../util/DocumentManager';
+import { DragManager, dropActionType } from "../../util/DragManager";
+import { SelectionManager } from '../../util/SelectionManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { Transform } from '../../util/Transform';
+import { undoBatch } from "../../util/UndoManager";
+import { DocumentView } from "../nodes/DocumentView";
+import { PresBox } from '../nodes/PresBox';
+import { CollectionDockingView } from './CollectionDockingView';
+import "./TabDocView.scss";
+import { CollectionDockingViewMenu } from './CollectionDockingViewMenu';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionViewType } from './CollectionView';
+import React = require("react");
+const _global = (window /* browser */ || global /* node */) as any;
+
+interface TabDocViewProps {
+    documentId: FieldId;
+    glContainer: any;
+}
+@observer
+export class TabDocView extends React.Component<TabDocViewProps> {
+    _mainCont: HTMLDivElement | null = null;
+    _tabReaction: IReactionDisposer | undefined;
+    @observable private _panelWidth = 0;
+    @observable private _panelHeight = 0;
+    @observable private _isActive: boolean = false;
+    @observable private _document: Doc | undefined;
+    @observable private _view: DocumentView | undefined;
+
+    get stack(): any { return (this.props as any).glContainer.parent.parent; }
+    get tab() { return (this.props as any).glContainer.tab; }
+    get view() { return this._view; }
+
+    @action
+    init = (tab: any, doc: Opt<Doc>) => {
+        if (tab.DashDoc !== doc && doc && tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
+            tab._disposers = {} as { [name: string]: IReactionDisposer };
+            tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
+            tab.DashDoc = doc;
+
+            CollectionDockingView.Instance.tabMap.add(tab);
+            tab.titleElement[0].onclick = (e: any) => {
+                if (Date.now() - tab.titleElement[0].lastClick < 1000) tab.titleElement[0].select();
+                tab.titleElement[0].lastClick = Date.now();
+                tab.titleElement[0].focus();
+            };
+            tab.titleElement[0].onchange = (e: any) => {
+                tab.titleElement[0].size = e.currentTarget.value.length + 1;
+                Doc.GetProto(doc).title = e.currentTarget.value;
+            };
+            tab.titleElement[0].size = StrCast(doc.title).length + 1;
+            tab.titleElement[0].value = doc.title;
+            tab.titleElement[0].style["max-width"] = "100px";
+            const gearSpan = document.createElement("span");
+            gearSpan.className = "collectionDockingView-gear";
+            gearSpan.style.position = "relative";
+            gearSpan.style.paddingLeft = "0px";
+            gearSpan.style.paddingRight = "12px";
+            const stack = tab.contentItem.parent;
+            tab.element[0].onpointerdown = (e: any) => {
+                e.target.className !== "lm_close_tab" && this.view && SelectionManager.SelectDoc(this.view, false);
+            };
+            // shifts the focus to this tab when another tab is dragged over it
+            tab.element[0].onmouseenter = (e: MouseEvent) => {
+                if (SnappingManager.GetIsDragging() && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
+                    tab.header.parent.setActiveContentItem(tab.contentItem);
+                }
+                tab.setActive(true);
+            };
+            const onDown = (e: React.PointerEvent) => {
+                setupMoveUpEvents(this, e, (e) => {
+                    !e.defaultPrevented && DragManager.StartDocumentDrag([gearSpan], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY);
+                    return !e.defaultPrevented;
+                }, returnFalse, emptyFunction);
+            };
+
+            tab._disposers.selectionDisposer = reaction(() => SelectionManager.SelectedDocuments().some(v => v.props.Document === doc),
+                (selected) => {
+                    selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && tab.header.parent.setActiveContentItem(tab.contentItem);
+                }
+            );
+            tab._disposers.buttonDisposer = reaction(() => this.view,
+                (view) => {
+                    if (view) {
+                        ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} >
+                            <CollectionDockingViewMenu views={() => [view]} Stack={stack} />
+                        </span>,
+                            gearSpan);
+                        tab._disposers.buttonDisposer?.();
+                    }
+                }, { fireImmediately: true });
+
+            tab.reactComponents = [gearSpan];
+            tab.element.append(gearSpan);
+            tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
+                tab.titleElement[0].value = title;
+                tab.titleElement[0].style.padding = degree ? 0 : 2;
+                tab.titleElement[0].style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
+            }, { fireImmediately: true });
+            tab.closeElement.off('click') //unbind the current click handler
+                .click(function () {
+                    Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
+                    Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true);
+                    SelectionManager.DeselectAll();
+                    tab.contentItem.remove();
+                });
+        }
+    }
+    /**
+     * Adds a document to the presentation view
+     **/
+    @undoBatch
+    @action
+    public static PinDoc(doc: Doc, unpin = false) {
+        if (unpin) TabDocView.UnpinDoc(doc);
+        else {
+            //add this new doc to props.Document
+            const curPres = CurrentUserUtils.ActivePresentation;
+            if (curPres) {
+                const pinDoc = Doc.MakeAlias(doc);
+                pinDoc.presentationTargetDoc = doc;
+                pinDoc.presZoomButton = true;
+                pinDoc.context = curPres;
+                Doc.AddDocToList(curPres, "data", pinDoc);
+                if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
+                if (!DocumentManager.Instance.getDocumentView(curPres)) {
+                    CollectionDockingView.AddSplit(curPres, "right");
+                }
+                DocumentManager.Instance.jumpToDocument(doc, false, undefined, Cast(doc.context, Doc, null));
+            }
+        }
+    }
+    /**
+     * Adds a document to the presentation view
+     **/
+    @undoBatch
+    @action
+    public static UnpinDoc(doc: Doc) {
+        //add this new doc to props.Document
+        const curPres = CurrentUserUtils.ActivePresentation;
+        if (curPres) {
+            const ind = DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc));
+            ind !== -1 && Doc.RemoveDocFromList(curPres, "data", DocListCast(curPres.data)[ind]);
+        }
+    }
+
+    componentDidMount() {
+        const color = () => StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white");
+        const selected = () => SelectionManager.SelectedDocuments().some(v => v.props.Document === this._document);
+        const updateTabColor = () => this.tab?.titleElement[0] && (this.tab.titleElement[0].style.backgroundColor = selected() ? color() : "");
+        const observer = new _global.ResizeObserver(action((entries: any) => {
+            for (const entry of entries) {
+                this._panelWidth = entry.contentRect.width;
+                this._panelHeight = entry.contentRect.height;
+            }
+            updateTabColor();
+        }));
+        observer.observe(this.props.glContainer._element[0]);
+        this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
+        this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged();
+        this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: color() }), updateTabColor, { fireImmediately: true });
+    }
+
+    componentWillUnmount() {
+        this._tabReaction?.();
+        this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged);
+    }
+
+    @action.bound
+    private onActiveContentItemChanged() {
+        if (this.props.glContainer.tab && this._isActive !== this.props.glContainer.tab.isActive) {
+            this._isActive = this.props.glContainer.tab.isActive;
+            this._isActive && setTimeout(() => this.view && SelectionManager.SelectDoc(this.view, false), 0);
+            (CollectionDockingView.Instance as any)._goldenLayout.isInitialised && CollectionDockingView.Instance.stateChanged();
+            !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
+        }
+    }
+
+    get layoutDoc() { return this._document && Doc.Layout(this._document); }
+    nativeAspect = () => this.nativeWidth() ? this.nativeWidth() / this.nativeHeight() : 0;
+    panelWidth = () => this.layoutDoc?.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) :
+        (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth)
+    panelHeight = () => this.nativeAspect() && this.nativeAspect() > this._panelWidth / this._panelHeight ? this._panelWidth / this.nativeAspect() : this._panelHeight;
+    nativeWidth = () => !this.layoutDoc?._fitWidth ? NumCast(this.layoutDoc?._nativeWidth) || this._panelWidth : 0;
+    nativeHeight = () => !this.layoutDoc?._fitWidth ? NumCast(this.layoutDoc?._nativeHeight) || this._panelHeight : 0;
+
+    contentScaling = () => {
+        const nativeH = this.nativeHeight();
+        const nativeW = this.nativeWidth();
+        let scaling = 1;
+        if (!this.layoutDoc?._fitWidth && (!nativeW || !nativeH)) {
+            scaling = 1;
+        } else if (NumCast(this.layoutDoc?._nativeWidth) && ((this.layoutDoc?._fitWidth) ||
+            this._panelHeight / NumCast(this.layoutDoc?._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc?._nativeWidth))) {
+            scaling = this._panelWidth / NumCast(this.layoutDoc?._nativeWidth);
+        } else if (nativeW && nativeH) {
+            const wscale = this.panelWidth() / nativeW;
+            scaling = wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
+        }
+        return scaling;
+    }
+
+    ScreenToLocalTransform = () => {
+        if (this._mainCont && this._mainCont.children) {
+            const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0].firstChild as HTMLElement);
+            const scale = Utils.GetScreenTransform(this._mainCont).scale;
+            return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
+        }
+        return Transform.Identity();
+    }
+    get previewPanelCenteringOffset() { return this.nativeWidth() ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
+    get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this._panelWidth * 100}% ` : undefined; }
+
+    // adds a tab to the layout based on the locaiton parameter which can be:
+    //  close[:{left,right,top,bottom}]  - e.g., "close" will close the tab, "close:left" will close the left tab, 
+    //  add[:{left,right,top,bottom}] - e.g., "add" will add a tab to the current stack, "add:right" will add a tab on the right
+    //  replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents, 
+    //                                  "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name, 
+    //                                   "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
+    //  inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack
+    addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => {
+        SelectionManager.DeselectAll();
+        if (doc._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
+        const locationFields = location.split(":");
+        const locationParams = locationFields.length > 1 ? locationFields[1] : "";
+        switch (locationFields[0]) {
+            case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
+            case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
+            case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
+            case "inPlace":
+            case "add":
+            default: return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack);
+        }
+    }
+
+    @computed get renderContentBounds() {
+        const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
+        const xbounds = bounds[2] - bounds[0];
+        const ybounds = bounds[3] - bounds[1];
+        const dim = Math.max(xbounds, ybounds);
+        return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
+    }
+    @computed get miniLeft() { return 50 + (NumCast(this._document?._panX) - this.renderContentBounds.cx) / this.renderContentBounds.dim * 100 - this.miniWidth / 2; }
+    @computed get miniTop() { return 50 + (NumCast(this._document?._panY) - this.renderContentBounds.cy) / this.renderContentBounds.dim * 100 - this.miniHeight / 2; }
+    @computed get miniWidth() { return this.panelWidth() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
+    @computed get miniHeight() { return this.panelHeight() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
+    childLayoutTemplate = () => Cast(this._document?.childLayoutTemplate, Doc, null);
+    returnMiniSize = () => NumCast(this._document?._miniMapSize, 150);
+    miniDown = (e: React.PointerEvent) => {
+        this._document && setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
+            this._document!._panX = clamp(NumCast(this._document!._panX) + delta[0] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.l, this.renderContentBounds.l + this.renderContentBounds.dim);
+            this._document!._panY = clamp(NumCast(this._document!._panY) + delta[1] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.t, this.renderContentBounds.t + this.renderContentBounds.dim);
+            return false;
+        }), emptyFunction, emptyFunction);
+    }
+    getCurrentFrame = (): number => {
+        const presTargetDoc = Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null);
+        return Cast(presTargetDoc._currentFrame, "number", null);
+    }
+
+    renderMiniMap() {
+        return <div className="miniMap" style={{
+            width: this.returnMiniSize(), height: this.returnMiniSize(), background: StrCast(this._document!._backgroundColor,
+                StrCast(this._document!.backgroundColor, CollectionDockingView.Instance.props.backgroundColor?.(this._document!, 0))),
+        }}>
+            <CollectionFreeFormView
+                Document={this._document!}
+                LibraryPath={emptyPath}
+                CollectionView={undefined}
+                ContainingCollectionView={undefined}
+                ContainingCollectionDoc={undefined}
+                ChildLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid havin to set stuff like this.
+                noOverlay={true} // don't render overlay Docs since they won't scale
+                active={returnTrue}
+                select={emptyFunction}
+                dropAction={undefined}
+                isSelected={returnFalse}
+                dontRegisterView={true}
+                annotationsKey={""}
+                fieldKey={Doc.LayoutFieldKey(this._document!)}
+                bringToFront={emptyFunction}
+                rootSelected={returnTrue}
+                addDocument={returnFalse}
+                moveDocument={returnFalse}
+                removeDocument={returnFalse}
+                ContentScaling={returnOne}
+                PanelWidth={this.returnMiniSize}
+                PanelHeight={this.returnMiniSize}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
+                ScreenToLocalTransform={this.ScreenToLocalTransform}
+                renderDepth={0}
+                whenActiveChanged={emptyFunction}
+                focus={emptyFunction}
+                backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
+                addDocTab={this.addDocTab}
+                pinToPres={TabDocView.PinDoc}
+                docFilters={CollectionDockingView.Instance.docFilters}
+                searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
+                fitToBox={true}
+            />
+            <div className="miniOverlay" onPointerDown={this.miniDown} >
+                <div className="miniThumb" style={{
+                    width: `${this.miniWidth}% `,
+                    height: `${this.miniHeight}% `,
+                    left: `${this.miniLeft}% `,
+                    top: `${this.miniTop}% `,
+                }}
+                />
+            </div>
+        </div>;
+    }
+    setView = action((view: DocumentView) => this._view = view);
+    @computed get docView() {
+        TraceMobx();
+        return !this._document ? (null) :
+            <><DocumentView key={this._document[Id]}
+                LibraryPath={emptyPath}
+                Document={this._document}
+                getView={this.setView}
+                DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
+                bringToFront={emptyFunction}
+                rootSelected={returnTrue}
+                addDocument={undefined}
+                removeDocument={undefined}
+                ContentScaling={this.contentScaling}
+                PanelWidth={this.panelWidth}
+                PanelHeight={this.panelHeight}
+                NativeHeight={this.nativeHeight}
+                NativeWidth={this.nativeWidth}
+                ScreenToLocalTransform={this.ScreenToLocalTransform}
+                renderDepth={0}
+                parentActive={returnTrue}
+                whenActiveChanged={emptyFunction}
+                focus={emptyFunction}
+                backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
+                addDocTab={this.addDocTab}
+                pinToPres={TabDocView.PinDoc}
+                docFilters={CollectionDockingView.Instance.docFilters}
+                searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
+                ContainingCollectionView={undefined}
+                ContainingCollectionDoc={undefined} />
+                {this._document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
+            </>;
+    }
+
+    render() {
+        return !this._isActive ? (null) :
+            (<div className="collectionDockingView-content" ref={ref => {
+                if (this._mainCont = ref) {
+                    (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
+                    DocServer.GetRefField(this.tab.contentItem.config.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.init(this.tab, this._document)));
+                }
+            }}
+                style={{
+                    transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`,
+                    height: this.layoutDoc?._fitWidth ? undefined : "100%",
+                    width: this.widthpercent
+                }}>
+                {this.docView}
+            </div >);
+    }
+}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index a03c45a53..07b2d51d1 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -1,34 +1,33 @@
 import React = require("react");
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from "@material-ui/core";
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, DocCastAsync, WidthSym } from "../../../fields/Doc";
-import { InkTool } from "../../../fields/InkField";
-import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
-import { returnFalse, returnOne, numberRange, returnTrue } from "../../../Utils";
+import { ColorState, SketchPicker } from "react-color";
+import { Doc, DocCastAsync, DocListCast } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
-import { DocumentManager } from "../../util/DocumentManager";
-import { undoBatch } from "../../util/UndoManager";
-import { CollectionDockingView, DockedFrameRenderer } from "../collections/CollectionDockingView";
-import { CollectionView, CollectionViewType } from "../collections/CollectionView";
-import { FieldView, FieldViewProps } from './FieldView';
-import { DocumentType } from "../../documents/DocumentTypes";
-import "./PresBox.scss";
-import { ViewBoxBaseComponent } from "../DocComponent";
-import { makeInterface, listSpec } from "../../../fields/Schema";
-import { Docs, DocUtils } from "../../documents/Documents";
+import { InkTool } from "../../../fields/InkField";
+import { List } from "../../../fields/List";
 import { PrefetchProxy } from "../../../fields/Proxy";
+import { listSpec, makeInterface } from "../../../fields/Schema";
 import { ScriptField } from "../../../fields/ScriptField";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
+import { returnFalse, returnOne } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { DocumentManager } from "../../util/DocumentManager";
 import { Scripting } from "../../util/Scripting";
-import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
-import { List } from "../../../fields/List";
-import { Tooltip } from "@material-ui/core";
-import { actionAsync } from "mobx-utils";
 import { SelectionManager } from "../../util/SelectionManager";
+import { undoBatch } from "../../util/UndoManager";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { TabDocView } from "../collections/TabDocView";
+import { ViewBoxBaseComponent } from "../DocComponent";
 import { AudioBox } from "./AudioBox";
-import { DocumentView } from "./DocumentView";
-import { SketchPicker, ColorState } from "react-color";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./PresBox.scss";
 
 type PresBoxSchema = makeInterface<[typeof documentSchema]>;
 const PresBoxDocument = makeInterface(documentSchema);
@@ -1112,7 +1111,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
             const presData = Cast(this.rootDoc.data, listSpec(Doc));
             if (data && presData) {
                 data.push(doc);
-                DockedFrameRenderer.PinDoc(doc, false);
+                TabDocView.PinDoc(doc, false);
                 this.gotoDocument(this.childDocs.length, this.itemIndex);
             } else {
                 this.props.addDocTab(doc, "add:right");
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index d4eb76ecd..b8633b06f 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -20,7 +20,7 @@ import { Scripting } from '../client/util/Scripting';
 import { SettingsManager } from '../client/util/SettingsManager';
 import { Transform } from '../client/util/Transform';
 import { UndoManager } from "../client/util/UndoManager";
-import { DockedFrameRenderer } from '../client/views/collections/CollectionDockingView';
+import { TabDocView } from '../client/views/collections/TabDocView';
 import { CollectionViewType } from "../client/views/collections/CollectionView";
 import { GestureOverlay } from "../client/views/GestureOverlay";
 import { AudioBox } from "../client/views/nodes/AudioBox";
@@ -516,7 +516,7 @@ export class MobileInterface extends React.Component {
             return <div className="docButton"
                 title={Doc.isDocPinned(this._activeDoc) ? "Unpin from presentation" : "Pin to presentation"}
                 style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
-                onClick={e => DockedFrameRenderer.PinDoc(this._activeDoc, isPinned)}>
+                onClick={e => TabDocView.PinDoc(this._activeDoc, isPinned)}>
                 <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
             </div>;
         } else return (null);
-- 
cgit v1.2.3-70-g09d2

